如何在ObjectInputStream.readObject中绕过ClassNotFoundException?

时间:2013-12-12 12:06:53

标签: java serialization deserialization

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

是否有办法(可能通过编写我自己的ObjectInputStream?)忽略那些ClassNotFoundException并用null替换流的相应对象?

我想要阅读的对象与此类似:

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

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

如果我发现异常,我甚至无法拥有Log对象。

2 个答案:

答案 0 :(得分:2)

实际上,我尝试了多种方式来执行此操作(扩展ObjectInputStream并实施ObjectInputStream.readClassDescriptor(),以便返回ObjectStreamClass的代理,该代理将返回null作为默认值方法ObjectStreamClass.getResolveException(),使用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!

答案 1 :(得分:0)

除了克隆和修改Java序列化实现之外,我认为没有办法做到这一点。

当然,readObjectreadResolve挂钩无济于事,因为它们依赖于您无法加载的类的方法。