@Delegate 注释的属性,会把这个属性对象的公有非静态方法合到当前类
代理模式,把字段的方法代理给类,默认代理所有方法。注意:公共 非静态方法
public class Demo extends Parent { private final int finalVal = 10; @Delegate private String xxxName; private int age; }
编译后:把String类的公共 非静态方法全拿来了 个人觉得很鸡肋有木有
public class Demo extends Parent { private final int finalVal = 10; private String xxxName; private int age; public Demo() { } public int length() { return this.xxxName.length(); } public boolean isEmpty() { return this.xxxName.isEmpty(); } public char charAt(int index) { return this.xxxName.charAt(index); } public int codePointAt(int index) { return this.xxxName.codePointAt(index); } . . .
备注:它不能用于基本数据类型字段比如int,只能用在包装类型比如Integer
参数们:
1.types:指定代理的方法
2.excludes:和types相反
@NonFinal 设置不为Final,@FieldDefaults和@Value也有这功能
@SuperBuilder 本以为它是支持到了父类属性的builder构建,但其实,我们还是等等吧 目前还不好使
@UtilityClass 工具类 会把所有字段方法static掉,没啥用
@Wither 生成withXXX方法,返回类实例 没啥用,因为还有bug
@Builder和@NoArgsConstructor一起使用冲突问题
当我们这么使用时候:
编译报错:
Error:(17, 1) java: 无法将类 com.sayabc.groupclass.dtos.appoint.TeaPoolLogicalDelDto中的构造器 TeaPoolLogicalDelDto应用到给定类型; 需要: 没有参数 找到: java.lang.Long,java.lang.Long,java.lang.Long,java.lang.Integer 原因: 实际参数列表和形式参数列表长度不同
其实原因很简单,自己点进去看编译后的源码一看便知。
只使用@Builder会自动创建全参构造器。而添加上@NoArgsConstructor后就不会自动产生全参构造器
两种解决方式:
1.去掉@NoArgsConstructor
2.添加@AllArgsConstructor(建议使用这种,毕竟无参构造最好保证是有的)
but,枚举值建议这样来就行了,不要加@NoArgsConstructor
我认为这也是Lombok的一个bug,希望在后续版本中能够修复
@builder注解影响设置默认值的问题
例子如下,本来我是想给age字段直接赋一个默认值的:
没有使用lombok,我们这么写:
public static void main(String[] args) { Demo demo = new Demo(); System.out.println(demo); //Demo{id=null, age=10} } private static class Demo { private Integer id; private Integer age = 10; //放置默认值年龄 //省略手动书写的get、set、方法和toString方法 @Override public String toString() { return "Demo{" + "id=" + id + ", age=" + age + '}'; } }
我们发现,这样运行没有问题,默认值也生效了。但是,但是我们用了强大的lombok,我们怎么可能还愿意手写get/set呢?关键是,我们一般情况下还会用到它的@buider注解:
public static void main(String[] args) { Demo demo = new Demo(); System.out.println(demo); //Demo{id=null, age=10} //采用builder构建 这是我们使用最多的场景吧 Demo demo2 = Demo.builder().build(); System.out.println(demo2); //PeriodAddReq.Demo(id=null, age=null) } @Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor @ToString private static class Demo { private Integer id; private Integer age = 10; //放置默认值年龄 }
代码简洁了不少。但是我们却发现一个问题。new出来的对象默认值仍然没有问题,但是buider构建出来的demo2对象,默认值却没有设置进去。这是一个非常隐晦的问题,一不小心,就可能留下一个惊天大坑,所以需要注意
其实在执行编译的时候,idea开发工具已经警告我们了:
Warning:(51, 25) java: @Builder will ignore the initializing expression entirely. If you want the initializing expression to serve as default, add @Builder.Default. If it is not supposed to be settable during building, make the field final.
方案一:
从它的建议可以看出,把字段标为final就ok了(亲测好用)。但很显然,绝大多数我们并不希望他是final的字段。
因此我们采用第二个方案:
@Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor @ToString private static class Demo { private Integer id; @Builder.Default private Integer age = 10; //放置默认值年龄 }
lombok考虑到了这种现象,因此我们只需要在需要设置默认值的字段上面加上 @Builder.Default注解就ok了
public static void main(String[] args) { Demo demo = new Demo(); System.out.println(demo); //PeriodAddReq.Demo(id=null, age=null) //采用builder构建 这是我们使用最多的场景吧 Demo demo2 = Demo.builder().build(); System.out.println(demo2); //PeriodAddReq.Demo(id=null, age=10) }
但是我们坑爹的发现:builder默认值没问题了,但是new出来又有问题了。见鬼啊,
我认为这是lombok的一个大bug,希望后续版本中能够修复
但是我们不能因为有这么一个问题,咱们就不使用它了。本文主要提醒读者,在使用的时候留心这个问题即可。
备注:@Builder.Default会使得使用@NoArgsConstructor生成的无参构造没有默认值,自己显示写出来的也不会给你设置默认值的,需要注意。
2019年1.18日补充内容:Lombok 1.18.4版本
上面已经指出了Lombok设置默认值的bug,果不其然。官方在1.18.4这个版本修复了这个bug。各位要有版本意识:这个版本级以上版本是好用的,比这版本低的都不行。
用这个版本运行上面例子,默认值没有问题了。
Main.Demo(id=null, age=10) Main.Demo(id=null, age=10)
我们不用自动生成空构造,显示书写出来呢?如下:
@Getter @Setter @Builder @AllArgsConstructor @ToString private static class Demo { private Integer id; @Builder.Default private Integer age = 10; //放置默认值年龄Default //显示书写出空构造 public Demo() { } }
我们发现手动书写出来的空构造,默认值是不生效的。这点需要特别注意。
这个就不说是Lombok的bug了,因为既然你都使用Lombok了,为何还自己写空构造呢?不是作死吗?
Lombok背后的自定义注解原理
作为一个Java开发者来说光了解插件或者技术框架的用法只是做到了“知其然而不知其所以然”,如果真正掌握其背后的技术原理,看明白源码设计理念才能真正做到“知其然知其所以然”。好了,话不多说下面进入本章节的正题,看下Lombok背后注解的深入原理。
可能熟悉Java自定义注解的同学已经猜到,Lombok这款插件正是依靠可插件化的Java自定义注解处理API(JSR 269: Pluggable Annotation Processing API)来实现在Javac编译阶段利用“Annotation Processor”对自定义的注解进行预处理后生成真正在JVM上面执行的“Class文件”。有兴趣的同学反编译带有Lombok注解的类文件也就一目了然了。其大致执行原理图如下:
从上面的Lombok执行的流程图中可以看出,在Javac 解析成AST抽象语法树之后, Lombok 根据自己编写的注解处理器,动态地修改 AST,增加新的节点(即Lombok自定义注解所需要生成的代码),最终通过分析生成JVM可执行的字节码Class文件。使用Annotation Processing自定义注解是在编译阶段进行修改,而JDK的反射技术是在运行时动态修改,两者相比,反射虽然更加灵活一些但是带来的性能损耗更加大。
需要更加深入理解Lombok插件的细节,自己查阅其源代码是必比可少的。
AnnotationProcessor这个类是Lombok自定义注解处理的入口。该类有两个比较重要的方法一个是init方法,另外一个是process方法。在init方法中,先用来做参数的初始化,将AnnotationProcessor类中定义的内部类(JavacDescriptor、EcjDescriptor)先注册到ProcessorDescriptor类型定义的列表中。其中,内部静态类—JavacDescriptor在其加载的时候就将 lombok.javac.apt.LombokProcessor这个类进行对象实例化并注册。在 LombokProcessor处理器中,其中的process方法会根据优先级来分别运行相应的handler处理类。Lombok中的多个自定义注解都分别有对应的handler处理类.
在Lombok中对于其自定义注解进行实际的替换、修改和处理的正是这些handler类。对于其实现的细节可以具体参考其中的代码。
Java6以后,java编译器已经有了开源的版本了。Java6提供了JSR269的标准实现,提供插入式注解处理API(Pluggable Annotation Processing API)一套标准API来处理Annotations。只要代码实现了此API,就能在javac运行的时候得到调用。Lombok就是一个实现了JSR269的程序