本篇就是以我们最常用的 lombok
作为主线来引出 javac
注解处理器,Lombok 插件注解功能很多,出了有自动 set、get 方法外,还有链式调用、建造者模式等等,但是我们就讨论最简单的 set、get 方法的生成。
一、用Lombok引出问题
1.1、引入
1、idea 中打开 settings (快捷键:ctrl+alt+s) ,搜索 plugin ,在 plugins 里面搜索 lombok ,安装
2、在项目中引入 lombok 的依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
1.2、优缺
Lombok 是一个 Java 库,能自动插入编辑器并构建工具,简化 Java 开发。通过添加注解的方式,不需要为类编写 getter或 eques 方法,同时可以自动化日志变量。官网链接
优点:简化 Java 开发,减少了许多重复代码。
缺点:
- 降低了源码的可读性和完整性;
- 有可能会破坏封装性,因为有些属性并不需要向外暴露;
- 降低了可调试性;
- Lombok 会帮我们自动生成很多代码,但这些代码是在编译期生成的,因此在开发和调试阶段这些代码可能是“丢失的”,这就给调试代码带来了很大的不便。
如果不考虑的那么严谨,我觉得还是要用的,因为我懒。
1.3、使用
写一个类来分析一下:
我们自己手写的一个JavaBean
/**
* @description:
* @author: Yihui Wang
* @date: 2022年07月06日 20:23
*/
public class Student {
private String name;
private String age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
用了 lombok 注解的 JavaBean
/**
* @description:
* @author: Yihui Wang
* @date: 2022年07月06日 20:23
*/
@Data
public class StudentLombok {
private String name;
private String age;
}
我们编译一下,Idea 中点击顶部菜单 Build ,下拉选择 Recompile
看看他们生成的 class文件是什么样的。
可以明显看出,使用了 @Setter、@Getter 注解后,和我们手动编写的 Java 代码,编译完的结果是一样的。
它直接帮我们生成了这些方法,这些步骤究竟是谁做的勒?我们是否也可以自己编写这样的注解呢?
二、Lombok 原理分析
其实这里面用到了 AOP 编程的编译时织入技术,就是在编译的时候修改最终 class 文件。
大部分的程序代码从开始编译到最终转化成物理机的目标代码或虚拟机能执行的指令集之前,都会按照如下图所示的各个步骤进行:
Javac 的编译过程
归纳起来主要是由以下三个过程组成:
- 分析和输入到符号表
- 注解处理
- 语义分析和生成 class 文件
而Lombok 正是利用注解处理这一步来进行实现的。Lombok 使用的是 JDK 6 实现的 JSR 269: Pluggable Annotation Processing API (编译期的注解处理器) ,它允许在编译期处理注解,读取、修改、添加抽象语法树中的内容。
其实说到这里,我们还只是知道它是在这一步处理的,但如何处理的,我们还是一无所知。
稍后我们会手动实现 Lombok 中的 @Getter、@Setter 注解,这里先事先说明可能会牵扯到的知识。
- 主要使用到的都是 jdk 源码的 tools.ja 包
- 使用的 api 主要是
com.sun.tools.javac
包下的 - 抽象语法 JCTree 使用
不懂也没关系,我也不是很懂,哈哈,我也只是因为好奇,才来探寻的
其中最主要的就是牵扯到的AbstractProcessor
抽象注解处理类,还有就是 JCTree 相关的api,这些的话,我也用的不多,不敢胡乱发言。
要实现注解处理器首先要做的就是继承抽象类 javax.annotation.processing.AbstractProcessor,然后重写它的 process() 方法,process() 方法是 javac 编译器在执行注解处理器代码时要执行的过程。
/**
一个