【问题标题】:How to safely serialize a lambda?如何安全地序列化 lambda?
【发布时间】:2016-06-24 16:47:19
【问题描述】:

虽然it is possible to serialize a lambda in Java 8,却是strongly discouraged;甚至serializing inner classes is discouraged。给出的原因是 lambda 可能无法在另一个 JRE 上正确反序列化。但是,这是否意味着一种安全序列化 lambda 的方法?

例如,假设我定义一个类是这样的:

public class MyClass {
    private String value;
    private Predicate<String> validateValue;

    public MyClass(String value, Predicate<String> validate) {
        this.value = value;
        this.validateValue = validate;
    }

    public void setValue(String value) {
        if (!validateValue(value)) throw new IllegalArgumentException();
        this.value = value;
    }

    public void setValidation(Predicate<String> validate) {
        this.validateValue = validate;
    }
}

如果我这样声明一个类的实例,我就不应该序列化它:

MyClass obj = new MyClass("some value", (s) -> !s.isEmpty());

但是如果我像这样创建一个类的实例呢:

// Could even be a static nested class
public class IsNonEmpty implements Predicate<String>, Serializable {
    @Override
    public boolean test(String s) {
        return !s.isEmpty();
    }
}
MyClass isThisSafeToSerialize = new MyClass("some string", new IsNonEmpty());

现在可以安全地序列化吗?我的直觉说是的,它应该是安全的,因为没有理由将java.util.function 中的接口与任何其他随机接口区别对待。但我还是很警惕。

【问题讨论】:

  • 接口与序列化完全无关,所以是的,实现Predicate 与实现任何其他接口具有相同的影响,none。但是您认为 lambda 可能无法在另一个 JRE 上正确反序列化的假设是错误的。有一个well defined persistent representation
  • @Holger 那为什么oracle docs 似乎暗示他们没有?
  • 嗯,它包含对inner class related problems with serialization 的引用。简而言之,它可能会创建编译器依赖项,而不是 JRE 特定的问题。当然,文本有点误导。请注意意外序列化捕获的周围上下文值的危险,包括this...

标签: java serialization lambda java-8


【解决方案1】:

这取决于您想要哪种安全性。并非不能在不同的 JRE 之间共享序列化的 lambda。它们具有明确定义的持久表示,SerializedLambda。当您研究它的工作原理时,您会发现它依赖于定义类的存在,该定义类将具有重构 lambda 的特殊方法。

使它不可靠的是对编译器特定工件的依赖,例如合成目标方法,它有一些生成的名称,所以像插入另一个 lambda 表达式或使用不同的编译器重新编译类这样的简单更改可能会破坏与现有序列化 lambda 表达式的兼容性。

但是,使用手动编写的类也不能幸免。如果没有明确声明的serialVersionUID,默认算法将通过散列类工件(包括private 和合成工件)来计算一个id,并添加类似的编译器依赖项。因此,如果您想要可靠的持久化表单,最少要做的是声明一个明确的serialVersionUID

或者你转向最健壮的形式:

public enum IsNonEmpty implements Predicate<String> {
    INSTANCE;

    @Override
    public boolean test(String s) {
        return !s.isEmpty();
    }
}

序列化这个常量不存储实际实现的任何属性,除了它的类名(当然,它是一个enum)和对常量名称的引用。反序列化后,将使用该名称的实际唯一实例。


请注意serializable lambda expressions may create security issues,因为它们打开了另一种获取对象的方法,该对象允许调用目标方法。但是,这适用于所有可序列化的类,因为您的问题和此答案中显示的所有变体都允许故意反序列化允许调用封装操作的对象。但是对于显式可序列化的类,作者通常更清楚这一事实。

【讨论】:

  • 我认为枚举没有任何危险;序列化时,它基本上只是类名和实例名。这怎么是安全问题?问题基本上是攻击者可以访问INSTANCE 而我不打算这样做吗?
  • 没错。使类可序列化就像添加一个额外的public 构造函数(或访问器),即使类本身不是public 也可以使用它。结合Predicate 之类的通用接口,它意味着提供对封装操作的访问。如果操作本身不重要,则没有问题。
猜你喜欢
  • 1970-01-01
  • 2010-10-17
  • 1970-01-01
  • 1970-01-01
  • 2012-12-08
  • 1970-01-01
  • 2014-05-13
相关资源
最近更新 更多