基本概念
Java 注解(Annotation)分为两类:编译时(Compile time)处理的注解和在运行时(Runtime)通过反射机制运行处理的注解。本文将重点介绍在编译时(Compile time)处理的注解,关于在运行时(Runtime)通过反射机制运行处理的注解,比较简单这里不做介绍大家可以自行找资料学习。
注解处理器(Annotation Processor)是javac的一个工具,它用来在编译时扫描和处理注解(Annotation). 你可以自定义注解, 在编译期生成新的Java文件。
在Java 5首次引入注解的时候, 注解处理器的API还没有成熟, 也没有标准化. 处理注解需要一个名为apt(也就是Annotation Processor Tool, 注解处理器工具)的独立的工具, 以及包含在com.sum.mirror包中的Mirror API. apt需要使用Mirror API来自定义处理器.
从Java 6开始,注解处理器通过JSR 269已经标准化并被纳入到标准库中,APT工具也被无缝集成到Java编译工具javac里面。Java 6提供了一个已经实现通用功能的抽象类javax.annotation.processing.AbstractProcessor,同时还提供了javax.lang.model包。
注解处理器API
如果我们要自己开发一个注解处理器,需要继承于AbstractProcessor,如下所示:
package com.bytebeats.apt;
public class ExampleProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment env){ }
@Override
public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotataions = new LinkedHashSet<String>(2);
annotataions.add(Factory.class.getCanonicalName());
return annotataions;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}
其中,
- init(ProcessingEnvironment env):每一个注解处理器类都必须有一个空的构造函数。init()方法会被注解处理工具调用,并输入ProcessingEnviroment参数。ProcessingEnviroment提供很多有用的工具类Elements, Types和Filer。
- process(Set<? extends TypeElement> annotations, RoundEnvironment
env):这是最重要的一个方法,你在这里写你的扫描、评估和处理注解的代码,以及生成Java文件。输入参数RoundEnviroment,可以让你查询出包含特定注解的被注解元素。 - getSupportedAnnotationTypes():该注解处理器是注册到哪些注解上。它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称。
- getSupportedSourceVersion():用来指定你使用的Java版本。通常这里返回SourceVersion.latestSupported()。然而,如果你有足够的理由只支持Java 7的话,你也可以返回SourceVersion.RELEASE_7。
注意:
在Java 6及以上版本中,getSupportedAnnotationTypes()方法和getSupportedSourceVersion()方法分别可以通过@SupportedAnnotationTypes注解和@SupportedSourceVersion注解来替换,如下:
package com.bytebeats.apt;
@SupportedAnnotationTypes({ "com.bytebeats.apt.Factory" })
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class ExampleProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment env){ }
@Override
public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }
}
实现一个注解处理器
1. 定义注解
package com.bytebeats.apt;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Ricky Fung
* @date 2016-12-28 11:26
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.CLASS)
public @interface Bean {
String id() default "";
}
Retention注解有一个属性value, 接受RetentionPolicy类型的值, RetentionPolicy是一个枚举类型, 有3个值: SOURCE, CLASS, RUNTIME
* RetentionPolicy.SOURCE: 表示编译时注解的信息会被编译器抛弃,不会留在class文件中,
注解的信息只会留在源文件中;
* RetentionPolicy.CLASS: 表示编译时注解的信息被保留在class文件(字节码文件)中,但不会被虚拟机读取在运行的时候;
* RetentionPolicy.RUNTIME: 表示编译时注解的信息被保留在class文件(字节码文件)中,并且会被虚拟机保留在运行时, 可以用反射的方式读取;
2. 编写注解处理器
自定义处理器都需要继承于AbstractProcessor,如下所示:
package com.bytebeats.apt;
import com.google.auto.service.AutoService;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* 注解处理器
*
* @author Ricky Fung
* @date 2016-12-28 11:19
*/
public class BeanProcessor extends AbstractProcessor {
private Types typeUtils;
private Elements elementUtils;
private Filer filer;
private Messager messager;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
typeUtils = processingEnv.getTypeUtils();
elementUtils = processingEnv.getElementUtils();
filer = processingEnv.getFiler();
messager = processingEnv.getMessager();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotataions = new LinkedHashSet<String>(2);
annotataions.add(Factory.class.getCanonicalName());
return annotataions;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (Element element : roundEnv.getElementsAnnotatedWith(Bean.class)) {
if (element.getKind() != ElementKind.CLASS) {
error(element, "Only classes can be annotated with @%s", Factory.class.getSimpleName());
return true;
}
}
return false;
}
/**log**/
private void error(Element e, String msg, Object... args) {
messager.printMessage(
Diagnostic.Kind.ERROR,
String.format(msg, args),
e);
}
}
在init()中我们获得如下引用:
- Elements:一个用来处理Element的工具类;
- Types:一个用来处理TypeMirror的工具类;
- Filer:正如这个名字所示,使用Filer你可以创建文件;
在注解处理过程中,我们扫描所有的Java源文件。源代码的每一个部分都是一个特定类型的Element。换句话说:Element代表程序的元素,例如包、类或者方法。每个Element代表一个静态的、语言级别的构件。
Element代表的是源代码,它的子类有这些:
- PackageElement:包名
- TypeElement:类
- VariableElement:变量
- ExecutableElement:方法
通过一个简单的java类来说明:
package com.bytebeats; // PackageElement
public class Car { // TypeElement
private double price; // VariableElement
private Wheel wheel; // VariableElement
public Car () {} // ExecuteableElement
public void setWheel ( // ExecuteableElement
Wheel wheel // TypeElement
) {
this.wheel = wheel;
}
}
此时必须换个角度来看源代码,它只是结构化的文本,它不是可运行的。你可以想象它就像你将要去解析的XML文件一样(或者是编译器中抽象的语法树)。就像XML解释器一样,有一些类似DOM的元素。你可以从一个元素导航到它的父或者子元素上。
3. 注册注解处理器
如何注册你的处理器到javac中? 在当前项目中的resources/META-INF/services目录需要新建一个特殊的文件javax.annotation.processing.Processor,文件里的内容就是声明你的处理器。
最终打包生成的.jar文件目录结构如下:
- com
- - bytebeats
- - - apt
- - - - FactoryProcessor
- META-INF
- - services
- - - javax.annotation.processing.Processor
javax.annotation.processing.Processor文件的内容是注解处理器的合法的全名列表,每一个元素换行分割:
另外,使用google的auto-service可以自动生成META-INF/services/javax.annotation.processing.Processor文件, 如下:
@AutoService(Processor.class)
public class FactoryProcessor extends AbstractProcessor {
private Types typeUtils;
private Elements elementUtils;
private Filer filer;
private Messager messager;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
typeUtils = processingEnv.getTypeUtils();
elementUtils = processingEnv.getElementUtils();
filer = processingEnv.getFiler();
messager = processingEnv.getMessager();
}
@Override
public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) {
}
}
需要增加Maven依赖:
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
<version>1.0-rc2</version>
</dependency>