发布于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黑洞网
任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任
昵称:
评论内容:(最多支持255个字符)
---无人问津也好,技不如人也罢,你都要试着安静下来,去做自己该做的事,而不是让内心的烦躁、焦虑,坏掉你本来就不多的热情和定力
Copyright © 2018-2021 java黑洞网 All Rights Reserved 版权所有,并保留所有权利。京ICP备18063182号-2
投诉与举报,广告合作请联系vgs_info@163.com或QQ3083709327
免责声明:网站文章均由用户上传,仅供读者学习交流使用,禁止用做商业用途。若文章涉及色情,反动,侵权等违法信息,请向我们举报,一经核实我们会立即删除!