基于Lombok与Jackson创建可序列化不可变类的最优实现方案问询
嘿,针对你想要创建不可变类(private final字段、无Setter),同时支持Jackson序列化/反序列化、可读性强、样板代码少的需求,我整理了几个靠谱的方案,顺便帮你排查下之前踩的坑~
一、最省心的Lombok+Jackson组合方案
这应该是最符合你需求的方式,用Lombok减少重复代码,配合Jackson注解搞定序列化/反序列化:
1. 基础版(兼容所有Jackson版本)
你之前的问题核心是漏了Getter注解,以及@JsonCreator的配置不到位。直接用下面的注解组合就能解决:
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import lombok.ToString; import lombok.EqualsAndHashCode; @Getter // 必须加!Jackson序列化需要Getter读取字段值 @ToString @EqualsAndHashCode public class ImmutableUser { private final String username; private final int age; private final String email; // @JsonCreator告诉Jackson用这个构造器反序列化 @JsonCreator public ImmutableUser( // @JsonProperty映射JSON字段和构造器参数 @JsonProperty("username") String username, @JsonProperty("age") int age, @JsonProperty("email") String email) { this.username = username; this.age = age; this.email = email; } }
2. 进阶简化版(用Lombok自动生成构造器)
不想手动写构造器?可以用@AllArgsConstructor配合@JsonCreator,记得一定要加@Getter:
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import lombok.AllArgsConstructor; import lombok.ToString; import lombok.EqualsAndHashCode; @Getter @ToString @EqualsAndHashCode // 让Lombok生成全参构造器,并给构造器加上@JsonCreator @AllArgsConstructor(onConstructor = @__(@JsonCreator(mode = JsonCreator.Mode.PROPERTIES))) public class ImmutableUser { // @JsonProperty标注字段,同时映射序列化/反序列化的字段名 @JsonProperty("username") private final String username; @JsonProperty("age") private final int age; @JsonProperty("email") private final String email; }
这里指定
JsonCreator.Mode.PROPERTIES是为了让Jackson按属性名匹配参数,避免依赖额外模块,兼容性更好。
二、帮你分析之前的问题
1. @Data(onConstructor = @__(@JsonCreator))编译失败的原因
@Data包含@RequiredArgsConstructor,只会给final字段生成构造器,但旧版本Lombok不支持直接在@Data上通过onConstructor添加@JsonCreator。另外,即使编译通过,生成的构造器参数没有@JsonProperty,Jackson也没法正确映射JSON字段,一样会出问题。
2. @AllArgsConstructor(onConstructor = @__(@JsonCreator))抛出序列化异常的原因
@AllArgsConstructor只生成全参构造器,不会生成Getter方法!Jackson序列化时默认需要通过Getter读取字段值,没有Getter自然会抛出InvalidDefinitionException: No serializer found...,加上@Getter就解决了。
三、更进一步:用Jackson参数名称模块省掉@JsonProperty
如果你的项目用Java 8+,可以引入Jackson的参数名称模块,彻底省去@JsonProperty的编写:
- 先加Maven依赖(版本和你的Jackson保持一致):
<dependency> <groupId>com.fasterxml.jackson.module</groupId> <artifactId>jackson-module-parameter-names</artifactId> <version>2.15.2</version> <!-- 换成你的Jackson版本 --> </dependency>
- 给ObjectMapper注册这个模块:
ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new ParameterNamesModule());
- 简化到极致的不可变类:
import com.fasterxml.jackson.annotation.JsonCreator; import lombok.Getter; import lombok.AllArgsConstructor; import lombok.ToString; import lombok.EqualsAndHashCode; @Getter @ToString @EqualsAndHashCode @AllArgsConstructor(onConstructor = @__(@JsonCreator)) public class ImmutableUser { private final String username; private final int age; private final String email; }
这个模块会利用Java 8的参数名称反射功能,让Jackson自动匹配JSON字段和构造器参数名,代码超干净!
四、纯Jackson方案(不用Lombok)
如果不想依赖Lombok,纯Jackson注解也能实现,只是样板代码会多一点:
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonGetter; public class ImmutableUser { private final String username; private final int age; private final String email; @JsonCreator public ImmutableUser( @JsonProperty("username") String username, @JsonProperty("age") int age, @JsonProperty("email") String email) { this.username = username; this.age = age; this.email = email; } // Jackson序列化需要这些Getter @JsonGetter("username") public String getUsername() { return username; } @JsonGetter("age") public int getAge() { return age; } @JsonGetter("email") public String getEmail() { return email; } // 可选:重写toString、equals、hashCode提升可读性 @Override public String toString() { return "ImmutableUser{" + "username='" + username + '\'' + ", age=" + age + ", email='" + email + '\'' + '}'; } }
内容的提问来源于stack exchange,提问作者Christian Neverdal

