Подтвердить что ты не робот

Сделать Java runtime игнорировать serialVersionUID?

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

Мне непрактично возвращаться и исправлять весь этот сторонний код.

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

4b9b3361

Ответ 1

Если у вас есть доступ к базе кода, вы можете использовать задачу 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

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

private long serialVersionUID = 1L;

всюду?

Ответ 3

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

Ответ 4

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

Ответ 5

Вы можете использовать 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;
   }
}

Вам нужно будет добавить aspectj loader weaver agent в виртуальную машину, чтобы он мог сплести совет в ваши существующие сторонние классы. Это забавно, хотя как только вы перейдете к настройке Aspectj, его замечательное количество применений, которое вы будете использовать.

НТН

Ste