You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot解析JSON生成可变列表,如何保障DTO不可变性?

不可变DTO在Spring Boot中的反序列化问题解答

问题背景

我尝试创建一个不可变DTO,为Pizza类添加了Lombok的@Builder和@Getter注解,同时使用@Singular注解防止ingredients字段被初始化为可变List。但通过Spring Boot的API端点接收JSON请求体时,反序列化后的ingredients是可变列表,可成功添加元素;而通过Builder构建的Pizza实例调用add方法时,会抛出java.lang.UnsupportedOperationException。


1. Spring Boot是如何对请求体/响应体进行序列化与反序列化的?

Spring Boot默认使用Jackson作为JSON序列化/反序列化工具,核心依赖ObjectMapper类,处理流程如下:

  • 反序列化(请求体→Java对象):
    1. 优先通过无参构造器创建目标类实例;
    2. 存在对应字段的setter方法时,调用setter赋值;无setter则直接通过反射给字段赋值;
    3. 对于List这类集合类型,Jackson默认实例化可变集合(如ArrayList)存储JSON数组元素,不会自动适配DTO定义的不可变集合类型。
  • 序列化(Java对象→响应体):
    通过反射读取对象getter方法的返回值,转换为JSON格式输出;不可变集合的元素会被正常序列化,不会改变其不可变特性。

2. 如何防止Spring Boot将ingredients字段初始化为可变列表?

提供三种可行方案,按需选择:

方案一:让Jackson复用Lombok Builder(推荐)

从Lombok 1.18.14版本开始,新增@Jacksonized注解,可让Jackson直接使用Lombok生成的Builder创建DTO实例,反序列化后的ingredients会继承@Singular注解生成的不可变列表特性:

@Getter
@Builder
@Singular
@Jacksonized
public class Pizza {
    private final String name;
    private final List<String> ingredients;
}

此方案无需额外配置,能保证Builder构建和反序列化的实例行为一致。

方案二:自定义Jackson集合反序列化器

配置ObjectMapper,让Jackson将JSON数组反序列化为不可变列表,支持全局或字段级配置:

  • 全局配置示例:
@Configuration
public class JacksonConfig {
    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        SimpleModule module = new SimpleModule();
        module.addDeserializer(List.class, new JsonDeserializer<List<?>>() {
            @Override
            public List<?> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
                List<?> mutableList = p.readValueAs(List.class);
                return List.copyOf(mutableList); // Java 9+生成不可变列表
                // 若使用Guava可替换为:return ImmutableList.copyOf(mutableList);
            }
        });
        mapper.registerModule(module);
        return mapper;
    }
}
  • 字段级配置:在ingredients字段上添加@JsonDeserialize指定自定义反序列化器,仅对该字段生效。

方案三:在构造器中主动转为不可变列表

手动实现DTO构造器,将传入的集合转为不可变类型,同时用@JsonCreator告知Jackson使用该构造器反序列化:

@Getter
@Builder
@Singular
public class Pizza {
    private final String name;
    private final List<String> ingredients;

    @JsonCreator
    public Pizza(@JsonProperty("name") String name, @JsonProperty("ingredients") List<String> ingredients) {
        this.name = name;
        this.ingredients = List.copyOf(ingredients); // 转为不可变列表
    }
}

这种方式不依赖特定Lombok版本,兼容性好,但需要手动维护构造器。


内容的提问来源于stack exchange,提问作者Moritz Kampfinger

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 18:55:23