程序员最近都爱上了这个网站  程序员们快来瞅瞅吧!  it98k网:it98k.com

本站消息

站长简介/公众号

  出租广告位,需要合作请联系站长


+关注
已关注

分类  

暂无分类

标签  

暂无标签

日期归档  

暂无数据

使用过时的接口读取序列化的 lambda

发布于2022-12-05 19:56     阅读(1140)     评论(0)     点赞(0)     收藏(1)


曾几何时,有人注意到第三方图书馆有以下内容:

public interface SerializableFunction<I, O> implements Function<I, O>, Serializable {
}

他们用一个Serializable类(称之为Q)编写了一些代码,其中SerializableFunction<X,Y>包含一些 X 和 Y 类型的字段。该字段总是分配给一个lambda.

class Q implements Serializable {
    SerializableFunction<X,Y> lfield;
}

不幸的是,该库的许可证不友好,我们需要停止使用它。有没有办法创建一个Q可以读取旧数据的 readObject 方法?还是别无选择,只能创建新数据?

class Q阅读时可用但是,它使用不同的SerializableFunction接口——不同之处仅在于它位于不同的包中。

早期迹象表明没有办法解决这个问题:基本的例外是IllegalArgumentException读取lambda. 我看不到进入该过程并避免它的方法,但也许我遗漏了一些东西。


解决方案


通常,序列化机制非常健壮。如果您serialVersionUID按照流类描述符的预期在您的类中声明相同的内容,序列化实现将忽略不存在的字段并保留流中不存在的新字段的默认值。

您甚至可以实现readObject用于初始化新字段的方法或提供重构字段的翻译,例如

private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
    ObjectInputStream.GetField fields = ois.readFields();
    this.foo = (String)fields.get("oldNameOfFoo", null);
}

但是,有一个问题:现在不存在的字段引用的对象在删除之前仍然被反序列化,将任何相关问题传播给调用者。由于我们无法将转换代码(如readObject方法)注入到 JRE 提供的SerializedLambda表示中,因此没有太多选项可以捕获序列化 lambda 表达式定义类的缺失。

无论哪种情况,我们都需要控制ObjectInputStream创作。如果我们动手,首先,我们创建一个替换SerializedLambda类,它允许我们自定义过程,即将旧的功能接口重定向到新的:

public final class MySerializedLambda implements Serializable {
    private final Class<?> capturingClass;
    private final String functionalInterfaceClass;
    private final String functionalInterfaceMethodName;
    private final String functionalInterfaceMethodSignature;
    private final String implClass;
    private final String implMethodName;
    private final String implMethodSignature;
    private final int implMethodKind;
    private final String instantiatedMethodType;
    private final Object[] capturedArgs;

    private MySerializedLambda() {
        throw new UnsupportedOperationException();
    }

    private Object readResolve() throws ReflectiveOperationException {
        String funcInterfaceClass = this.functionalInterfaceClass;
        if(funcInterfaceClass.equals("package/to/old/SerializableFunction")) {
            funcInterfaceClass="package/to/new/SerializableFunction";
        }
        SerializedLambda serializedLambda = new SerializedLambda(capturingClass,
            funcInterfaceClass, functionalInterfaceMethodName,
            functionalInterfaceMethodSignature, implMethodKind, implClass, implMethodName,
            implMethodSignature, instantiatedMethodType, capturedArgs);
        Method m = capturingClass
                  .getDeclaredMethod("$deserializeLambda$", SerializedLambda.class);
        m.setAccessible(true);
        return m.invoke(null, serializedLambda);
    }
}

它具有与原始类完全相同的字段,SerializedLambda因此我们可以读取它们,然后追溯SerializedLambda该步骤中将执行的操作readResolve(),但替换功能接口。

要使用这个类,我们需要一个子类ObjectInputStream

try(FileInputStream os=new FileInputStream(serialized);
    ObjectInputStream oos=new ObjectInputStream(os) {
        @Override
        protected ObjectStreamClass readClassDescriptor()
                                    throws IOException, ClassNotFoundException {
            final ObjectStreamClass d = super.readClassDescriptor();
            if(d.getName().equals("java.lang.invoke.SerializedLambda")) {
                return ObjectStreamClass.lookup(MySerializedLambda.class);
            }
            return d;
        }
    }) {
    Q q = (Q)oos.readObject();
}

readClassDescriptor()负责验证流类与运行时类的兼容性,所以如果我们之后重定向结果,不同的名称/包/没有serialVersionUID影响。
不幸的是,SerializedLambda实例中的功能接口表示为 a String,所以我们不能以相同的方式重定向它......
注意接口的字符串表示使用 JVM 内部语法,即/代替.包和类分离以及$内部类.

很老套,但恐怕没有更好的解决方案。


请注意,由于依赖于保存 lambda 主体的编译器生成的方法,序列化 lambda 表达式总是很脆弱。如果您使用使用相同编译器编译的其他未更改类,则上述解决方案有效,因此功能签名匹配并且合成方法恰好以与以前完全相同的形式存在。



所属网站分类: 技术文章 > 问答

作者:黑洞官方问答小能手

链接:http://www.javaheidong.com/blog/article/582887/acb169f145e3d9e8a42a/

来源:java黑洞网

任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任

0 0
收藏该文
已收藏

评论内容:(最多支持255个字符)