1. 概叙
Lombok是什么?
- Project Lombok 是一个 JAVA 库,它可以自动插入编辑器和构建工具,为您的 JAVA 锦上添花。
- 再也不要写另一个 getter/setter 或 equals 等方法,只要有一个注注解,你的类就有一个功能齐全的生成器,自动记录变量,等等。
官方地址:https://projectlombok.org/
Lombok是一种Java™实用工具,可用来帮助开发人员消除Java的冗长代码,尤其是对于简单的Java对象(POJO)。它通过注释实现这一目的。
首先Lombok
是一款Java IDE的应用工具插件,一个可以通过简单的注解形式来帮助我们简化消除一些必须有但显得很臃肿的Java代码的工具,比如属性的构造器、getter、setter、equals、hashcode、toString方法。结合IDE,通过使用对应的注解,可以在编译源码的时候生成对应的方法。
通过在开发环境中实现Lombok,开发人员可以节省构建诸如hashCode()和equals()这样的方法以及以往用来分类各种accessor和mutator的大量时间。
虽然上述的那些常用方法IDE都能生成,但是lombok更加简洁与方便,能够达到的效果就是在源码中不需要写一些通用的方法,但是在编译生成的字节码文件中会帮我们生成这些方法,这就是lombok的神奇作用。
lombok插件安装
使用的IDE是Intellij idea,编译器需要在
preference->plugins->Browse repositories
搜索lombok,然后安装plugins,需要稍等片刻
添加jar包
在项目中添加lombok的jar包,笔者用的是maven,所以在pom文件中添加了如下的依赖。gradle使用见官网。
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.16</version>
<scope>provided</scope>
</dependency>
常用的注解
官网注解介绍:Stable
2. Lombok原理介绍
2.1 Java类文件编译过程
首先,我们知道 Lombok 功能是作用在类编译时期,那我们来看下一个类编译的过程。
- 定义一个 PersonDTO.Java 类
代码语言:javascript
复制
public class PersonDTO {
//姓名
private String name;
}
- Javac PersonDTO.Java 对源代码进行解析转化,会生成一棵抽象语法树( AST );
- 运行过程中会调用实现了 JSR 269 注解处理器,下面介绍;
- JSR 实现可处理自定义逻辑,包括可修改编译后的抽象语法树(AST);
- Javac 使用修改后的抽象语法树(AST)生成字节码文件;
过程如下图:
AST 是抽象语法树(Abstract Syntax Tree) 的缩写,是 JAVA 源代码展示的一种树状结构它将代码的结构和语法元素映射到树节点上,使得程序可以在编译、
分析和转换过程中更容易地操作和理解。有兴趣可以学习 JavaParser 源码, 了解将 Java 源代码解析生成成一个抽象语法树( AST ),这个树形结构表示了代码的
语法结构包括类、方法、变量、语句等等过程。
github地址:https://github.com/javaparser/javaparser.
如: PersonDTO.Java 在 idea 中使用可视化工具展示文件 AST 树
2.2 JSR 269介绍
首先 JSR 269全称" Pluggable Annotation Processing API ",是 JAVA 平台的一项规范,也被称之为注解处理器 API 。在Java6引入,用于在编译时处理
注解,目标是提供更丰富的编译时元数据处理能力,以增强Java编译器的功能。这个规范允许开发人员创建自定义的注解处理器,这些处理器可以在编译时检查、分析和生成Java代码。
JSR 269在JDK6中被引入,作为APT的替代方案。javac执行的时候会调用Pluggable Annotation Processing API,因此我们可以通过实现此API来改变编译期的一些行为从而达到目的。具体的编译流程如下:
javac编译流程
举例来说,现在有一个实现了Pluggable Annotation Processing API的程序A,那么使用javac编译时的具体流程如下:
1. javac编译器对源码进行分析,生成一个抽象的语法树(AST)
2. javac编译器运行A程序
3. A程序完成逻辑,一般是修改此语法树
4. javac使用修改后的语法树生成可执行的字节码文件
Lomok便是通过Pluggable Annotation Processing API来实现代码生成的。
应用框架:
- Servlet、JAX-RS(RESTful Web服务)JSR 269 来生成用于处理 HTTP 请求的代码。
- Spring Boot 项目中以处理各种自定义注解,如 @Controller、@Service、@Repository 等。这些注解可以用于自动化配置、依赖注入等方面。
- Hibernate 它使用 JSR 269 来处理 JPA 注解,并生成与数据库交互的代码。
- Lombok 是一个 JAVA 库,它通过注解处理器生成常见的 JAVA 代码,如 getter、setter、equals、hashCode 等,以简化开发工作。
- MapStruct 是一个用于对象映射的 JAVA 库,它使用 JSR 269 来生成类型安全的映射代码,帮助开发人员将一个对象映射到另一个对象。
如何实现自定义注解注解处理器:
1.声明自定义注解;如 Lombok 下的 @Data,@Getter,@Setter等。
2.实现 Process接口,或者继承 AbstractProcessor 复写 process 方法,处理自定义注解逻辑。
代码语言:javascript
复制
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import java.util.Set;
@SupportedAnnotationTypes("com.example.MyAnnotation") // 自定义注解
@SupportedSourceVersion(SourceVersion.RELEASE_8) //支持的Java版本
public class MyAnnotationProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 在这里处理自定义注解,生成代码或执行其他任务
return true;
}
}
3.注册注解处理器
两种方式:
- Resource 文件:项目 META-INF/services 创建 javax.annotation.processing.Processor 文件,自定义注解处理器的全类名写到此文件中。
- 通过谷歌工具包 auto-service ,可自动生成以上配置文件。
代码语言:javascript
复制
<!-- 编译注解执行注册 jar包-->
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
<version>1.0.1</version>
</dependency>
@AutoService(Processor.class) //谷歌工具包方式:注册注解处理器
@SupportedAnnotationTypes("com.example.MyAnnotation") // 自定义注解
@SupportedSourceVersion(SourceVersion.RELEASE_8) //支持的Java版本
public class MyAnnotationProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)
{
// 在这里处理自定义注解,生成代码或执行其他任务
return true;
}
}
2.3 Lombok 实现原理
1. Lombok 实际就是结合注解处理器和 AST 技术, Lombok 实现的注解处理器会遍历 AST ,查找与 Lombok 注解相关的元素,根据注解的要求生成新的代码。
2.编译前后的 AST 语法树对比
加入@Getter注解编译后
3. Lombok 注解处理器,采用 Resource 方式注册编译注解处理器
注解处理器 AnnotationProcessor 源码:
代码语言:javascript
复制
class AnnotationProcessorHider {
public static class AstModificationNotifierData {
public volatile static boolean lombokInvoked = false;
}
public static class AnnotationProcessor extends AbstractProcessor {
// 获取支持的注解类型
@Override
public Set<String> getSupportedOptions() {
return instance.getSupportedOptions();
}
// 获取支持的注解类型
@Override
public Set<String> getSupportedAnnotationTypes() {
return instance.getSupportedAnnotationTypes();
}
// 支持的JDK版本
@Override
public SourceVersion getSupportedSourceVersion() {
return instance.getSupportedSourceVersion();
}
//初始化环境
@Override
public void init(ProcessingEnvironment processingEnv) {
disableJava9SillyWarning();
AstModificationNotifierData.lombokInvoked = true;
instance.init(processingEnv);
super.init(processingEnv);
}
// 处理自定义注解逻辑
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
roundEnv) {
return instance.process(annotations, roundEnv);
}
}
自定义注解处理器 Handler : 在 Jar 包的 lombok.javac.handlers下,每个注解处理对应一个 Handler. 如 HadlerGetter.java 操作 AST 树生成 getter 方法.
2.4手动实现一个 @Getter 功能
2.4.1.创建 maven 工程 demo 包含两个子模块 getter/getter-use
2.4.2. getter 工程
pom文件:
代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>demo</artifactId>
<groupId>com.example</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>getter</artifactId>
<dependencies>
<!-- 注解执行器依赖jar -->
<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>1.6.0</version>
<scope>system</scope>
<systemPath>${java.home}/../lib/tools.jar</systemPath>
</dependency>
<!-- 编译注解执行注册 -->
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
<version>1.0.1</version>
</dependency>
</dependencies>
</project>
自定义 GetterTest 注解
代码语言:javascript
复制
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author zcy1
*/
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface GetterTest {
}
GetterTest 编译注解处理器 GetterProcessor
代码语言:javascript
复制
import com.google.auto.service.AutoService;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.tree.TreeTranslator;
import com.sun.tools.javac.util.*;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import java.util.Set;
/**
* @author zcy1
*/
@AutoService(Processor.class) //谷歌工具包方式:注册注解处理器
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("GetterTest")
public class GetterProcessor extends AbstractProcessor {
/**
* 用于在编译器打印消息的组件
*/
private Messager messager;
/**
* 提供待处理抽象语法树
*/
private JavacTrees trees;
/**
* 用来构造语法树节点
*/
private TreeMaker treeMaker;
/**
* 用于创建标识符的对象
*/
private Names names;
/**
* 获取一些注解处理器执行处理逻辑时一些关键对象
* @param processingEnv 处理环境
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
this.messager = processingEnv.getMessager();
this.trees = JavacTrees.instance(processingEnv);
Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
this.treeMaker = TreeMaker.instance(context);
this.names = Names.instance(context);
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)
{
// 获取自定义GetterTest注解的类
Set<? extends Element> elementsAnnotatedWith =
roundEnv.getElementsAnnotatedWith(GetterTest.class);
elementsAnnotatedWith.forEach(e -> {
JCTree tree = trees.getTree(e);
tree.accept(new TreeTranslator() {
@Override
public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
List<JCTree.JCVariableDecl> jcVariableDeclList = List.nil();
// 在抽象树中找出所有的成员变量
for (JCTree jcTree : jcClassDecl.defs) {
if (jcTree.getKind().equals(Tree.Kind.VARIABLE)) {
JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) jcTree;
jcVariableDeclList = jcVariableDeclList.append(jcVariableDecl);
}
}
// 对于变量进行生成getter方法的操作
jcVariableDeclList.forEach(jcVariableDecl -> {
messager.printMessage(Diagnostic.Kind.NOTE, jcVariableDecl.getName() + "
has been processed");
jcClassDecl.defs =
jcClassDecl.defs.prepend(createGetterMethod(jcVariableDecl));
});
super.visitClassDef(jcClassDecl);
}
});
});
return true;
}
private JCTree.JCMethodDecl createGetterMethod(JCTree.JCVariableDecl jcVariableDecl) {
ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
// 生成表达式 this.name = name
JCTree.JCExpressionStatement aThis =
makeAssignment(treeMaker.Select(treeMaker.Ident(names.fromString("this")), jcVariableDecl.getName()), treeMaker.Ident(jcVariableDecl.getName()));
statements.append(aThis);
JCTree.JCBlock block = treeMaker.Block(0, statements.toList());
// 生成入参 (String name)
JCTree.JCVariableDecl param = treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER),
jcVariableDecl.getName(), jcVariableDecl.vartype, null);
List<JCTree.JCVariableDecl> parameters = List.of(param);
// 生成返回对象 void
JCTree.JCExpression methodType = treeMaker.Type(new Type.JCVoidType());
//生成方法名 getName
Name methodName = getMethodName(jcVariableDecl.getName());
// 返回语法树对象
return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC),methodName, methodType,
List.nil(),parameters, List.nil(), block, null);
}
/**
* 驼峰方法名
*/
private Name getMethodName(Name name) {
String s = name.toString();
return names.fromString("get" + s.substring(0, 1).toUpperCase() + s.substring(1,
name.length()));
}
private JCTree.JCExpressionStatement makeAssignment(JCTree.JCExpression lhs,
JCTree.JCExpression rhs) {
return treeMaker.Exec(treeMaker.Assign(lhs, rhs));
}
}
2.4.4 getter-use工程
pom 文件 引入getter工程
代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>demo</artifactId>
<groupId>com.example</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>getter-use</artifactId>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>getter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
PersonDTO 使用@GetterTest
代码语言:javascript
复制
@GetterTest
public class PersonDTO {
private String name;
private Integer age;
}
2.5.5 项目进行编译
getter 模块下自动生成注册器
getter-use 模块下 PersonDTO.class 可见生成了对应属性的 get 方法
3.总结
本文通过以上对 Lombok 相关介绍,通过对 JAVA 文件编译过程分析和 JSR269 实现的方式, 基于这个规范然后引申出 Lombok 实现原理过程介绍,以及手动实现 getter 案例,想必我们对 Lombok 原理也有了相应的了解。虽然 Lombok 提供了许多便利,由于生成的代码不在源文件中可见,就会导致代码的可读性和维护性较差。在工作中 Lombok 使用时注意闭坑:
问题 | 解决 |
---|---|
@Data 和 @Builder 一起使用时,无参构造方法会被干掉 | 手动加上注解: @AllArgsConstructor、@NoArgsConstructor。 |
@Builder 导致类属性默认值无效。 | 有默认值属性上加注解 : @lombok.Builder.Default。 |
@Data 生成的 toString 方法,默认能不输出父类属性 | 子类添加: @ToString(callSuper = true)。 |
参考文献
Lombok 官网地址: https://projectlombok.org
JavaParser 源码地址: https://github.com/javaparser/javaparser
JAVA 抽象语法树 AST 浅析与使用: https://www.freesion.com/article/4068581927/