引言
自从Java 引入了注解(Annotation) 的特性以后,我们获得了由它带来的便利,尤其是在Spring当中得到了大量的应用。大部分情况下使用的注解都是运行时通过反射机制来使用它,今天我们不讨论那些在运行时(Runtime)通过反射机制运行处理的注解,而是讨论在编译时(Compile time)处理的注解。
注解处理器(Annotation Processor)是javac的一个工具,它用来在编译时扫描和处理注解(Annotation)。我们可以自定义注解,并注册相应的注解处理器来完成相应的操作,比如大家非常熟悉的lombok就是采用该机制实现的。
AbstractProcessor
每一个处理器都是继承于AbstractProcessor,如下所示:
public class MyProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment env){ }
@Override
public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }
@Override
public Set<String> getSupportedAnnotationTypes() { }
@Override
public SourceVersion getSupportedSourceVersion() { }
}
init(ProcessingEnvironment env) : 每一个注解处理器类都必须有一个空的构造函数。这里有一个特殊的init()方法,它会被注解处理工具调用,并输入ProcessingEnviroment 参数。ProcessingEnviroment 提供很多有用的工具类Elements , Types 和Filer 。process(Set<? extends TypeElement> annotations, RoundEnvironment env) : 这相当于每个处理器的主函数main()。在这里写我们的扫描、评估和处理注解的代码,以及生成Java文件。输入参数RoundEnviroment ,可以让我们查询出包含特定注解的被注解元素。getSupportedAnnotationTypes() : 这里指定这个注解处理器是处理哪些注解的。注意,它的返回值是一个字符串的集合。getSupportedSourceVersion() : 用来指定我们使用的Java版本。通常这里返回SourceVersion.latestSupported() 。
在Java 7以后,我们也可以使用注解来代替getSupportedAnnotationTypes() 和getSupportedSourceVersion() ,像这样:
@SupportedSourceVersion(SourceVersion.latestSupported())
@SupportedAnnotationTypes({
// 合法注解全名的集合
})
public class MyProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment env){ }
@Override
public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }
}
注册处理器
我们可能会问如何让处理器MyProcessor 注册到javac中。首先我们必须提供一个.jar文件。就像其他.jar文件一样,打包我们的注解处理器到此文件中。并且,在我们的jar中需要打包一个特定的文件javax.annotation.processing.Processor 到META-INF/services 路径下。所以,我们的.jar文件看起来就像下面这样: MyProcessor.jar ----com --------example ------------MyProcessor.class ----META-INF --------services ------------javax.annotation.processing.Processor
com.example.MyProcessor
把MyProcessor.jar 放到我们的buildpath中,javac会自动检查和读取javax.annotation.processing.Processor 中的内容,并且注册MyProcessor 作为注解处理器。
上面注册处理器是不是特别的麻烦呢?有没有简单的方式,比如利用一个annotation来搞定?Google就为我们提供了一个这样的annotation:@AutoService(Processor.class)
@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {
}
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
<version>1.0.1</version>
</dependency>
在代码的第一行加上@AutoService(Processor.class) ,它就能自动帮我们生成META-INF/services/javax.annotation.processing.Processor 文件
举个栗子:
一、定义一个annotation:
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE, ElementType.FIELD})
public @interface FieldConstant {
}
二、定义一个annotation处理器:
@AutoService(Processor.class)
public class FieldConstantProcessor extends AbstractProcessor {
// 主要是输出信息
private Messager messager;
private JavacTrees javacTrees;
private TreeMaker treeMaker;
private Names names;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
System.out.println("process");
this.messager = processingEnv.getMessager();
this.javacTrees = 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) {
System.out.println("process");
// 拿到被注解标注的所有的类
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(FieldConstant.class);
//如果是类,则对所有的属性进行修改
for (Element element : elements) {
System.out.println("element is " + element.getSimpleName().toString());
}
return false;
}
/**
* 本处理器想要处理的注解类型的合法全称
* @return
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
return new HashSet<String>(Arrays.asList(FieldConstant.class.getCanonicalName()));
}
/**
* 用来指定使用的Java版本
* @return
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}
三、定义一个测试类:
@FieldConstant
public class Test {
/**
* 创建时间
*/
private Date createTime;
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
}
public class Main {
public static void main(String[] args) {
Test test = new Test();
System.out.println(test);
}
}
通过maven编译Main类,看一下编译的输出:
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ sample ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 2 source files to D:\code\sample\target\classes
init
process
element is Test
process
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
四、问题:
为什么process会打印了两次呢?
|