Заставить Java во время выполнения игнорировать serialVersionUID? - PullRequest
15 голосов
/ 29 ноября 2009

Мне приходится работать с большим количеством скомпилированных классов Java, которые явно не указали serialVersionUID. Поскольку их UID были произвольно сгенерированы компилятором, многие из классов, которые необходимо сериализовать и десериализовать, в конечном итоге вызывают исключения, даже если фактические определения классов совпадают. (Это все ожидаемое поведение, конечно.)

Для меня нецелесообразно возвращаться и исправлять весь этот сторонний код.

Поэтому мой вопрос таков: есть ли способ заставить среду выполнения Java игнорировать различия в serialVersionUID и не выполнить десериализацию только при наличии реальных различий в структуре?

Ответы [ 5 ]

35 голосов
/ 29 ноября 2009

Если у вас есть доступ к базе кода, вы можете использовать задачу SerialVer для Ant , чтобы вставить и изменить serialVersionUID в исходном коде сериализуемого класса и устранить проблему раз и навсегда .

Если вы не можете, или если это не вариант (например, если вы уже сериализовали некоторые объекты, которые необходимо десериализовать), одним из решений будет расширение ObjectInputStream. Увеличьте его поведение, чтобы сравнить serialVersionUID дескриптора потока с serialVersionUID класса в локальной JVM, который представляет этот дескриптор, и использовать дескриптор локального класса в случае несоответствия. Затем просто используйте этот пользовательский класс для десериализации. Примерно так (кредиты это сообщение ):

import java.io.IOException;
import java.io.InputStream;
import java.io.InvalidClassException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


public class DecompressibleInputStream extends ObjectInputStream {

    private static Logger logger = LoggerFactory.getLogger(DecompressibleInputStream.class);

    public DecompressibleInputStream(InputStream in) throws IOException {
        super(in);
    }

    protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
        ObjectStreamClass resultClassDescriptor = super.readClassDescriptor(); // initially streams descriptor
        Class localClass; // the class in the local JVM that this descriptor represents.
        try {
            localClass = Class.forName(resultClassDescriptor.getName()); 
        } catch (ClassNotFoundException e) {
            logger.error("No local class for " + resultClassDescriptor.getName(), e);
            return resultClassDescriptor;
        }
        ObjectStreamClass localClassDescriptor = ObjectStreamClass.lookup(localClass);
        if (localClassDescriptor != null) { // only if class implements serializable
            final long localSUID = localClassDescriptor.getSerialVersionUID();
            final long streamSUID = resultClassDescriptor.getSerialVersionUID();
            if (streamSUID != localSUID) { // check for serialVersionUID mismatch.
                final StringBuffer s = new StringBuffer("Overriding serialized class version mismatch: ");
                s.append("local serialVersionUID = ").append(localSUID);
                s.append(" stream serialVersionUID = ").append(streamSUID);
                Exception e = new InvalidClassException(s.toString());
                logger.error("Potentially Fatal Deserialization Operation.", e);
                resultClassDescriptor = localClassDescriptor; // Use local class descriptor for deserialization
            }
        }
        return resultClassDescriptor;
    }
}
2 голосов
/ 29 ноября 2009

Использовать CGLIB для вставки их в двоичные классы?

2 голосов
/ 29 ноября 2009

Насколько непрактично это исправить? Если у вас есть исходный код и вы можете его перестроить, можете ли вы просто запустить скрипт над всей базой кода, чтобы вставить

private long serialVersionUID = 1L;

везде?

1 голос
/ 29 ноября 2009

Ошибки сериализации во время выполнения указывают вам явно, какой ожидается идентификатор. Просто измените ваши классы, чтобы объявить их в качестве идентификатора, и все будет хорошо. Это требует от вас внесения изменений, но я не верю, что этого можно избежать

0 голосов
/ 30 ноября 2009

Вы можете использовать Aspectj, чтобы «ввести» поле в каждый сериализуемый класс по мере его загрузки. Сначала я хотел бы ввести интерфейс маркера в каждый класс, используя пакет, а затем ввести поле, используя хэш файла класса для serialVersionUID

public aspect SerializationIntroducerAspect {

   // introduce marker into each class in the org.simple package
   declare parents: (org.simple.*) implements SerialIdIntroduced;

   public interface SerialIdIntroduced{}

   // add the field to each class marked with the interface above.
   private long SerialIdIntroduced.serialVersionUID = createIdFromHash(); 

   private long SerialIdIntroduced.createIdFromHash()
   {
       if(serialVersionUID == 0)
       {
           serialVersionUID = getClass().hashCode();
       }
       return serialVersionUID;
   }
}

Вам нужно будет добавить ткач агента времени загрузки аспекта *1005* к ВМ, чтобы он мог интегрировать рекомендации в существующие классы сторонних разработчиков. Забавно, что, как только вы приступите к настройке Aspectj, вы заметите, сколько раз вы его используете.

НТН

Ste

...