背景
最近遇到了一个使用@Builder注解导致线上报NPE的问题。原因在给一个用@Builder注解的Java Bean的一个属性赋默认值之后,使用build方式构建出来的对象该属性的值是null。
本地模拟
在本地创建一个User类,给vip赋上默认值true。并写了一个测试用例,判断vip是否是null。
代码语言:javascript复制@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {
private Long id;
private Boolean vip = true;
}
@Test
public void buildTest(){
User user = User.builder().id(1L).build();
assertNull(user.getVip());
}
测试用例可以跑过,说明User的vip并没有赋默认值。这是为什么呢?打开User.class反编译之后的文件可以看到,@Builder注解的作用其实就是帮程序员免去写构造者模式的麻烦,内部生成一个UserBuilder静态内部类。当调用build
函数的时候,创建对象。由于生成的代码中,没有给vip字段赋默认值自然拿到的是null。
.....省略部分代码......
public static class UserBuilder {
private Long id;
private Boolean vip;
UserBuilder() {
}
public UserBuilder id(final Long id) {
this.id = id;
return this;
}
public UserBuilder vip(final Boolean vip) {
this.vip = vip;
return this;
}
public User build() {
return new User(this.id, this.vip);
}
public String toString() {
return "User.UserBuilder(id=" this.id ", vip=" this.vip ")";
}
}
解决办法
解决办法也很简单,不需要我们重写User的建造者模式,只要在赋默认值的属性上加上@Builder.Default
注解就可以了。
@Data
@Builder
public class User {
private Long id;
@Builder.Default
private Boolean vip = true;
}
再反编译User.class文件文件,发现生成了一个静态方法defaultvip(),该方法返回的就是vip的默认值。调用build方法时会判断vip是否已经设置,没有设置则调用defaultvip()方法赋值。
代码语言:javascript复制public class User {
private Long id;
private Boolean vip;
private static Boolean $default$vip() {
return true;
}
public static class UserBuilder {
..
public User build() {
Boolean vip$value = this.vip$value;
if (!this.vip$set) {
vip$value = User.$default$vip();
}
return new User(this.id, vip$value);
}
}
}
总结
Lombok虽然可以帮程序员自己去写很多代码,但是lombok自动生成的代码毕竟不可控,需要我们在实际开发中去探究他实际生成的代码是否满足我们的要求,这样才能避免下次踩坑。