5

バージョン トレラント ストレージのクラス構造に柔軟性を持たせるために、Java シリアライゼーション メカニズムのさまざまなオプションを調査しています (別のメカニズムを提唱しています。教えていただく必要はありません)。

たとえば、下位互換性のみが必要な場合、デフォルトのシリアル化メカニズムはフィールドの追加と削除の両方を処理できます。

ただし、クラスの名前を変更したり、別のパッケージに移動したりすることは、はるかに難しいことがわかっています。この質問で、ObjectInputStream をサブクラス化し、readClassDescriptor() をオーバーライドすることで、単純なクラスの名前変更および/またはパッケージの移動を実行できることがわかりました。

    if (resultClassDescriptor.getName().equals("package.OldClass"))
        resultClassDescriptor = ObjectStreamClass.lookup(newpackage.NewClass.class);

単純な名前変更には問題ありません。しかし、フィールドを追加または削除しようとすると、java.io.StreamCorruptedException が発生します。さらに悪いことに、これは、フィールドが追加または削除された後にクラスの名前を変更した場合でも発生します。これにより、複数の開発者または複数のチェックインで問題が発生する可能性があります。

私が行ったいくつかの読書に基づいて、名前を新しいクラスに正しく再指定しているが、古いクラス自体をロードせず、フィールドの変更を爆撃していないという考えで、resolveClass() のオーバーライドも少し試しました。しかし、これは、シリアライゼーション メカニズムの詳細についての非常に漠然とした理解に由来するものであり、正しいツリーをほえているかどうかさえわかりません。

だから2つの正確な質問:

  1. readClassDescriptor() を使用してクラス名を再指定すると、互換性のある通常のクラス変更で逆シリアル化が失敗するのはなぜですか?
  2. resolveClass() または別のメカニズムを使用してこれを回避し、クラスの進化 (フィールドの追加と削除) と名前の変更/再パッケージ化の両方を可能にする方法はありますか?

私はぶらぶらして、SOで同等の質問を見つけることができませんでした。どうしても、そのような質問があれば教えてください。しかし、別の質問が私の正確な質問に実際に答えない限り、私を閉じないように、質問を注意深く読んでください。

4

5 に答える 5

9

私はあなたと同じように柔軟性に関して同じ問題を抱えていましたが、道を見つけました。ここで私のバージョンの readClassDescriptor()

    static class HackedObjectInputStream extends ObjectInputStream
{

    /**
     * Migration table. Holds old to new classes representation.
     */
    private static final Map<String, Class<?>> MIGRATION_MAP = new HashMap<String, Class<?>>();

    static
    {
        MIGRATION_MAP.put("DBOBHandler", com.foo.valueobjects.BoardHandler.class);
        MIGRATION_MAP.put("DBEndHandler", com.foo.valueobjects.EndHandler.class);
        MIGRATION_MAP.put("DBStartHandler", com.foo.valueobjects.StartHandler.class);
    }

    /**
     * Constructor.
     * @param stream input stream
     * @throws IOException if io error
     */
    public HackedObjectInputStream(final InputStream stream) throws IOException
    {
        super(stream);
    }

    @Override
    protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException
    {
        ObjectStreamClass resultClassDescriptor = super.readClassDescriptor();

        for (final String oldName : MIGRATION_MAP.keySet())
        {
            if (resultClassDescriptor.getName().equals(oldName))
            {
                String replacement = MIGRATION_MAP.get(oldName).getName();

                try
                {
                    Field f = resultClassDescriptor.getClass().getDeclaredField("name");
                    f.setAccessible(true);
                    f.set(resultClassDescriptor, replacement);
                }
                catch (Exception e)
                {
                    LOGGER.severe("Error while replacing class name." + e.getMessage());
                }

            }
        }

        return resultClassDescriptor;
    }
于 2013-01-30T15:49:30.123 に答える
1

これが writeReplace() と readResolve() の目的です。あなたはそれを実際よりもはるかに複雑にしています。これらのメソッドは、関連する 2 つのオブジェクト、または 3 つのオブジェクト ストリーム クラスのサブクラスで定義できることに注意してください。

于 2012-06-08T21:57:05.570 に答える
1

問題は、 readClassDescriptor が ObjectInputStream に、現在読み込んでいるストリームにあるデータを読み取る方法を伝えることになっていることです。シリアル化されたデータ ストリームの内部を見ると、データが格納されているだけでなく、正確にどのフィールドが存在するかに関する多くのメタデータが格納されていることがわかります。これにより、シリアライゼーションで単純なフィールドの追加/削除を処理できるようになります。ただし、そのメソッドをオーバーライドしてストリームから返された情報を破棄すると、シリアル化されたデータに含まれるフィールドに関する情報 が破棄されます。

問題の解決策は、super.readClassDescriptor() によって返された値を取得し、新しいクラス名を返す新しいクラス記述子を作成することです、それ以外の場合は古い記述子から情報を返すことだと思います。(ただし、ObjectStreamField を見ると、それよりも複雑になる場合がありますが、それが一般的な考え方です)。

于 2012-06-07T17:58:57.400 に答える
-1

私はクラス記述子を十分にいじっていませんが、問題が名前の変更と再パッケージ化だけである場合は、それに対するはるかに簡単な解決策があります。シリアル化されたデータ ファイルをテキスト エディターで編集し、古い名前を新しい名前に置き換えるだけです。人間が読める形式で存在します。たとえば、これをOldClass内部に配置し、次のようoldpackageに を含むとします。oldField

package oldpackage;

import java.io.Serializable;

public class OldClass implements Serializable
{
    int oldField;
}

このクラスのインスタンスをシリアル化し、次のようなものを取得すると:

¬í sr oldpackage.OldClasstqŽÇ§Üï I oldFieldxp    

クラスの名前を に変更してNewClass内部に配置しnewpackage、そのフィールドの名前を に変更したい場合はnewField、次のようにファイルの名前を変更するだけです。

¬í sr newpackage.NewClasstqŽÇ§Üï I newFieldxp    

新しいクラスに適切な serialVersionUID を定義します。

それで全部です。拡張とオーバーライドは必要ありません。

于 2012-06-07T18:44:25.987 に答える