使用不可变 class 和 java 的 spark 中的反序列化错误(+ lombok)
Deserialization Error in spark using immutable class with java (+ lombok)
我有这个简单的模型class
@Value // lombok - create standard all arg constructor and getters
public class ModelA implements Serializable {
private String word;
private double value;
}
这个简单的测试失败了:
public class SparkSerializationTest {
private SparkSession spark = SparkSession.builder()
.master("local")
.appName("Test")
.getOrCreate();
@Test
public void testSerializationModelA() {
ModelA modelA1 = new ModelA("A1", 12.34);
ModelA modelA2 = new ModelA("A2", 56.78);
Dataset<ModelA> dataset = spark.createDataset(
Arrays.asList(modelA1, modelA2),
Encoders.bean(ModelA.class));
List<ModelA> yo = dataset.collectAsList(); // <== *** failure here ***
assertThat(yo).isEqualTo(Arrays.asList(modelA1, modelA2));
}
}
有例外:
java.util.concurrent.ExecutionException: org.codehaus.commons.compiler.CompileException: File 'generated.java', Line 24, Column 67: failed to compile: org.codehaus.commons.compiler.CompileException: File 'generated.java', Line 24, Column 67: No applicable constructor/method found for zero actual parameters; candidates are: "com.xxx.yyy.ModelA(java.lang.String, double)"
它似乎需要一个零参数构造函数。但是我希望我的模型是不可变的,因此具有完整的 arg 构造函数并且没有 setter。我应该怎么做?
简单的出路
只要给它一个没有任何设置器的无参数构造函数。它将是可变的,但与您提供所有 setter 相比,它的混乱程度稍微低一些。当您使用 Kryo 作为反序列化器时 (我想您已经这样做了),您可以将此构造函数保密。
全参数构造函数 仍然可以用空值和无意义的值调用。如果您想对对象的有效性强加一些契约,请明确使用验证。如果您追求的是不变性,那么使用无参数构造函数,您的成员将不再是最终的。
对象创建的动态特性
反序列化通过调用最简单的(无参数)构造函数来工作,因为对于所使用的实用程序的作者来说,这是更容易实现的过程,而不是将一次调用组装到具有所有必要属性的全参数构造函数,其顺序可以是任意的并且不保证对对象属性的分配。
相反,他们创建了 vanila 对象并通过设置器或反射填充它,确保名称在序列化版本和对象版本之间匹配。 All-args 构造函数会不太可靠地执行此操作,并且会更难实现。
在 Kryo 中创建自定义对象
如果您需要保持不变性,则必须使用自定义对象创建。请看一下 Kryo's example for custom object creation:
Registration registration = kryo.register(SomeClass.class);
registration.setInstantiator(new ObjectInstantiator<SomeClass>() {
public SomeClass newInstance () {
return new SomeClass("some constructor arguments", 1234);
}
});
我有这个简单的模型class
@Value // lombok - create standard all arg constructor and getters
public class ModelA implements Serializable {
private String word;
private double value;
}
这个简单的测试失败了:
public class SparkSerializationTest {
private SparkSession spark = SparkSession.builder()
.master("local")
.appName("Test")
.getOrCreate();
@Test
public void testSerializationModelA() {
ModelA modelA1 = new ModelA("A1", 12.34);
ModelA modelA2 = new ModelA("A2", 56.78);
Dataset<ModelA> dataset = spark.createDataset(
Arrays.asList(modelA1, modelA2),
Encoders.bean(ModelA.class));
List<ModelA> yo = dataset.collectAsList(); // <== *** failure here ***
assertThat(yo).isEqualTo(Arrays.asList(modelA1, modelA2));
}
}
有例外:
java.util.concurrent.ExecutionException: org.codehaus.commons.compiler.CompileException: File 'generated.java', Line 24, Column 67: failed to compile: org.codehaus.commons.compiler.CompileException: File 'generated.java', Line 24, Column 67: No applicable constructor/method found for zero actual parameters; candidates are: "com.xxx.yyy.ModelA(java.lang.String, double)"
它似乎需要一个零参数构造函数。但是我希望我的模型是不可变的,因此具有完整的 arg 构造函数并且没有 setter。我应该怎么做?
简单的出路
只要给它一个没有任何设置器的无参数构造函数。它将是可变的,但与您提供所有 setter 相比,它的混乱程度稍微低一些。当您使用 Kryo 作为反序列化器时 (我想您已经这样做了),您可以将此构造函数保密。
全参数构造函数 仍然可以用空值和无意义的值调用。如果您想对对象的有效性强加一些契约,请明确使用验证。如果您追求的是不变性,那么使用无参数构造函数,您的成员将不再是最终的。
对象创建的动态特性
反序列化通过调用最简单的(无参数)构造函数来工作,因为对于所使用的实用程序的作者来说,这是更容易实现的过程,而不是将一次调用组装到具有所有必要属性的全参数构造函数,其顺序可以是任意的并且不保证对对象属性的分配。相反,他们创建了 vanila 对象并通过设置器或反射填充它,确保名称在序列化版本和对象版本之间匹配。 All-args 构造函数会不太可靠地执行此操作,并且会更难实现。
在 Kryo 中创建自定义对象
如果您需要保持不变性,则必须使用自定义对象创建。请看一下 Kryo's example for custom object creation:
Registration registration = kryo.register(SomeClass.class);
registration.setInstantiator(new ObjectInstantiator<SomeClass>() {
public SomeClass newInstance () {
return new SomeClass("some constructor arguments", 1234);
}
});