Укажите, какие поля (не) сериализуются в ObjectOutputStream без использования transient или serialPersistentFields - PullRequest
7 голосов
/ 10 марта 2011

Есть ли способ сообщить 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).


Извините за этот действительно длинный пост, но я просто хотел показать, что я уже пробовал, и надеюсь, что кто-то может мне помочь. Заранее спасибо.

Ответы [ 2 ]

0 голосов
/ 15 июля 2019

Не переписывая большую часть сериализации Java, вам нужно будет переписать байт-код. Во время выполнения это может быть сделано с агентами Java, но также может быть сделано для файлов классов во время сборки.

0 голосов
/ 11 марта 2011

Я бы пересмотрел, если "Serialization" действительно то, что вы хотите сделать.Учитывая, что правила сериализации зависят от некоторой логики, определенной во время выполнения, процесс десериализации будет кошмаром для написания.

Хотя интересная проблема.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...