Есть ли способ сообщить ObjectOutputStream
, какие поля сериализуемого класса должны быть сериализованы без использования ключевого слова transient
и без определения serialPersistentFields
-массива?
Справочная информация:Мне нужно использовать аннотации, чтобы определить, какие члены класса должны быть сериализованы (или лучше: не сериализироваться).Участвующие классы должны реализовывать интерфейс Serializable
, но НЕ Externalizable
, поэтому я не хочу реализовывать алгоритм сериализации / десериализации для каждого объекта, а просто использовать аннотации для него.Я не могу использовать ключевое слово transient
, поскольку аннотация требует некоторых дополнительных проверок, чтобы определить, следует ли сериализовать поле или нет.Эти проверки должны выполняться ObjectOutputStream
(или в моем собственном подклассе ObjectOutputStream
).Я также не могу определить serialPersistentFields
-массив в каждом классе, потому что, как объяснялось ранее, во время компиляции не определено, какие поля должны быть сериализованы.
Так что единственное, что следует отметить в затронутом классеэто аннотация на уровне поля (@Target(ElementType.FIELD)
).
За последние несколько дней я пробовал довольно много подходов, но не нашел работающего:
В ObjectOutputStream
есть метод writeObjectOverride(Object)
, который можно использовать для определения собственной реализации процесса сериализации при расширении ObjectOutputStream
.Это работает, только если ObjectOutputStream
инициализирован конструктором без аргументов, потому что в противном случае writeObjectOverride
никогда не вызывается.Но этот подход требует от меня реализации всего процесса сериализации самостоятельно, и я не хочу этого делать, поскольку он довольно сложный и уже реализован по умолчанию ObjectOutputStream
.Я ищу способ просто изменить реализацию сериализации по умолчанию.
Еще один подход - снова расширить ObjectOutputStream
и переопределить writeObjectOverride(Object)
(после вызова enableReplaceObject(true)
).В этом методе я попытался использовать какой-нибудь SerializationProxy (см. Что такое шаблон прокси-сервера сериализации? ) для инкапсуляции сериализованного объекта в прокси, который определяет список полей, которые должны быть сериализованы.Но этот подход также терпит неудачу, так как writeObjectOverride затем также вызывается для списка полей (List<SerializedField> fields
) в Proxy, что приводит к бесконечному циклу.
Пример:
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 {
}
Когда я обнаружил, что можно модифицировать модификаторы во время выполнения (см. Изменение частного статического конечного поля с использованием отражения Java )Я попытался установить Transient-Modifier во время выполнения, если была установлена соответствующая аннотация.К сожалению, это также не работает, потому что подход, использованный в предыдущей ссылке, кажется, работает только для статических полей.При попытке сделать это с нестатическими полями, он запускается без исключения, но не сохраняется, потому что выглядит так: 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")
, но я не могу связать значение name
(то есть test
) с классом, содержащим поле test
, поскольку нет ссылки насодержащий класс доступен, и поэтому невозможно прочитать аннотацию, отмеченную для поля test
.
Я также попробовал несколько других подходов, но, как уже упоминалось, я не смог успешно сериализовать все полякроме тех, которые с аннотацией DontSerialize
присутствует.
Я также сталкивался с манипуляторами ByteCode. Может быть, это возможно с этим, но у меня есть требование не использовать какие-либо внешние инструменты - это должна быть чистая Java (1.5 или 1.6).
Извините за этот действительно длинный пост, но я просто хотел показать, что я уже пробовал, и надеюсь, что кто-то может мне помочь.
Заранее спасибо.