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包。
如果我们要自己开发一个注解处理器,需要继承于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 getSupportedAnnotationTypes() {
Set annotataions = new LinkedHashSet(2);
annotataions.add(Factory.class.getCanonicalName());
return annotataions;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}
其中,
注意:
在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) { }
}
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文件(字节码文件)中,并且会被虚拟机保留在运行时, 可以用反射的方式读取;
自定义处理器都需要继承于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 getSupportedAnnotationTypes() {
Set annotataions = new LinkedHashSet(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()中我们获得如下引用:
在注解处理过程中,我们扫描所有的Java源文件。源代码的每一个部分都是一个特定类型的Element。换句话说:Element代表程序的元素,例如包、类或者方法。每个Element代表一个静态的、语言级别的构件。
Element代表的是源代码,它的子类有这些:
通过一个简单的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的元素。你可以从一个元素导航到它的父或者子元素上。
如何注册你的处理器到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.servicegroupId>
<artifactId>auto-serviceartifactId>
<version>1.0-rc2version>
dependency>
点此下载源码:https://github.com/TiFG/annotation-in-action