を使用せずにserialPersistentFields
配列を定義しないでシリアル化可能なクラスのフィールドをシリアル化する方法はありますか?transientまたはserialPersistentFieldsを使用せずにObjectOutputStreamでシリアル化するフィールドを指定する
背景:私はクラスのメンバーをシリアル化(またはより良い:シリアル化されていない)しなければならないかを定義するためのアノテーションを使用する必要があります。関与するクラスはSerializable
のインターフェイスを実装する必要がありますが、Externalizable
ではなく、各オブジェクトのシリアル化/逆シリアル化アルゴリズムを実装するのではなく、アノテーションを使用します。注釈には、フィールドをシリアル化するかどうかを判断するために、さらにチェックする必要があるため、transient
キーワードを使用することはできません。これらの小切手は、ObjectOutputStream
(または私自身のサブクラスObjectOutputStream
)によって行われなければなりません。前に説明したように、コンパイル時にはどのフィールドをシリアル化するべきかが定義されていないため、各クラスにserialPersistentFields
配列を定義することもできません。
影響を受けたクラスでnotetする必要があるのは、フィールドレベルの注釈(@Target(ElementType.FIELD)
)だけです。
:
ObjectOutputStream
が独自に定義するために使用できる方法writeObjectOverride(Object)
を持っていますObjectOutputStream
を拡張するときのシリアライゼーションプロセスの実装。これは、ObjectOutputStream
がno-argument-constructorで初期化されている場合にのみ有効です。それ以外の場合はwriteObjectOverride
が呼び出されないためです。しかし、このアプローチでは、自分でシリアル化プロセス全体を実装する必要があり、これは非常に複雑でデフォルトのObjectOutputStream
によって既に実装されているため、これを実行したくありません。私はちょうど既定のシリアル化の実装を変更する方法を探しています。
別のアプローチは、(enableReplaceObject(true)
を呼び出した後に)再びObjectOutputStream
を拡張し、writeObjectOverride(Object)
をオーバーライドました。このメソッドでは、何らかのSerializationProxy(What is the Serialization Proxy Pattern?を参照)を使用して、シリアライズする必要のあるフィールドのリストを定義するプロキシで直列化オブジェクトをカプセル化しようとしました。しかし、このアプローチはまた、writeObjectOverrideがプロキシ内のフィールドリスト(List<SerializedField> fields
)に対しても呼び出され、無限ループに終わるので失敗します。
例:
public class AnnotationAwareObjectOutputStream extends ObjectOutputStream {
public AnnotationAwareObjectOutputStream(OutputStream out)
throws IOException {
super(out);
enableReplaceObject(true);
}
@Override
protected Object replaceObject(Object obj) throws IOException {
try {
return new SerializableProxy(obj);
} catch (Exception e) {
return new IOException(e);
}
}
private class SerializableProxy implements Serializable {
private Class<?> clazz;
private List<SerializedField> fields = new LinkedList<SerializedField>();
private SerializableProxy(Object obj) throws IllegalArgumentException,
IllegalAccessException {
clazz = obj.getClass();
for (Field field : getInheritedFields(obj.getClass())) {
// add all fields which don't have an DontSerialize-Annotation
if (!field.isAnnotationPresent(DontSerialize.class))
fields.add(new SerializedField(field.getType(), field
.get(obj)));
}
}
public Object readResolve() {
// TODO: reconstruct object of type clazz and set fields using
// reflection
return null;
}
}
private class SerializedField {
private Class<?> type;
private Object value;
public SerializedField(Class<?> type, Object value) {
this.type = type;
this.value = value;
}
}
/** return all fields including superclass-fields */
public static List<Field> getInheritedFields(Class<?> type) {
List<Field> fields = new ArrayList<Field>();
for (Class<?> c = type; c != null; c = c.getSuperclass()) {
fields.addAll(Arrays.asList(c.getDeclaredFields()));
}
return fields;
}
}
// I just use the annotation DontSerialize in this example for simlicity.
// Later on I want to parametrize the annotation and do some further checks
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DontSerialize {
}
私は実行時に修飾子を変更することが可能であることを見出した(Change private static final field using Java reflectionを参照してください)私は、対応するアノテーションが設定された場合は、実行時に過渡-モディファイを設定しようとしました。 残念ながら、これもうまくいきません。これは、前のリンクで使用されていたアプローチが静的なフィールドでしか機能しないように思われるためです。 それは例外なく動作しますが、永続化されていない非静的フィールドでそれをしようとするためField.class.getDeclaredField(...)
戻りますように影響を受ける分野の新しいインスタンスを探しますされている場合、それが呼び出されるたび:
public void setTransientTest() throws SecurityException,
NoSuchFieldException, IllegalArgumentException,
IllegalAccessException {
Class<MyClass> clazz = MyClass.class;
// anyField is defined as "private String anyField"
Field field = clazz.getDeclaredField("anyField");
System.out.println("1. is "
+ (Modifier.isTransient(field.getModifiers()) ? "" : "NOT ")
+ "transient");
Field modifiersField = Field.class.getDeclaredField("modifiers");
boolean wasAccessible = modifiersField.isAccessible();
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() | Modifier.TRANSIENT);
modifiersField.setAccessible(wasAccessible);
System.out.println("2. is "
+ (Modifier.isTransient(field.getModifiers()) ? "" : "NOT ")
+ "transient");
Field field2 = clazz.getDeclaredField("anyField");
System.out.println("3. is "
+ (Modifier.isTransient(field2.getModifiers()) ? "" : "NOT ")
+ "transient");
}
は、出力は次のようになります。
1. is NOT transient
2. is transient
3. is NOT transient
したがって、getDeclaredFieldを再度呼び出した後(Field field2 = clazz.getDeclaredField("anyField");
)、トランジエントモディファイアは既に失われています。
次にアプローチ:
はObjectOutputStream
を拡張し、ObjectOutputStream.PutField putFields()
をオーバーライドし、自PutField-実装を定義します。 PutFieldでは、どのフィールドをシリアル化するかを指定することができますが、残念ながらインターフェイスにはput(String name, <type> val)
のメソッドしかありません。これらのメソッドを実装するときは、メソッド呼び出しを呼び出し元のクラスフィールドに関連付けることができません。たとえば、private String test = "foo"
と宣言されたフィールドをシリアル化する場合、メソッドput("test", "foo")
が呼び出されますが、フィールドtest
を含むクラスにname
(つまりtest
)の値を関連付けることはできません。フィールドtest
の注釈を読んでください。
私はまた、他のいくつかのアプローチを試みたが、すでに私が正常に注釈DontSerialize
存在を持つものを除くすべてのフィールドをシリアル化することができませんでした言及しました。
私はByteCodeマニピュレータも出てきました。たぶんそれは可能ですが、私は外部ツールを使わないという要件があります - 純粋なJava(1.5または1.6)である必要があります。
これは本当に長い記事のために申し訳ありませんが、私はちょうど私はすでに試したし、誰かが私を助けることができることを期待しています何を見せたかったです。 ありがとうございます。
これはかなり読んでいます.... –
なぜ私は一時的なキーワードを使用することはできません。何らかの理由で注釈を使用する必要があるように見えますが、これにより一時的な使用を停止することはありません。 –
'DontSerialize'を使っているのは悪い例でした。私の注釈は 'DontSerializeOnMondays'と呼ばれます(このアノテーションの目的は疑問ではありません)。 ObjectOutputStreamはオブジェクトをシリアライズする必要があるとき、曜日が月曜日かどうかをチェックし、そうであれば、注釈が関連付けられているフィールドをシリアル化するのではなく、クラス内の他のすべてのフィールドをシリアル化します。したがって、フィールドを含むクラスのコードを書くときには、直列化プロセスが月曜日に実行されるかどうかわからないので、キーワード 'transient'を定義する必要があるかどうかはわかりません。 – m0m0