遇到的问题
在写代码时,有时因为需要定义一些重复的参数,为了复用之前传参的DTO,会对原有的类进行继承,从而达到避免重复代码的效果。
但是,当父类中使用了lombok的@Builder
注解,子类也需要@builder
注解时,就会出现异常
排查和解决
由于实际的代码存在敏感信息,这里分别定义两个类Parent
和Child
来进行场景的模拟
@Data
@Builder
class Parent {
private String parentProperty1;
private String parentProperty2;
}
@EqualsAndHashCode(callSuper = true)
@Data
@Builder
class Child extends Parent {
private String childProperty1;
}
这时在尝试使用子类的builder方法时,发现没有办法链式调用,使用时只能初始化子类中的变量,编译时会直接失败,抛出异常
Error:(160, 1) java: 无法将类 org.example.Parent中的构造器 Parent应用到给定类型;
需要: java.lang.String,java.lang.String
找到: 没有参数
原因: 实际参数列表和形式参数列表长度不同
这是在编译子类的@Builder
注解时出现的异常,原因直观的看起来是找不到构造器,在Parent
类上加上@NoArgsConstructor
和@AllArgsConstructor
这两个注解就能解决这个问题,但是同时会出现新的编译问题,是什么问题先按下不表。
想要简单的解决加上@Builder
之后就会报错的问题,那么直接把父类的@Builder
这个注解拿掉就行了,不过这时无法设置父类的属性,如果还想在子类中使用构建器模式来初始化父类的属性,还有另一种方法,在子类中实现一个能够初始化父类属性的构造器,并在这个构造方法上添加@Builder
注解。
这时的代码:
@Data
@NoArgsConstructor
@AllArgsConstructor
class Parent {
private String parentProperty1;
private String parentProperty2;
}
@Data
class Child extends Parent {
private String childProperty1;
@Builder
public Child(String parentProperty1, String parentProperty2, String childProperty1){
super(parentProperty1, parentProperty2);
this.childProperty1 = childProperty1;
}
}
不过使用这种方法只能解决子类使用@Builder
的问题,但是在更多的时候,父类也是需要@Builder
这个注解的,那么在这种情况下应该怎么解决呢?
而且这时还会有另一个新的问题出现,使用了@Data注解和@Builder
注解的子类无法使用无参构造器来创建对象,这时需要在子类上显式的加上@NoArgsConstructor
这个注解才能解决。
如果要更细致的分析,就得从从@Builder
的原理说起,了解@Builder
到底生成了哪些代码?
这一步可以自己编译代码看看,当然如果自己写过builder建造者模式的实现,应该能想到他是实现了一个名称以Builder为后缀的静态内部类,在调用build()
方法的时候调用外部类的全参构造方法来生成外部类的实例。
回到之前的问题,当子类和父类同时存在@Builder
注解时,在解决了构造器异常之后,如果编译代码,会出现异常:
Error:(164, 5) java: org.example.Child中的builder()无法覆盖org.example.Parent中的builder()
返回类型org.example.Child.ChildBuilder与org.example.Parent.ParentBuilder不兼容
这里的问题就简单一些了,父类的builder()
方法返回的是ParentBuilder
这个静态内部类类型的对象,而子类生成的builder()
方法返回的是ChildBuilder
这个类型的对象。两者的名称重复了,而由于返回类型不兼容而无法按覆盖。根据@Builder
注解的源码可以发现名称是可以自定义的,于是可以通过给子类builder方法自定义名称的方式来解决这个问题。
最终的代码:
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
class Parent {
private String parentProperty1;
private String parentProperty2;
}
@Data
class Child extends Parent {
private String childProperty1;
@Builder(builderMethodName = "childBuilder")
public Child(String parentProperty1, String parentProperty2, String childProperty1){
super(parentProperty1, parentProperty2);
this.childProperty1 = childProperty1;
}
}
结尾
值得一提的是,1.8.2
之后版本的lombok提供了一个新的注解@SuperBuilder
来解决这个问题,不过我没有用过,而且从网上搜索出来的结果来看,还是存在一些问题的,建议谨慎升级。