1. JSR-269
在网上搜索AnnotationProcessor相关的内容, 最后总能指向一个神秘代码: JSR-269
https://jcp.org/en/jsr/detail?id=269
JSR是Java Specification Requests的首字母缩写,是所有提交的Java技术规范的总称,包括已提交、审核中和发布的java规范。
JSR-269规范, 这个是由 Java Community Process(JCP)通过的一个Java语言的规范, 查看 也就是说JSR-269在任何1.6+的JDK上都是必须支持的
- JCP不是一个组织,而是一个开放的流程,一个审核机制,旨在让任何第三方组织或个人开发者都可以参与Java规范的制定;
- JCP包括JSR提交、公共评审和投票等核心环节,最终由EC投票完成。一个JSR要经过3次EC投票和3次公开评审;
那这个标准是干嘛的呢?
[
](https://jcp.org/en/jsr/detail?id=269)
我们来看一下JSR-269的部分原文
Original Java Specification Request (JSR)标题下的 Section 2: Request
2.1 J2SE 1.5 添加了一种新的Java语言机制”annotations”, 它允许在类,成员变量, 方法上添加 注解. 这些注解可以在编译时或运行时提供新的语义效果 为了支持这种注解在编译时的作用, 这个JSR会定义一些API, 以允许创建实现了标准的可插拔的API的”annotation processor” 这样会简化创建annotation processor的工作, 并允许 annotation processor 指定适用的源文件范围 该规范至少包括两个部分,一是对Java编程语言建模的API部分,二是用于声明注释处理器并控制它们的运行方式。 由于注释放置在程序元素上,注释处理框架需要反映程序结构。注释处理器将能够指定它们处理的注释以及多个处理器能够协同运行。 processor和编写的api可以在构建时被访问执行, 换句话说, 这个功能填补了读取annotation的核心反射支持。
javac的编译过程大致可分为三步:
- 解析与填充符号
- 注解处理器处理
- 分析与字节码生成
那么AnnotationProcessor主要就是工作在第二步
标准做法:
- 创建自己的Annotation Processor, 并继承AbstractProcessor类
- 重写process()方法, 实现注解处理的逻辑(这一步有机会对java编译过程中产生的抽象语法树进行修改)
- META-INF/services目录下创建javax.annotation.processing.Processor文件注册自己实现的Annotation Processor
2. 创建一个自己的AnnotationProcessor
https://gitee.com/spitman/study_processor/tree/master/fake-lombok
2.1 新建一个Maven项目
新建一个最简单的空maven项目
2.2 设置依赖
pom.xml文件中设置依赖
<dependencies>
<!-- 必须手动依赖tool.jar不然无法使用操作AST的操作 -->
<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>1.8</version>
<scope>system</scope>
<systemPath>${java.home}/../lib/tools.jar</systemPath>
</dependency>
</dependencies>
2.3 创建注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface MyAnnotation {
}
2.4 创建AnnotationProcessor
@SupportedAnnotationTypes("com.example.annotation.MyAnnotation")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyAnnotationProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
//初始化processor操作
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
//注解处理操作, 这里有机会对标记注解类的抽象语法树进行操作
return true;
}
}
2.5 创建javax.annotation.processing.Processor文件
在META-INF/services目录下创建javax.annotation.processing.Processor文件
内容:
com.example.processor.MyAnnotationProcessor
xxx.yyy.zzz.MyAnnotationProcessor2
...
2.6 编辑maven中的编译相关配置
2.6.1 排除javax.annotation.processing.Processor文件
主要是为了防止在编译此AnnotationProcessor项目中自己加载AnnotationProcessor形成循环调用AnnotationProcessor
<build>
<!-- 在编译的时候先排除掉 META-INF/services/javax.annotation.processing.Processor 文件 -->
<!-- 不然自己编译会调用未编译的Processor -->
<resources>
<resource>
<directory>src/main/resources</directory>
<excludes>
<exclude>META-INF/**/*</exclude>
</excludes>
</resource>
</resources>
</build>
2.6.2 编译结果添加javax.annotation.processing.Processor
<build>
...
<plugins>
...
<!-- 前面resource标签排除掉了META-INF/services/javax.annotation.processing.Processor 文件 -->
<!-- 所以需要在编译完成之后, 把META-INF/services/javax.annotation.processing.Processor 文件复制回编译好的目录下, 再进行打包操作 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.6</version>
<executions>
<execution>
<id>process-META</id>
<phase>prepare-package</phase> <!--生效的阶段-->
<goals>
<goal>copy-resources</goal> <!--目标是复制资源文件-->
</goals>
<configuration>
<!--复制的目的地-->
<outputDirectory>target/classes</outputDirectory>
<!--复制的来源-->
<resources>
<resource>
<directory>${basedir}/src/main/resources/</directory>
<includes>
<include>**/*</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
...
</plugins>
...
</build>
2.7 打包
执行mvn clean install
执行mvn clean package
2.8 使用
其他项目想要使用非常简单
- 引入这个AnnotationProcessor项目作为依赖
- 使用
MyAnnotation
这个注解 - 编译项目即可执行MyAnnotationProcessor.process()方法
3. 修改AST
3.1 预备知识
3.1.2 com.sun.tools.javac.tree.JCTree
JCTree提供了大量的内部类, 用来表示抽象语法树上的各个节点
3.1.3 com.sun.tools.javac.api.JavacTrees
3.1.4 com.sun.tools.javac.tree.TreeMaker
3.1.5 com.sun.tools.javac.util.Names
3.1.6 RoundEnvironment
RoundEnvironment是JSR-269定义的标准接口
3.2 在AbstractProcessor中修改抽象语法树
�当项目启动的时候, 在javax.annotation.processing.Processor声明的AbstractProcessor实现类会被执行
javax.annotation.processing.AbstractProcessor#init()
会在初始化的时候被调用, 这里可以进行一些初始化操作javax.annotation.processing.AbstractProcessor#process()
此方法提供了RoundEnvironment类型的形参可以通过它对AST进行修改操作
//举例: 给属性添加getter方法
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(MyAnnotation.class);//过滤出标记的类
//Element represents a program element such as a package, class, or method
set.forEach(element -> {
//对注解标记的类生成一个JCTree语法树
JCTree jcTree = trees.getTree(element);
jcTree.accept(new TreeTranslator() {
//处理遍历语法树得到的类定义部分 JCClassDecl
@Override
public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
//创建一个 jcVariableDeclList 保存类的成员变量
//遍历 jcTree 的所有成员(包括成员变量和成员函数和构造函数)
for (JCTree tree : jcClassDecl.defs) {
//过滤出其中的成员变量
if (tree.getKind().equals(Tree.Kind.VARIABLE)) {
JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) tree;
//打印
messager.printMessage(Diagnostic.Kind.NOTE, jcVariableDecl.getName() + " has been processed");
//将 jcVariableDeclList 的所有变量转换成需要添加的 getter 方法
JCTree.JCMethodDecl jcMethodDecl = makeGetterMethodDecl(jcVariableDecl);
//并添加进 jcClassDecl 的成员中
jcClassDecl.defs = jcClassDecl.defs.prepend(jcMethodDecl);
}
}
super.visitClassDef(jcClassDecl);
}
});
});
return true;
}
3.3 其他
有了可以操作抽象语法树的入口, 我们就可以在编译时修改代码的能力
例如Android里面很经典的 ButterKnife 工具, 有兴趣的可以自己了解一下