java - 如何在 ObjectInputStream.readObject 时绕过 ClassNotFoundException?

标签 java serialization deserialization

我正在尝试读取一个序列化的 Java 文件,其中包含我在读取时类路径中没有的类的实例。

有没有办法(也许通过自己编写ObjectInputStream?)来忽略那些ClassNotFoundException并将流的相应对象替换为null ?

我要读取的对象与这个类似:

public class Log {
    private String someField;
    private Throwable throwable;
}

实际上,Log 对象已被读取,但我的类路径中没有某些 Log.throwable 值的具体类。我希望在那种情况下,throwable 字段值将为 null,但我希望我的 Log 对象读取其他字段。

如果我捕获异常,我什至无法拥有我的 Log 对象。

最佳答案

实际上,我尝试了多种方法来做到这一点(扩展 ObjectInputStream 并实现 ObjectInputStream.readClassDescriptor() 以返回 ObjectStreamClass< 的代理 将为默认方法 ObjectStreamClass.getResolveException() 返回 null,使用 Javassist 因为 JDK 无法代理类,但问题是:ObjectStreamClass 不能在 java.io 包之外实例化)。

但我终于找到了一种(相当丑陋的)方法来做到这一点:

public class DecompressibleObjectInputStream extends ObjectInputStream {
    private static Logger logger = LoggerFactory.getLogger(DecompressibleObjectInputStream.class);

    public DecompressibleObjectInputStream(InputStream in) throws IOException {
        super(in);

        try {
            // activating override on readObject thanks to https://stackoverflow.com/a/3301720/535203
            Field enableOverrideField = ObjectInputStream.class.getDeclaredField("enableOverride");

            enableOverrideField.setAccessible(true);

            Field fieldModifiersField = Field.class.getDeclaredField("modifiers");
            fieldModifiersField.setAccessible(true);
            fieldModifiersField.setInt(enableOverrideField, enableOverrideField.getModifiers() & ~Modifier.FINAL);

            enableOverrideField.set(this, true);
        } catch (NoSuchFieldException e) {
            warnCantOverride(e);
        } catch (SecurityException e) {
            warnCantOverride(e);
        } catch (IllegalArgumentException e) {
            warnCantOverride(e);
        } catch (IllegalAccessException e) {
            warnCantOverride(e);
        }
    }

    private void warnCantOverride(Exception e) {
        logger.warn("Couldn't enable readObject override, won't be able to avoid ClassNotFoundException while reading InputStream", e);
    }
    @Override
    public void defaultReadObject() throws IOException, ClassNotFoundException {
        try {
            super.defaultReadObject();
        } catch (ClassNotFoundException e) {
            logger.warn("Potentially Fatal Deserialization Operation.", e);
        }
    }

    @Override
    protected Object readObjectOverride() throws IOException, ClassNotFoundException {
    // copy of JDK 7 code avoiding the ClassNotFoundException to be thrown :
        /*
            // if nested read, passHandle contains handle of enclosing object
        int outerHandle = passHandle;
        try {
            Object obj = readObject0(false);
            handles.markDependency(outerHandle, passHandle);
            ClassNotFoundException ex = handles.lookupException(passHandle);
            if (ex != null) {
                throw ex;
            }
            if (depth == 0) {
                vlist.doCallbacks();
            }
            return obj;
        } finally {
            passHandle = outerHandle;
            if (closed && depth == 0) {
                clear();
            }
        }
         */
        try {
        int outerHandle = getObjectInputStreamFieldValue("passHandle");
        int depth = getObjectInputStreamFieldValue("depth");
        try {
            Object obj = callObjectInputStreamMethod("readObject0", new Class<?>[] {boolean.class}, false);
            Object handles = getObjectInputStreamFieldValue("handles");
            Object passHandle = getObjectInputStreamFieldValue("passHandle");
            callMethod(handles, "markDependency", new Class<?>[] {int.class, int.class}, outerHandle, passHandle);

            ClassNotFoundException ex = callMethod(handles, "lookupException", new Class<?>[] {int.class},  passHandle);

            if (ex != null) {
                logger.warn("Avoiding exception", ex);
            }
            if (depth == 0) {
                callMethod(getObjectInputStreamFieldValue("vlist"), "doCallbacks", new Class<?>[] {});
            }
            return obj;
        } finally {
            getObjectInputStreamField("passHandle").setInt(this, outerHandle);
            boolean closed = getObjectInputStreamFieldValue("closed");
            if (closed && depth == 0) {
                callObjectInputStreamMethod("clear", new Class<?>[] {});
            }
        }
        } catch (NoSuchFieldException e) {
            throw createCantMimicReadObject(e);
        } catch (SecurityException e) {
            throw createCantMimicReadObject(e);
        } catch (IllegalArgumentException e) {
            throw createCantMimicReadObject(e);
        } catch (IllegalAccessException e) {
            throw createCantMimicReadObject(e);
        } catch (InvocationTargetException e) {
            throw createCantMimicReadObject(e);
        } catch (NoSuchMethodException e) {
            throw createCantMimicReadObject(e);
        } catch (Throwable t) {
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            }
            if (t instanceof IOException) {
                throw (IOException)t;
            }
            throw createCantMimicReadObject(t);
        }
    }

    private IllegalStateException createCantMimicReadObject(Throwable t) {
        return new IllegalStateException("Can't mimic JDK readObject method", t);
    }

    @SuppressWarnings("unchecked")
    private <T> T getObjectInputStreamFieldValue(String fieldName) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
        Field declaredField = getObjectInputStreamField(fieldName);
        return (T) declaredField.get(this);
    }

    private Field getObjectInputStreamField(String fieldName) throws NoSuchFieldException {
        Field declaredField = ObjectInputStream.class.getDeclaredField(fieldName);
        declaredField.setAccessible(true);
        return declaredField;
    }

    @SuppressWarnings("unchecked")
    private <T> T callObjectInputStreamMethod(String methodName, Class<?>[] parameterTypes, Object... args) throws Throwable {
        Method declaredMethod = ObjectInputStream.class.getDeclaredMethod(methodName, parameterTypes);
        declaredMethod.setAccessible(true);
        try {
            return (T) declaredMethod.invoke(this, args);
        } catch (InvocationTargetException e) {
            throw e.getCause();
        }
    }

    @SuppressWarnings("unchecked")
    private <T> T callMethod(Object object, String methodName, Class<?>[] parameterTypes, Object... args) throws Throwable {
        Method declaredMethod = object.getClass().getDeclaredMethod(methodName, parameterTypes);
        declaredMethod.setAccessible(true);
        try {
            return (T) declaredMethod.invoke(object, args);
        } catch (InvocationTargetException e) {
            throw e.getCause();
        }
    }
}

然后我覆盖了 ObjectInputStream.readClassDescriptor() 以忽略 serialVersionUID 之间的差异(如 that answer 中所述)并且我有一个 ObjectInputStream 几乎可以读取所有内容!

关于java - 如何在 ObjectInputStream.readObject 时绕过 ClassNotFoundException?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20543100/

相关文章:

java - Google App Engine 持久性中的 2d 或 3d 数组?

c# - 如何使用 C# 反序列化 JSON

java - 如何使序列化类的输出更有效率?

c# - 如何重命名和重新映射具有非法 json 字段名称的字段

java - 序列化:将对象作为 URL 参数传递并提取值

java - 在运行用户提供的 Java 代码时,我应该防范哪些安全风险?

java - 读取由制表符分隔的行中的字段

xaml - 将 Activity 序列化为 xaml

java - 在 Spring 中编写 JSON 反序列化器或对其进行扩展的正确方法

java - charAt 方法返回一个整数。需要转成int