A4.领域驱动设计之领域层序列化与Jackson融合实践.adoc
前言
这是一个基于Java 21 的 六边形架构与领域驱动设计的一个通用项目,并且结合现有的最新版本技术架构实现了 领域驱动设计模式和六边形架构模式组件定义. 并且结合微服务,介绍了领域层,领域事件,资源库,分布式锁,序列化,安全认证,日志等,并提供了实现功能. 并且我会以日常发布文章和更新代码的形式来完善它.
简介
在系列文章,我们介绍了领域驱动设计中的领域层,其中关于业务的ID我们抽象出了一个基础类,例如我们定义了一个客户ID,因为没有提供get方法,会导致不能显示这个值。或者使用属性直接序列化,又不是我们想要的结构。
package com.iokays.sample.customer.domain;
import com.iokays.common.domain.jpa.AbstractId;
import jakarta.persistence.Embeddable;
import org.apache.commons.lang3.Validate;
import java.util.UUID;
@Embeddable
public class CustomerId extends AbstractId {
private static final String UUID_REGEX = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$";
private CustomerId() {
super();
}
public CustomerId(String id) {
super(id);
}
public static CustomerId makeCustomerId() {
return new CustomerId(UUID.randomUUID().toString());
}
/**
* 验证是否是UUID字符串
*
* @param anId
*/
@Override
protected void validateId(String anId) {
Validate.notNull(anId);
Validate.isTrue(anId.matches(UUID_REGEX));
}
}
我们需要得到的序列化的结果希望是这样的:
{
"customerId": "f7236822-b6e4-47b4-94c4-61fd05ab7001"
}
而不是
{
"customerId":
{
"id": "f7236822-b6e4-47b4-94c4-61fd05ab7001"
}
}
本用例将介绍如何通过Jackson的序列化扩展,实现我们想要的结果。
Jackson
Jackson是一个用于处理JSON数据的Java库,有丰富的特性去自定义序列化格式,并且可以灵活[注解,全局]的使用。我们只需要了解下面的类,就可以简单的使用它。
objectMapper
Jackson的入口类,我们通过这个类可以完成序列化与反序列化。我提供了一些测试类,测试类中已经展示了如何使用。
package com.iokays.common.domain.serialization;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
/**
* ObjectMapper 测试类
*/
class ObjectMapperTest {
@Test
void testSample() throws JsonProcessingException {
final var objectMapper = new ObjectMapper();
final var person = new Person("Andy", 30);
//序列化
final var json = objectMapper.writeValueAsString(person);
Assertions.assertEquals("{\"name\":\"Andy\",\"age\":30}", json);
//反序列化
final var person2 = objectMapper.readValue(json, Person.class);
Assertions.assertEquals(person, person2);
}
/**
* ALWAYS:
* 默认值。所有属性都会被包含在 JSON 中,无论它们的值是什么。
* NON_NULL:
* 只有非 null 的属性会被包含在 JSON 中。如果某个属性的值为 null,则该属性不会出现在生成的 JSON 字符串中。
* NON_ABSENT:
* 只有非 null 且非 Optional.empty() 的属性会被包含在 JSON 中。适用于 Java 8 的 Optional 类型。
* NON_DEFAULT:
* 只有非默认值的属性会被包含在 JSON 中。对于基本类型,这意味着只有非零值或非默认值的属性会被包含;对于对象类型,这意味着只有非 null 的属性会被包含。
* NON_EMPTY:
* 只有非 null 且非“空”的属性会被包含在 JSON 中。对于集合、数组和 Map,“空”意味着长度为 0;对于字符串,“空”意味着长度为 0 或仅包含空白字符。
* CUSTOM:
* 允许自定义包含逻辑。可以通过实现 JsonInclude.Value 接口来自定义包含规则
*
* @throws JsonProcessingException
*/
@Test
void testJsonInclude() throws JsonProcessingException {
final var objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); //不含值null的字段
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); //不含空字符串的字段
final var person = new Person("", null);
//序列化
final var json = objectMapper.writeValueAsString(person);
Assertions.assertEquals("{}", json);
}
/**
* 测试对象
*/
record Person(String name, Integer age) {
}
}
自定义序列化&反序列化
Jackson提供了两种自定义序列化与反序列化的方式,一种是通过实现`JsonSerializer`和`JsonDeserializer`接口, 或者继承已经实现的子类。因为业务ID的(反)序列化,使用的是这个方法。我这边就一并介绍。
前面我们定义了一个CustomerId类,来标识客户ID, 接着我们来定义(反)序列化的方式。
package com.iokays.common.domain.serialization;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.iokays.common.domain.jpa.AbstractId;
import java.io.IOException;
/**
* 将默认的 abstractId.id() 序列化为Id字符串
* 当对象是 AbstractId, 将值序列化为: abstractId.id。而不是默认的嵌套结构。
*/
public class AbstractIdSerializer<T extends AbstractId> extends JsonSerializer<T> {
@Override
public void serialize(T value, JsonGenerator gen, SerializerProvider provider) throws IOException {
// 自定义序列化逻辑
if (null != value) {
gen.writeString(value.id());
}
}
}
因为继承AbstractId的子类结构都是一样的,所以可以一个通用的序列化工具。但反序列化需要得到一个具体的子类,使用基类的方式, 很难创建到子类实例。所以这里使用了一个模板方法模式,来处理反序列化。
package com.iokays.common.domain.serialization; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.iokays.common.domain.jpa.AbstractId; import java.io.IOException; /** * 反序列化,因为需要反序列化的子类,所以将该类设置为抽象类,让子类自己实现 * * @param <T> */ public abstract class AbstractIdDeserializer<T extends AbstractId> extends JsonDeserializer<T> { @Override public final T deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { return create(jsonParser.getText()); } protected abstract T create(String text); }
所以每个子类都需要继承AbstractIdDeserializer,来实现对象的创建过程。例如我们就提供了CustomerIdDeserializer.
package com.iokays.common.domain.serialization;
/**
* CustomerId 反序列化子类。
*/
public class CustomerIdDeserializer extends AbstractIdDeserializer<CustomerId> {
@Override
protected CustomerId create(String text) {
return new CustomerId(text);
}
}
接着我们定义一个类,含有多个属性,并且都是CustomerId类型, 来测试基于属性注解和全局配置的使用方法。
package com.iokays.common.domain.serialization;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
/**
* 这个对象有多个 CustomerId 属性,
*/
public class ManyCustomerId {
/**
* 使用注解指定了 Jackson 序列化与反序列化 CustomerId
*/
@JsonSerialize(using = AbstractIdSerializer.class)
@JsonDeserialize(using = CustomerIdDeserializer.class)
protected CustomerId customerId1;
/**
* 使用注解 仅仅指定了 Jackson 反序列化 CustomerId
*/
@JsonDeserialize(using = CustomerIdDeserializer.class)
protected CustomerId customerId2;
public CustomerId getCustomerId1() {
return customerId1;
}
public CustomerId getCustomerId2() {
return customerId2;
}
}
测试方法如下:
package com.iokays.common.domain.serialization;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.iokays.common.domain.jpa.AbstractId;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.UUID;
public class CustomerTest {
private static final Logger log = LoggerFactory.getLogger(CustomerTest.class);
/**
* 使用默认注解形式,对指定的属性字段 进行测试
*
* @throws JsonProcessingException
*/
@Test
public void testAnnotation() throws JsonProcessingException {
final var objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
final var test1 = new ManyCustomerId();
test1.customerId1 = new CustomerId(UUID.randomUUID().toString());
test1.customerId2 = new CustomerId(UUID.randomUUID().toString());
final var json = objectMapper.writeValueAsString(test1);
log.info("test1_json: {}", json);
Assertions.assertTrue(json.contains("\"customerId2\":{}")); //customerId2为空
final var test2 = objectMapper.readValue(json, CustomerTest.class);
log.info("test2_json: {}", objectMapper.writeValueAsString(test2));
}
/**
* 序列化、反序列化都使用全局模式, 进行测试
*
* @throws JsonProcessingException
*/
@Test
public void testSimpleModule() throws JsonProcessingException {
final var objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
final SimpleModule simpleModule = new SimpleModule();
//序列化
simpleModule.addSerializer(AbstractId.class, new AbstractIdSerializer<>());
//反序列化,需要指定为具体的实现类,不建议使用反射机制
simpleModule.addDeserializer(CustomerId.class, new CustomerIdDeserializer());
objectMapper.registerModule(simpleModule);
final var test1 = new ManyCustomerId();
log.info("test1_empty_json: {}", objectMapper.writeValueAsString(test1));
test1.customerId1 = new CustomerId(UUID.randomUUID().toString());
test1.customerId2 = new CustomerId(UUID.randomUUID().toString());
final var json = objectMapper.writeValueAsString(test1);
log.info("test1_json: {}", json);
Assertions.assertFalse(json.contains("\"customerId2\":{}")); //customerId2不为空
final var test2 = objectMapper.readValue(json, CustomerTest.class);
log.info("test2_json: {}", objectMapper.writeValueAsString(test2));
}
}