0%

Lombok 实战及常见问题

在日常工作中,使用 Lombok 主要还是用于 Bean、POJO、DTO 一类,还是挺好用的。

建议使用 Lombok 1.18.xx 以上版本,支持 @SuperBuilder 注解,这也是 SpringBoot 2.1.X 开始采用的新版本。

官网:https://projectlombok.org/

注解

@NoArgsConstructor

提供一个无参数构造器。

如果我们需要使用到单例模式,(很少,一般使用 Lombok 都是用作POJO、DTO),可以设置 (access = AccessLevel.PRIVATE)。

如果包含 final 成员,会导致编译错误。可以强制使用 (force = true),从而对 final 进行默认初始化(0/false/null)。

@NonNull 注解的字段,不做任何检查。

@RequiredArgsConstructor

提供无参数或有参数的构造器。

当包含 final 成员,和 @NonNull 注解的字段,会生成带参数的构造器。

所以这个注解相对使用起来比 @NoArgsConstructor 更安全。

@AllArgsConstructor

提供一个全参数的构造器。

@Data

组合了这些注解 @ToString@EqualsAndHashCode@Getter@Setter@RequiredArgsConstructor

@Builder

提供了建造者模式。会默认生成一个私有的 @AllArgsConstructor

注意: 当与 @Data 一起使用的时候,会丢失无参构造器。作者认为如果使用 @Builder,原则上你不太需要使用 new 来进行创建对象。然而目前有很多框架是使用反射无参构造器来创建对象的,所以会产生问题。

解决方法: 如果有问题,可以手动增加 @NoArgsConstructor, @AllArgsConstructor 注解

@Builder.Default

指定建造者模式的默认值,否则未初始化的值默认是 null

常见问题

Lombok 的继承,会有一些问题。

有继承关系时的 @Data

默认情况下 @Data 仅仅比较子类的值,并且 toString 方法也没有父类信息,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {   
protected Integer id;   

@Builder.Default   
protected String name = "默认";
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserChild extends User {
private Integer age;
}

看下示例代码,调用 equals 结果是 true:

1
2
3
4
5
6
7
8
9
10
11
UserChild userChild11 = new UserChild();
userChild11.setId(1);
userChild11.setName("孩子1");
userChild11.setAge(12);
System.out.println(userChild11);

UserChild userChild12 = new UserChild();
userChild12.setId(2);
userChild12.setName("孩子2");
userChild12.setAge(12);
System.out.println(userChild11.equals(userChild12));

解决方案:

在子类上增加 @ToString(callSuper = true)@EqualsAndHashCode(callSuper = true)

@Builder (1.18 版本以前)

在子类增加 @Builder,建造者模式只能作用于子类成员,无法应用到父类。

在 1.18 版本以前,只能通过很丑陋的方式来进行子类的 .builder(),这完全丢失了 lombok 的便捷性:

  1. 取消父子类的类级别 @Builder

  2. 自定义子类的构造器,初始化父类成员和子类成员,并且使用 @Builder 注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserBuilder {
protected Integer id;
protected String name;
}

@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class UserChildBuilder extends UserBuilder {
private Integer age;
@Builder
public UserChildBuilder(Integer id, String name, Integer age) {
super(id, name);
this.age = age;
}
}

@SuperBuilder (1.18 版本之后)

父类子类都使用 @SuperBuilder 就可以简单的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Data
@NoArgsConstructor
@AllArgsConstructor
@SuperBuilder
public class UserBuilder {
protected Integer id;
protected String name;
}

@Data
@NoArgsConstructor
@AllArgsConstructor
@SuperBuilder
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class UserChildBuilder extends UserBuilder {
private Integer age;
}

1.18.X 版本

Lombok 1.18.X 引入了一个不兼容的变更(为了兼容 Java 9 的 Module):

当我们同时使用 @Data 与 @Builder 时, Jackson 反序列化时会报错 ,比如如下代码在 1.16.22 工作正常,但是在 1.18.X 时报 (no Creators, like default construct, exist):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Data
@Builder
public class UserJson {
private Integer id;

private String name;
}

try {
UserJson userJson = UserJson.builder().id(1).name("").build();
String json = mapper.writeValueAsString(userJson);
UserJson user = mapper.readValue(json, UserJson.class);
System.out.println(user);
} catch (IOException e) {
e.printStackTrace();
}

解决方案:

给类额外添加 @NoArgsConstructor, @AllArgsConstructor 注解