共计 4701 个字符,预计需要花费 12 分钟才能阅读完成。
注解处理器初探
平时做项目中有个非常好用的一个插件, 叫 lombok. 它提供了一些简单的注解, 可以用来生成 javabean 和一些 getter/setter 方法, 提高了开发的效率节省了开发时间.
今天我们就来看看 lombok 使用的什么方式来实现这种操作的. 其实 lombok 使用的是 annotation processor, 这个是 jdk1.5 中增加的新功能. 像 @Getter 只是一个注解, 它真正的处理部分
是在注解处理器里面实现的. 官方参考链接.
背景介绍
注解处理器其实全称叫 Pluggable Annotation Processing API, 插入式注解处理器, 它是对 JSR269 提案的实现, 具体可以看链接里面的内容,JSR269 链接.
它是怎么工作的呢? 可以参考下图:
1.parse and enter: 解析和输入,java 编译器这个阶段会把源代码解析生成 AST(抽象语法分析树)
2.annotation processing: 注解处理器阶段, 此时将调用注解处理器, 这时候可以校验代码, 生成新文件等等 (处理完可以循环到第一步)
3.analyse and generate: 分析和生成, 此时前两步完成后, 生成字节码(这个阶段进行了解糖, 比如类型擦除)
这些其实只是为了给大家留有一个粗浅的印象, 它是怎么执行的.
实践
看了上面的资料, 大脑中应该有了一个大概的印象, 现在我们实际操作一下写一个简单的例子, 实践一下.
要使用注解处理器需要两个步骤:
1. 自定义一个注解
2. 继承 AbstractProcessor 并且实现 process 方法
我们接下来写一个很简单的例子, 就是在一个类上加上 @InterfaceAnnotation, 编译的时候去生成一个 ”I”+ 类名的接口类.
首先我这里是定义了两个 moudle, 一个用来写注解和处理器, 另一个用来调用注解.
第一步: 自定义一个注解
@Target({ElementType.TYPE}) | |
@Retention(RetentionPolicy.SOURCE) | |
public @interface InterfaceAnnotation {} |
1.@Target: 表示的是这个注解在什么上面使用, 这里 ElementType.TYPE 是指在类上使用该注解
2.@Retention: 表示的是保留到什么阶段, 这里 RetentionPolicy.SOURCE 是源代码阶段, 编译后的 class 上就没有这个注解了
第二步: 继承 AbstractProcessor 并且实现 process 方法
@SupportedAnnotationTypes(value = {"com.example.processor.InterfaceAnnotation"}) | |
@SupportedSourceVersion(value = SourceVersion.RELEASE_8) | |
public class InterfaceProcessor extends AbstractProcessor { | |
@Override | |
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {Messager messager = processingEnv.getMessager(); | |
messager.printMessage(Diagnostic.Kind.NOTE, "进入到 InterfaceProcessor 中了~~~"); | |
// 将带有 InterfaceProcessor 的类给找出来 | |
Set<? extends Element> clazz = roundEnv.getElementsAnnotatedWith(InterfaceAnnotation.class); | |
clazz.forEach(item -> { | |
// 生成一个 I + 类名的接口类 | |
String className = item.getSimpleName().toString(); | |
className = "I" + className.substring(0, 1) + className.substring(1); | |
TypeSpec typeSpec = TypeSpec.interfaceBuilder(className).addModifiers(Modifier.PUBLIC).build(); | |
try { | |
// 生成 java 文件 | |
JavaFile.builder("com.example.processor", typeSpec).build().writeTo(new File("./src/main/java/")); | |
} catch (IOException e) {e.printStackTrace(); | |
} | |
}); | |
return true; | |
} | |
} |
1.@SupportedAnnotationTypes: 表示这个 processor 类要对什么注解生效
2.@SupportedSourceVersion: 表示支持的 java 版本
3.annotations: 被要求的注解, 就是 @SupportedAnnotationTypes 对应的注解
4.roundEnv: 存放着当前和上一轮 processing 的环境信息
5.TypeSpec 这个可能有点没看懂是干嘛的, 它是 javaPoet 中的一个类,javaPoet 是 java 用于生成 java 文件的一款第三方插件很好用, 所以这里使用了这个类来生成 java 文件,
实际上这里用 java 自带的 PrintWriter 等输入输出流也可以生成 java 文件, 生成文件有很多方式.javaPoet 的链接.javaPoet 使用指南.
6.Messager 是用来打印输出信息的,System.out.println 其实也可以;
7.process 如果返回是 true 后续的注解处理器就不会再处理这个注解, 如果是 false, 在下一轮 processing 中, 其他注解处理器也会来处理改注解.
写好之后, 这里需要指定 processor,META-INF/services/javax.annotation.processing.Processor 写好 com.example.processor.InterfaceProcessor. 如果你不知道这是啥, 可以看下我另一篇博客 (实力推广 XD) 什么是 SPI
我们在把注解处理器给编译好,maven 里插件的设置:
<plugin> | |
<groupId>org.apache.maven.plugins</groupId> | |
<artifactId>maven-compiler-plugin</artifactId> | |
<version>3.7.0</version> | |
<configuration> | |
<source>1.8</source> | |
<target>1.8</target> | |
<!-- 不加这一句编译会报找不到 processor 的异常 --> | |
<compilerArgument>-proc:none</compilerArgument> | |
</configuration> | |
</plugin> |
此时的目录结构是这样:
. | |
├── HELP.md | |
├── pom.xml | |
├── processor.iml | |
└── src | |
└── main | |
├── java | |
│ └── com | |
│ └── example | |
│ └── processor | |
│ ├── InterfaceAnnotation.java | |
│ └── InterfaceProcessor.java | |
└── resources | |
└── META-INF | |
└── services | |
└── javax.annotation.processing.Processor |
然后 mvn clean install.
第三步: 使用注解
在使用之前呢, 注解处理器要是编译好的. 引入注解处理器的 jar 包.
测试类加上 @InterfaceAnnotation
@InterfaceAnnotation | |
public class TestProcessor {} |
maven 指定编译时使用的注解处理器.
<plugin> | |
<groupId>org.apache.maven.plugins</groupId> | |
<artifactId>maven-compiler-plugin</artifactId> | |
<version>3.7.0</version> | |
<configuration> | |
<source>1.8</source> | |
<target>1.8</target> | |
<encoding>UTF-8</encoding> | |
<annotationProcessors> | |
<annotationProcessor> | |
com.example.processor.InterfaceProcessor | |
</annotationProcessor> | |
</annotationProcessors> | |
</configuration> | |
</plugin> |
此时目录结构是
. | |
├── HELP.md | |
├── pom.xml | |
├── src | |
│ └── main | |
│ ├── java | |
│ │ └── com | |
│ │ └── example | |
│ │ └── test | |
│ │ └── TestProcessor.java | |
│ └── resources | |
└── test.iml |
然后 mvn compile, 生成了 java 文件, 此时目录结构是:
. | |
├── HELP.md | |
├── pom.xml | |
├── src | |
│ └── main | |
│ ├── java | |
│ │ └── com | |
│ │ └── example | |
│ │ ├── processor | |
│ │ │ └── ITestProcessor.java // 这里就是生成的 java 文件 | |
│ │ └── test | |
│ │ └── TestProcessor.java | |
│ └── resources | |
├── target | |
│ ├── classes | |
│ │ └── com | |
│ │ └── example | |
│ │ └── test | |
│ │ └── TestProcessor.class | |
│ ├── generated-sources | |
│ │ └── annotations | |
│ └── maven-status | |
│ └── maven-compiler-plugin | |
│ └── compile | |
│ └── default-compile | |
│ ├── createdFiles.lst | |
│ └── inputFiles.lst | |
└── test.iml |
看到了生成的 java 文件就大功告成~
总结:
1.java 注解处理器在很多地方都可以使用, 实际应用比如 lombok, 安卓生成 fragment 等等, 只使用一个注解可以省去很多代码, 提高效率;
2. 本文只是列举了一个很简单的例子, 很多注解处理器里面的 api 都没有使用到, 读者有兴趣的可以自行研究, 而且有涉及到抽象语法树的 api;
3. 注解处理器可以用于生成新的类来完成某些功能, 但是不能直接修改当前的类.
参考资料:
1.https://docs.oracle.com/javas…
2.https://jcp.org/aboutJava/com…
3.https://github.com/square/jav…
4.https://www.cnblogs.com/throw…
5.http://notatube.blogspot.com/…
6.https://www.baeldung.com/java…
7.http://hannesdorfmann.com/ann…