java源码分析-编译期注解处理器
上一篇我们把运行时注解处理器的原理大致分析了一遍,本片我们来讨论一下编译期注解处理器的底层实现。
1.Annotation Processing Tool
1.1概述
Java编译期注解处理器,Annotation Processing Tool,简称APT,是Java提供给开发者的用于在编译期对注解进行处理的一系列API,这类API的使用被广泛的用于各种框架,如dubbo,lombok等。
Annotation Processing Tool注解处理器是 javac 自带的一个工具,用来在编译时期扫描处理注解信息。你可以为某些注解注册自己的注解处理器。一个特定注解的处理器以 java 源代码(或者已编译的字节码)作为输入,然后生成一些文件(通常是.java
文件)作为输出。也就是说你可以在编译期生成java代码。这些 java 代码在生成的.java
文件中。因此你不能改变已经存在的java类,例如添加一个方法。这些生成的 java 文件跟其他手动编写的 java 源代码一样,将会被 javac 编译。
1.2API已过时
apt的核心API是都是在com.sun.mirror.apt包下,如下:
接口AnnotationProcessor是APT的一个核心接口,其内部只有一个方法process(),用于检查和处理源程序中的注解。
通过上图的我们可以看到,该APT的所有API均已过时。
事实上,APT自JDK5产生,JDK7已标记为过期,不推荐使用,JDK8中已彻底删除,自JDK6开始,可以使用Pluggable Annotation Processing API来替换它,apt被替换主要有2点原因:
1)、api都在com.sun.mirror非标准包下
2)、没有集成到javac中,需要额外运行
既然已经过时了,那我们就不细说了,我们来看看他的替代方案插件式注解处理器。
2.Pluggable Annotation Processing API
2.1概述
Pluggable Annotation Processing API,插件式注解处理器,它也是作用在编译期的注解处理器技术。作为APT的替代方案,他解决了APT的两个问题,javac在执行的时候会调用实现了该API的程序,这样我们就可以对编译器做一些增强,这时javac执行的过程如下:
(1)将源文件解析为抽象语法树;
(2)调用已经注册的注解处理器;
- 如果该过程生成了新的源文件,编译期将重复(1)、(2)步;
- 每次重复称为一轮;
- 第一轮解析处理的是输入至编译器中已有的源文件;
- 当注解处理器不在生成新的源文件,将进入最后一轮。
(3)生成字节码文件。
2.2使用场景
通过这种插件式注解处理器,我们可以在以下几个场景
(1)定义源码编译规则,并检查被编译的源码;
(2)修改源代码中已有的API,甚至包括java编译器的API,这种方式不推荐使用;
(3)可以通过该技术,在编译期生成新的源代码,类似于Lambok中的@Data注解,就是在编译期生成一些Getter/Setter方法等。
2.3核心API
注解处理器的核心API是Processor接口,它在javax.annotation.processing包下,我们看一下它的源码:
public interface Processor {
Set<String> getSupportedOptions();
//通过该方法获得该注解处理器能够处理的注解类型的全称,也就是当前这个注解处理器用于处理哪个(些)注解。
Set<String> getSupportedAnnotationTypes();
SourceVersion getSupportedSourceVersion();
//它会被注解处理器调用,并输入ProcessingEnviroment参数。ProcessingEnviroment提供了很多工具类Elements, Types和Filer。
void init(ProcessingEnvironment processingEnv);
//评估和处理注解的代码,以及生成Java文件。相当于每一个处理器的main函数
boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv);
Iterable<? extends Completion> getCompletions(Element element,
AnnotationMirror annotation,
ExecutableElement member,
String userText);
}
2.4自定义注解处理器
我们可以自定义注解处理器,获取我们自定义的注解信息,通过注解信息生成java文件或进行其它的一些操作。但是我们自定义的注解处理器并不是直接实现Processor接口,而是实现它的抽象类AbstractProcessor。
下面我们简单的实现一个自定义注解处理器,简单的用来实现在编译期通过注解处理器获取被自定义注解标注的方法信息。
(1)定义一个注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface MethodTest {
}
(2)定义一个注解处理器;
@SupportedAnnotationTypes("test.java.lang.annotation.MethodTest")
@SupportedSourceVersion(value = SourceVersion.RELEASE_8)
public class MyMethodTestProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
System.out.println("start annotation process .");
for (TypeElement typeElement : annotations){
System.out.println(typeElement+","+typeElement.getSimpleName());
}
System.out.println(roundEnv);
return true;
}
}
@SupportedAnnotationTypes(“test.java.lang.annotation.MethodTest”):标明当前注解处理器能够处理的注解;
@SupportedSourceVersion(value = SourceVersion.RELEASE_8):标明编译版本。
(3)编译主类,使用注解;
public class MyMethodTest {
public static void main(String[] args){
System.out.println("main start .");
method();
}
@MethodTest
public static void method(){
System.out.println("method start .");
}
}
(4)指定processor
勾选Idea中setting->Build,Execution,Deployment->Compiler->Annotation Processors中的Enable annotation processing(我没有试过)
也可以通过以下几种方式指定Processor:
- 直接使用编译参数指定;例如:javac -processor test.java.lang.annotation.MyMethodTest.java
- 通过服务注册指定,就是META-INF/services/javax.annotation.processing.Processor文件中添加test.java.lang.annotation.MyMethodTestProcessor;
- 通过配置Maven的编译插件;如下:
<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>
<encoding>UTF-8</encoding>
<annotationProcessors>
<annotationProcessor>
test.java.lang.annotation.MyMethodTestProcessor
</annotationProcessor>
</annotationProcessors>
</configuration>
</plugin>
值得注意的是,以上三点起效的前提是test.java.lang.annotation.MyMethodTestProcessor注解处理器是已经被编译过了。否则汇报错:
[ERROR] Bad service configuration file, or exception thrown while
constructing Processor object: test.java.lang.annotation.MyMethodTestProcessor:
Provider test.java.lang.annotation.MyMethodTestProcessor not found
我是直接使用编译参数指定的方式来制定Processor,结果如下:
可以看到,我们的注解处理器在编译期起到作用生效了。