Jackson 反序列化不可变对象
1. 概述
在本快速教程中,我们将展示使用Jackson 处理库反序列化不可变 Java 对象的两种不同方法。
2. 为什么我们使用不可变对象?
不可变对象 是从创建的那一刻起就保持其状态不变的对象。这意味着无论最终用户调用对象的哪些方法,对象的行为方式都是相同的。 当我们设计一个必须在多线程环境中工作的系统时,不可变对象会派上用场,因为不可变性通常可以保证线程安全。 另一方面,当我们需要处理来自外部源的输入时,不可变对象很有用。例如,它可以是用户输入或存储中的一些数据。在这种情况下,保存接收到的数据并保护其免受意外或意外更改可能至关重要。 让我们看看如何反序列化不可变对象。
3. 公共构造函数
让我们考虑一下Employee类结构。它有两个必填字段:id和name,因此我们定义了一个公共的全参数构造函数,该构造函数具有一组与对象字段集匹配的参数:
public class Employee {
private final long id;
private final String name;
public Employee(long id, String name) {
this.id = id;
this.name = name;
}
// getters
}
这样,我们将在创建时初始化所有对象的字段。**字段声明中的最终修饰符不会让我们将来更改它们的值。**为了使这个对象可反序列化,我们只需要向这个构造函数添加几个注解 :
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public Employee(@JsonProperty("id") long id, @JsonProperty("name") String name) {
this.id = id;
this.name = name;
}
让我们仔细看看我们刚刚添加的注解。 首先,@JsonCreator告诉 Jackson 反序列化器使用指定的构造函数进行反序列化。 有两种模式可以用作此注解的参数 - PROPERTIES和DELEGATING。
当我们声明一个全参数构造函数时,PROPERTIES是最合适的,而DELEGATING可能对单参数构造函数有用。 之后,我们需要使用*@JsonProperty注解每个构造函数参数,将相应属性的名称作为注解值。在这一步我们应该非常小心,因为所有属性名称都必须与我们在序列化期间使用的名称匹配。 让我们看一个简单的单元测试,它涵盖了Employee*对象的反序列化:
String json = "{\"name\":\"Frank\",\"id\":5000}";
Employee employee = new ObjectMapper().readValue(json, Employee.class);
assertEquals("Frank", employee.getName());
assertEquals(5000, employee.getId());
4. 私有构造器和生成器
有时会发生一个对象有一组可选字段。让我们考虑另一个类结构Person,它有一个可选的age字段:
public class Person {
private final String name;
private final Integer age;
// getters
}
当我们有大量此类字段时,创建公共构造函数可能会变得很麻烦。换句话说,我们需要为构造函数声明很多参数,并使用*@JsonProperty*注解对每个参数进行注解。结果,许多重复的声明会使我们的代码变得臃肿且难以阅读。
当经典的Builder 模式 来拯救时就是这种情况。让我们看看我们如何在反序列化中使用它的力量。首先,让我们声明一个私有的全参数构造函数和一个Builder类:
private Person(String name, Integer age) {
this.name = name;
this.age = age;
}
static class Builder {
String name;
Integer age;
Builder withName(String name) {
this.name = name;
return this;
}
Builder withAge(Integer age) {
this.age = age;
return this;
}
public Person build() {
return new Person(name, age);
}
}
为了让 Jackson 反序列化器使用这个Builder,我们只需要在我们的代码中添加两个注解。首先,我们需要用*@JsonDeserialize注解来标记我们的类,传递一个builder参数和一个builder类的完全限定域名。 之后,我们需要将构建器类本身注解为@JsonPOJOBuilder*:
@JsonDeserialize(builder = Person.Builder.class)
public class Person {
//...
@JsonPOJOBuilder
static class Builder {
//...
}
}
请注意,我们可以自定义构建期间使用的方法的名称。 参数buildMethodName默认为“ build”,代表我们在构建器准备好生成新对象时调用的方法的名称。 另一个参数withPrefix代表我们添加到负责设置属性的构建器方法的前缀。此参数的默认值为*“with”。这就是我们没有在示例中指定任何这些参数的原因。 让我们看一个简单的单元测试,它涵盖了Person*对象的反序列化:
String json = "{\"name\":\"Frank\",\"age\":50}";
Person person = new ObjectMapper().readValue(json, Person.class);
assertEquals("Frank", person.getName());
assertEquals(50, person.getAge().intValue());