在 Java 5 中初次引入了注解,此时注解处理需要额外的工具,即 apt(Annotation Processing Tool) 来处理。
在 Java 6 中将注解处理内置到了 javac 中,就不需要额外的 apt 来处理了。相关新增选项如下:
Java SE6 中注解处理的主要包:
注解是按照轮次(round)来处理的。
调用逻辑:
发现机制:有如下三种方式
接口方法:
上述 Processor 接口,不推荐自行实现。推荐继承 AbstractProcessor 抽象类来实现自定义的注解处理器。
AbstractProcessor 抽象类:作为 Processor 接口的实现类,作为自定义注解处理器的基类。其主要做了如下实现:
主要功能:
主要功能:
上面讲述的是注解处理器,下面讲述如何通过 AST 抽象语法树修改源代码。
AST,Abstract Syntax Tree,抽象语法树:用于表示源代码的抽象语法结构,一般是编译器会使用到它。
Java 源代码示例:
// Main class
public class GFG {
// Main driver method
public static void main(String[] args)
{
// Print statement
System.out.println("Hello World!");
}
}
对应的抽象语法树如下:
CLASS_DEF -> CLASS_DEF [1:0]
|--MODIFIERS -> MODIFIERS [1:0]
| `--LITERAL_PUBLIC -> public [1:0]
|--LITERAL_CLASS -> class [1:7]
|--IDENT -> GFG [1:13]
`--OBJBLOCK -> OBJBLOCK [1:17]
|--LCURLY -> { [1:17]
|--METHOD_DEF -> METHOD_DEF [2:4]
| |--MODIFIERS -> MODIFIERS [2:4]
| | |--LITERAL_PUBLIC -> public [2:4]
| | `--LITERAL_STATIC -> static [2:11]
| |--TYPE -> TYPE [2:18]
| | `--LITERAL_VOID -> void [2:18]
| |--IDENT -> main [2:23]
| |--LPAREN -> ( [2:27]
| |--PARAMETERS -> PARAMETERS [2:34]
| | `--PARAMETER_DEF -> PARAMETER_DEF [2:34]
| | |--MODIFIERS -> MODIFIERS [2:34]
| | |--TYPE -> TYPE [2:34]
| | | `--ARRAY_DECLARATOR -> [ [2:34]
| | | |--IDENT -> String [2:28]
| | | `--RBRACK -> ] [2:35]
| | `--IDENT -> args [2:37]
| |--RPAREN -> ) [2:41]
| `--SLIST -> { [2:43]
| |--EXPR -> EXPR [3:26]
| | `--METHOD_CALL -> ( [3:26]
| | |--DOT -> . [3:18]
| | | |--DOT -> . [3:14]
| | | | |--IDENT -> System [3:8]
| | | | `--IDENT -> out [3:15]
| | | `--IDENT -> println [3:19]
| | |--ELIST -> ELIST [3:27]
| | | `--EXPR -> EXPR [3:27]
| | | `--STRING_LITERAL -> "Hello World!" [3:27]
| | `--RPAREN -> ) [3:41]
| |--SEMI -> ; [3:42]
| `--RCURLY -> } [4:4]
`--RCURLY -> } [5:0]
Java 中的 AST,使用 JCTree 类来替代。
该类中包含大量静态内部类:
还有一些辅助的类:
下图可以比较好的解释树中节点对应的 Java 类:
假设有如下源码:
public class User {
private String name;
private int age;
}
则对应的 JCTree 对象如下:在 defs 属性上可以看到 1 个方法定义、两个变量定义
**在上述 JCTree 的 defs 列表中加入新的元素,就可以实现新增方法啦。**😎
了解上述原理后,我们手写一个 @Getter 注解,实现属性的 getter 方法的生成。lombok-demo.zip
创建两个模块:
processor 模块主要干两件事:
@Getter 注解定义非常简单,直接定义注解存放位置(类、字段)及在源码上保留。
/**
* Getter 注解,用于自动生成 public 类型的 Getter 方法
*
* @author demo
*/
@Target({ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.SOURCE)
public @interface Getter {
}
注解处理器代码如下:
package com.lombok.processor;
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.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.Context;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.Name;
import com.sun.tools.javac.util.Names;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
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;
/**
* @Getter 注解处理器
*
* @author demo
*/
@SupportedAnnotationTypes("com.lombok.annotation.Getter")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class GetterAnnotationProcessor extends AbstractProcessor {
// 用于输出日志
private Messager messager;
// java 处理环境的上下文
private Context context;
// 命名工具类
private Names names;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
this.messager = processingEnv.getMessager();
this.context = ((JavacProcessingEnvironment) processingEnv).getContext();
this.names = Names.instance(context);
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
messager.printMessage(Diagnostic.Kind.NOTE, "进入 GetterAnnotationProcessor.process() 方法");
// 正常来说,annotations 里只有一个注解,就是 com.lombok.annotation.Getter
for (TypeElement typeElement : annotations) {
// 获取所有具备此注解的元素
Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(typeElement);
set.forEach(element -> {
// 遍历元素,并找到其抽象语法树
JCTree jcTree = JavacTrees.instance(processingEnv).getTree(element);
// 访问类定义的时候,查找类中的变量定义,并给这些变量添加 getter 方法
jcTree.accept(new TreeTranslator() {
@Override
public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
// 遍历所有定义
for (JCTree tree : jcClassDecl.defs) {
if (tree.getKind().equals(Tree.Kind.VARIABLE)) {
// 对所有变量添加 getter 方法定义
JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) tree;
messager.printMessage(Diagnostic.Kind.NOTE, "正在为 " + jcVariableDecl.getName() + " 添加 getter 方法");
jcClassDecl.defs = jcClassDecl.defs.prepend(makeGetterMethodDecl(jcVariableDecl));
}
}
super.visitClassDef(jcClassDecl);
}
});
});
}
// 返回 true,表示后续处理器不会继续处理了
return true;
}
private JCTree.JCMethodDecl makeGetterMethodDecl(JCTree.JCVariableDecl jcVariableDecl) {
TreeMaker treeMaker = TreeMaker.instance(context);
ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
// return this.name;
statements.append(treeMaker.Return(treeMaker.Select(treeMaker.Ident(names.fromString("this")), jcVariableDecl.getName())));
// {\r\n return this.name; \r\n}
JCTree.JCBlock body = treeMaker.Block(0, statements.toList());
// public String getName() {
// return this.name;
//}
return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC), getGetMethodName(jcVariableDecl.getName()), jcVariableDecl.vartype,
List.nil(), List.nil(), List.nil(), body, null);
}
private Name getGetMethodName(Name name) {
String s = name.toString();
return names.fromString("get" + s.substring(0, 1).toUpperCase() + s.substring(1, name.length()));
}
}
有些难懂,拆解一下。😂
首先,先创建一个 SetterGetterAnnotationProcessor 类,其需要继承注解处理器的基类。
其次,指定注解处理器仅处理 SetterGetter 注解,即 @SupportedAnnotationTypes(“com.lombok.annotation.SetterGetter”)
最后,指定注解处理器支持的最大源码版本,即 @SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("com.lombok.annotation.Getter")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class GetterAnnotationProcessor extends AbstractProcessor { }
基类 AbstractProcessor 中已经有了 init 方法,我们可以 Override 一下,加入一下我们想要的属性。
// 用于输出日志
private Messager messager;
// java 处理环境的上下文
private Context context;
// 命名工具类
private Names names;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
this.messager = processingEnv.getMessager();
this.context = ((JavacProcessingEnvironment) processingEnv).getContext();
this.names = Names.instance(context);
}
核心处理逻辑如下:
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
messager.printMessage(Diagnostic.Kind.NOTE, "进入 GetterAnnotationProcessor.process() 方法");
// 正常来说,annotations 里只有一个注解,就是 com.lombok.annotation.Getter
for (TypeElement typeElement : annotations) {
// 获取所有具备此注解的元素
Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(typeElement);
set.forEach(element -> {
// 遍历元素,并找到其抽象语法树
JCTree jcTree = JavacTrees.instance(processingEnv).getTree(element);
// 访问类定义的时候,查找类中的变量定义,并给这些变量添加 getter 方法
jcTree.accept(new TreeTranslator() {
@Override
public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
// 遍历所有定义
for (JCTree tree : jcClassDecl.defs) {
if (tree.getKind().equals(Tree.Kind.VARIABLE)) {
// 对所有变量添加 getter 方法定义
JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) tree;
messager.printMessage(Diagnostic.Kind.NOTE, "正在为 " + jcVariableDecl.getName() + " 添加 getter 方法");
jcClassDecl.defs = jcClassDecl.defs.prepend(makeGetterMethodDecl(jcVariableDecl));
}
}
super.visitClassDef(jcClassDecl);
}
});
});
}
// 返回 true,表示后续处理器不会继续处理了
return true;
}
生成逻辑如下:
private JCTree.JCMethodDecl makeGetterMethodDecl(JCTree.JCVariableDecl jcVariableDecl) {
TreeMaker treeMaker = TreeMaker.instance(context);
ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
// return this.name;
statements.append(treeMaker.Return(treeMaker.Select(treeMaker.Ident(names.fromString("this")), jcVariableDecl.getName())));
// {\r\n return this.name; \r\n}
JCTree.JCBlock body = treeMaker.Block(0, statements.toList());
// public String getName() {
// return this.name;
//}
return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC), getGetMethodName(jcVariableDecl.getName()), jcVariableDecl.vartype,
List.nil(), List.nil(), List.nil(), body, null);
}
private Name getGetMethodName(Name name) {
String s = name.toString();
return names.fromString("get" + s.substring(0, 1).toUpperCase() + s.substring(1, name.length()));
}
JCTree、TreeMaker 等类在 JDK 的 tools.jar 包中,默认情况下,如果没有配置好 CLASSPATH,则可能会没有包含此 jar 到 CLASSPATH 下。
可在 pom.xml 中引入此系统依赖:
<dependencies>
<!-- https://mvnrepository.com/artifact/com.sun/tools -->
<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>8</version>
<scope>system</scope>
<systemPath>${java.home}/../lib/tools.jar</systemPath>
</dependency>
</dependencies>
定义好上述注解处理器后,调用方如何才能调用注解?
编译 processor 模块(clean install)时,会编译报错:Provider com.lombok.processor.GetterAnnotationProcessor not found
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.5.1:compile (default-compile) on project processor: Compilation failure
[ERROR] 服务配置文件不正确, 或构造处理程序对象javax.annotation.processing.Processor: Provider com.lombok.processor.GetterAnnotationProcessor not found时抛出异常错误
报错原因:编译器由于 SPI 机制,发现了注解处理器,就在编译期间执行了注解处理器,但是注解处理器本身还没编译,所以找不到注解处理器。
解决办法:processor 模块只编译,不需要执行注解处理器。在 processor 模块的 pom.xml 中指定编译器参数:-proc:none 即可。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
<executions>
<execution>
<id>default-compile</id>
<configuration>
<compilerArgument>-proc:none</compilerArgument>
</configuration>
</execution>
<execution>
<id>compile-project</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
想要使用 @Getter 注解,则需要引入 processor 模块。
<dependency>
<groupId>org.example</groupId>
<artifactId>processor</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
@Getter
public class User {
private String name;
private int age;
}
在 processor 模块执行 mvn clean install 后,选中 demo 模块,执行如下即可调试:【或者执行 mvnDebug clean install,然后在 IDEA 中添加 Remote JVM Debug 也可调试】
调试效果如下:
执行结果:
至此,@Getter 注解就完成自动生成 getter 方法的任务啦!😎
注意:默认情况下,IDEA 如果自动编译的话,可能不会走上述注解处理器的逻辑。
下面,我们对 Lombok 源码解读下。看看和我们自己写的 demo 有啥不同。
比对结论:Lombok 考虑得更加全面,功能更加完善,且使用了自定义类加载加载 lombok 文件。
Lombok 也是使用 SPI 来暴露注解处理器的:
其提供了两个注解处理器:
1、AnnotationProcessor 注解处理器:这个注解处理器实际上会委派给 lombok.core.AnnotationProcessor 注解处理器。
public static class AnnotationProcessor extends AbstractProcessor {
private final AbstractProcessor instance = createWrappedInstance();
@Override public Set<String> getSupportedOptions() {
return instance.getSupportedOptions();
}
@Override public Set<String> getSupportedAnnotationTypes() {
return instance.getSupportedAnnotationTypes();
}
@Override public SourceVersion getSupportedSourceVersion() {
return instance.getSupportedSourceVersion();
}
@Override public void init(ProcessingEnvironment processingEnv) {
disableJava9SillyWarning();
AstModificationNotifierData.lombokInvoked = true;
instance.init(processingEnv);
super.init(processingEnv);
}
// sunapi suppresses javac's warning about using Unsafe; 'all' suppresses eclipse's warning about the unspecified 'sunapi' key. Leave them both.
// Yes, javac's definition of the word 'all' is quite contrary to what the dictionary says it means. 'all' does NOT include 'sunapi' according to javac.
@SuppressWarnings({"sunapi", "all"})
private void disableJava9SillyWarning() {
// JVM9 complains about using reflection to access packages from a module that aren't exported. This makes no sense; the whole point of reflection
// is to get past such issues. The only comment from the jigsaw team lead on this was some unspecified mumbling about security which makes no sense,
// as the SecurityManager is invoked to check such things. Therefore this warning is a bug, so we shall patch java to fix it.
try {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe u = (Unsafe) theUnsafe.get(null);
Class<?> cls = Class.forName("jdk.internal.module.IllegalAccessLogger");
Field logger = cls.getDeclaredField("logger");
u.putObjectVolatile(cls, u.staticFieldOffset(logger), null);
} catch (Throwable t) {
// We shall ignore it; the effect of this code failing is that the user gets to see a warning they remove with various --add-opens magic.
}
}
@Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
return instance.process(annotations, roundEnv);
}
@Override public Iterable<? extends Completion> getCompletions(Element element, AnnotationMirror annotation, ExecutableElement member, String userText) {
return instance.getCompletions(element, annotation, member, userText);
}
private static AbstractProcessor createWrappedInstance() {
ClassLoader cl = Main.getShadowClassLoader();
try {
Class<?> mc = cl.loadClass("lombok.core.AnnotationProcessor");
return (AbstractProcessor) mc.getDeclaredConstructor().newInstance();
} catch (Throwable t) {
if (t instanceof Error) throw (Error) t;
if (t instanceof RuntimeException) throw (RuntimeException) t;
throw new RuntimeException(t);
}
}
}
上面 createWrappedInstance() 方法,使用的自定义的类加载,去加载了 lombok.core.AnnotationProcessor 类,这个类只能被 lombok 类加载器加载。具体类如下:
2、ClaimingProcessor 注解处理器:这个处理器啥也不干,做最后收尾,表示后续处理器都不要处理了。
@SupportedAnnotationTypes("lombok.*")
public static class ClaimingProcessor extends AbstractProcessor {
@Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
return true;
}
@Override public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latest();
}
}
既然我们知道了 Lombok 注解处理器的位置,直接打断点看下:选择模块,右键 Debug Maven,即可进入 Debug 模式。
1、首先进入 launch\AnnotationProcessor 的 process 方法
2、然后进入 core\AnnotationProcessor 的 process 方法
3、然后使用 LombokProcessor 来处理
4、处理完毕后,进入 ClaimProcessor 的 process 处理方法
然后继续后面的轮次,直到结束!😎
1.Java SE 6 — 更好的 JPA、更好的 JAXB 和更好的批注处理
2.Java注解编译期处理AbstractProcessor详解
3.用了那么久的Lombok,你知道它的原理么?-阿里云开发者社区
4.IDEA结合maven进行编译期注解处理器调试_如何在idea中调试编译期源码-CSDN博客
5.https://www.geeksforgeeks.org/abstract-syntax-tree-ast-in-java/
1.消除代码冗长神器 - Lombok | 入门-CSDN博客
2.消除代码冗长神器 - Lombok | 安装-CSDN博客
3.消除代码冗长神器 - Lombok | val/var 本地变量声明-CSDN博客
4.消除代码冗长神器 - Lombok | @Setter/@Getter 生成 setter/getter 方法-CSDN博客
5.消除代码冗长神器 - Lombok | @EqualsAndHashCode/@ToString注解详解-CSDN博客
6.消除代码冗长神器 - Lombok | @NoArgsConstructor/@RequiredArgsConstructor/@AllArgsConstructor 构造方法-CSDN博客
7.消除代码冗长神器 - Lombok | @Data 通用 POJO 注解-CSDN博客
8.消除代码冗长神器 - Lombok | @With 不可变的 setter 方法-CSDN博客
9.消除代码冗长神器 - Lombok | @Value 不可变 entity-CSDN博客
10.消除代码冗长神器 - Lombok | @Cleanup 自动清理资源-CSDN博客
11.消除代码冗长神器 - Lombok | @Log/@Slf4j 创建日志对象-CSDN博客
12.消除代码冗长神器 - Lombok | @Builder注解 - Builder 设计模式快速实现
13.消除代码冗长神器 - Lombok | @NonNull 判空逻辑
14.消除代码冗长神器 - Lombok | @SneakyThrows 检查型异常转抛
15.消除代码冗长神器 - Lombok | @Synchronized 锁
16.消除代码冗长神器 - Lombok | 试验性注解
17.消除代码冗长神器 - Lombok | 进阶用法 - 全局配置 & 去除Lombok
18.消除代码冗长神器 - Lombok | 高阶用法 -> 手写 @Getter 注解