- 概述
- 注解(Annotation)
- 元素(Element)
- PackageElement
- ExecutableElement
- TypeElement
- VariableElement
- kapt
- AbstractProcessor 类介绍
- init(ProcessingEnvironment env)
- getSupportedAnnotationTypes()
- getSupportedSourceVersion()
- process()
- Java SPI
- 源码分析
- 相关库
- JavaPoet
- auto-service
- 参考
概述
APT(Annotation Processing Tool) 即注解处理器,是一种注解处理工具,用来在编译期扫描和处理注解,通过注解来生成 Java 文件。
通过在编译期间调用 javac -processor
命令可以掉起注解处理器。只能生成新的源文件而不能修改已经存在的源文件。
Java API 已经提供了扫描源码并解析注解的框架,开发者可以通过继承 AbstractProcessor
类并实现 process()
方法来实现自己的注解解析逻辑。
APT 的原理是在注解了某些代码元素(如字段、函数、类等)后,在编译时编译器会检查 AbstractProcessor
的子类,并且自动调用其 process()
方法,然后将添加了指定注解的所有代码元素作为参数传递给该方法,开发者再根据注解元素在编译期输出对应的 Java 代码。
注解(Annotation)
// java.lang.annotation
/**
* The common interface extended by all annotation types.
*/
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class extends Annotation> annotationType();
}
示例:
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE, ElementType.FIELD,ElementType.PARAMETER,ElementType.METHOD})
public @interface PrintMe {
String value() default "";
}
@Retention
定义注解的生效范围,一共 3 种:
- SOURCE,只作用于源码。可以用作 Lint、APT 这种工作在源码上的工具,会在编译环节被编译器主动舍弃。
- CLASS(默认),作用于编译后的 Class 文件中,但不会在运行时被 JVM 加载。因为在编译环节中生效,如 class 编译成 dex 的过程中,就可以通过 transform 等手段加以利用。
- RUNTIME,作用于运行时,生命周期最长,可用于反射。
@Target
定义注解作用的对象,常用的有:
- FIELD 成员变量,包含枚举类型的成员值。
- METHOD 在方法上是修饰方法的返回值。
- PARAMETER 修饰方法的参数。
- TYPE 修饰类或接口。
@Inhertited
用来表明注解的继承特性。注解本身并不具备继承性,这里所说的继承指的是注解所标记的类或接口的继承关系。举个例子,注解@Foo 被定义为:
@Target(TYPE)
@Rentention(RUNTIME)
@Inhertited
public @interface Foo() {
int value() default -1;
}
@Foo(value = 2)
class A {}
class B extends A {}
public static void main(String[] args) {
Foo annotation = B.class.getAnnotation(Foo.class);
//如果去除 Foo 上的 @Inherited 注解,annotation == null
System.out.println("value: " + annotation.value());
}
元素(Element)
Represents a program element such as a package, class, or method. Each element represents a static, language-level construct (and not, for example, a runtime construct of the virtual machine).
Element 用于代表程序的一个元素,这个元素可以是包、类、接口、变量、方法等。
PackageElement
Represents a package program element. Provides access to information about the package and its members.
表示一个包程序元素。提供对有关包及其成员的信息的访问。
ExecutableElement
Represents a method, constructor, or initializer (static or instance) of a class or interface, including annotation type elements.
表示某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注释类型元素。
TypeElement
Represents a class or interface program element. Provides access to information about the type and its members. Note that an enum type is a kind of class and an annotation type is a kind of interface.
表示一个类或接口程序元素。提供对有关类型及其成员的信息的访问。注意,枚举类型是一种类,而注解类型是一种接口。
VariableElement
Represents a field, enum constant, method or constructor parameter, local variable, resource variable, or exception parameter.
表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数。
package com.java.annotation; // PackageElement
public class Circle { // TypeElement
private int i; // VariableElement
private Triangle triangle; // VariableElement
// ExecuteableElement
public Circle() {}
// ExecuteableElement
public void draw(String s) {// s: VariableElement
System.out.println(s);
}
}
kapt
kapt 也是 APT 工具的一种,使用 Kotlin 开发 Android 应用时,要在预编译阶段处理注解必须使用 kotlin-kapt 而不能使用 annotationProcessor,kotlin-kapt 不是 Android Gradle 内置插件需要额外引入。
apply plugin: 'kotlin-kapt'
kapt "com.android.databinding:compiler:$android_plugin_version"
AbstractProcessor 类介绍
// javax.annotation.processing.Processor.java
public interface Processor {
// 命令行选项
Set getSupportedOptions();
// 支持的注解
Set getSupportedAnnotationTypes();
// 支持的 Java 版本,一般返回 SourceVersion.latestSupported()
SourceVersion getSupportedSourceVersion();
// 注解处理器初始化
void init(ProcessingEnvironment processingEnv);
// 处理过程
boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv);
Iterable extends Completion> getCompletions(Element element,
AnnotationMirror annotation, ExecutableElement member, String userText);
}
- ProcessingEnvironment: 代表了注解处理器框架提供的一个上下文环境,要创建新的代码或者向编译器输出信息或者获取其他工具类等都需要用到这个实例变量。
- RoundEnvironment: 提供了访问到当前这个 Round 中语法树节点的功能。
// javax.annotation.processing.Processor.java
public abstract class AbstractProcessor implements Processor {
/**
* Processing environment providing by the tool framework.
*/
protected ProcessingEnvironment processingEnv;
private boolean initialized = false;
protected AbstractProcessor() {}
public Set getSupportedOptions() {
SupportedOptions so = this.getClass().getAnnotation(SupportedOptions.class);
if (so == null)
return Collections.emptySet();
else
return arrayToSet(so.value());
}
public Set getSupportedAnnotationTypes() {
SupportedAnnotationTypes sat = this.getClass().getAnnotation(SupportedAnnotationTypes.class);
if (sat == null) {
if (isInitialized())
processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
"No SupportedAnnotationTypes annotation " + "found on " + this.getClass().getName() +
", returning an empty set.");
return Collections.emptySet();
}
else
return arrayToSet(sat.value());
}
public SourceVersion getSupportedSourceVersion() {
SupportedSourceVersion ssv = this.getClass().getAnnotation(SupportedSourceVersion.class);
SourceVersion sv = null;
if (ssv == null) {
sv = SourceVersion.RELEASE_6;
if (isInitialized())
processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
"No SupportedSourceVersion annotation " +
"found on " + this.getClass().getName() +
", returning " + sv + ".");
} else
sv = ssv.value();
return sv;
}
public synchronized void init(ProcessingEnvironment processingEnv) {
if (initialized)
throw new IllegalStateException("Cannot call init more than once.");
Objects.requireNonNull(processingEnv, "Tool provided null ProcessingEnvironment");
this.processingEnv = processingEnv;
initialized = true;
}
public abstract boolean process(Set extends TypeElement> annotations,
RoundEnvironment roundEnv);
public Iterable extends Completion> getCompletions(Element element,
AnnotationMirror annotation,
ExecutableElement member,
String userText) {
return Collections.emptyList();
}
protected synchronized boolean isInitialized() {
return initialized;
}
private static Set arrayToSet(String[] array) {
assert array != null;
Set set = new HashSet(array.length);
for (String s : array)
set.add(s);
return Collections.unmodifiableSet(set);
}
}
init(ProcessingEnvironment env)
初始化方法,会被处理器调用。ProcessingEnvironment 包含了很多有用的工具类,列如 Elements、Types、Filter 等。
- getElementUtils():处理 Element 的工具类,用于获取程序的元素,例如包、类、方法。
- getTypeUtils():处理 TypeMirror 的工具类,用于取类信息
- getFiler():文件工具
- getMessager():错误处理工具
getSupportedAnnotationTypes()
指定处理器能够处理的注解类型。
@Override public Set getSupportedAnnotationTypes() {
Set supportedTypes = new HashSet<>(1);
supportedTypes.add(Print.class.getCanonicalName());
return supportedTypes;
}
getSupportedSourceVersion()
指定处理器使用的 Java 版本。一般是 SourceVersion.latestSupported()
。
process()
抽象方法,实现注解处理器的具体逻辑。获取被注解的元素,生成 Java 源代码等信息。
@Override public boolean process(Set extends TypeElement> set, RoundEnvironment env) {
Set extends Element> elements = env.getElementsAnnotatedWith(PrintMe.class);
//由于编译器的输出无法打印到控制台,使用 javapoet 库把需要输出的信息写入到一个新的类中
for (Element element : elements) {
PrintMe annotation = element.getAnnotation(PrintMe.class);
...
}
return false;
}
返回值表示注解是否由当前 Processor 处理。如果返回 true,则这些注解由此注解来处理,后续其它的 Processor 无需再处理它们;如果返回 false,则这些注解未在此 Processor 中处理并,那么后续 Processor 可以继续处理它们。
Java SPI
SPI 全称 Service Provider Interface,是 Java 提供的一套用来被第三方实现或者扩展的 API,它可以用来启用框架扩展和替换组件。
Java SPI 实际上是基于
接口的编程+策略模式+配置文件
组合实现的动态加载机制。
为某个接口寻找服务实现的机制。
使用 Java SPI,需要遵循如下约定:
- 当服务提供者提供了接口的一种具体实现后,在 jar 包的 META-INF/services 目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名;
- 接口实现类所在的 jar 包放在主程序的 classpath 中;
- 主程序通过 java.util.ServiceLoder 动态装载实现模块,它通过扫描 META-INF/services 目录下的配置文件找到实现类的全限定名,把类加载到 JVM;
- SPI 的实现类必须携带一个不带参数的构造方法;
Java 项目示例:
└─src
├─com
│ └─spi
│ IInterface.java
│ ImplA.java
│ ImplB.java
│ Main.java
│
└─resources
└─META-INF
└─services
com.spi.IInterface
com.spi.IInterface 文件内容为:
com.spi.ImplA
com.spi.ImplB
public interface IInterface {
String name();
}
public class ImplA implements IInterface {
@Override
public String name() {
return "ImplA";
}
}
public class ImplB implements IInterface {
@Override
public String name() {
return "ImplB";
}
}
public class Main {
public static void main(String[] args) {
ServiceLoader serviceLoader = ServiceLoader.load(IInterface.class);
for (IInterface service : serviceLoader) {
System.out.println(service.name());//输出: ImplA ImplB
}
}
}
//文件 com.spi.IInterface 包含如下内容
com.spi.ImplA
com.spi.ImplB
源码分析
public final class ServiceLoader implements Iterable {
private static final String PREFIX = "META-INF/services/";
// The class or interface representing the service being loaded
private final Class service;
// The class loader used to locate, load, and instantiate providers
private final ClassLoader loader;
// The access control context taken when the ServiceLoader is created
private final AccessControlContext acc;
// Cached providers, in instantiation order
private LinkedHashMap providers = new LinkedHashMap<>();
// The current lazy-lookup iterator
private LazyIterator lookupIterator;
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
private ServiceLoader(Class svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
private int parseLine(Class> service, URL u, BufferedReader r, int lc, List names) {
String ln = r.readLine();
if (ln == null) {
return -1;
}
int ci = ln.indexOf('#');
if (ci >= 0) ln = ln.substring(0, ci);
ln = ln.trim();
int n = ln.length();
if (n != 0) {
// 检查文件,不符合抛异常
if (!providers.containsKey(ln) && !names.contains(ln))
names.add(ln);
}
return lc + 1;
}
private Iterator parse(Class> service, URL u) throws ServiceConfigurationError {
ArrayList names = new ArrayList<>();
InputStream in = u.openStream();
BufferedReader r = new BufferedReader(new InputStreamReader(in, "utf-8"));
int lc = 1;
while ((lc = parseLine(service, u, r, lc, names)) >= 0);
return names.iterator();
}
public Iterator iterator() {
return new Iterator() {
public boolean hasNext() {
return lookupIterator.hasNext();
}
public S next() {
return lookupIterator.next();
}
};
}
public static ServiceLoader load(Class service, ClassLoader loader) {
return new ServiceLoader<>(service, loader);
}
// Private inner class implementing fully-lazy provider lookup
private class LazyIterator implements Iterator {
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
private S nextService() {
String cn = nextName;
nextName = null;
Class> c = Class.forName(cn, false, loader);
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
}
}
}
- ServiceLoader.load() 方法创建 ServiceLoader 对象;
- 通过 foreach 遍历 ServiceLoader 时调用 ServiceLoader#iterator() 方法;
- 实际是通过私有内部类 LazyIterator 代理遍历解析 Service;
- 解析过程是找到 META-INF 目录下的接口文件,打开该文件流,一行一行的读取,获取实现类字符串,保存到 names 集合中;
- 遍历 names 集合,通过反射实例化实例,并保存在 providers 缓存中;
如果使用 AutoService 需要添加如下依赖:
dependencies {
implementation 'com.google.auto.service:auto-service:1.0'
annotationProcessor 'com.google.auto.service:auto-service:1.0'
}
相关库
JavaPoet
JavaPoet 是 square 开源的 Java 代码生成框架,可以很方便地通过其提供的 API 来生成指定格式(修饰符、返回值、参数、函数体等)的代码。
依赖: com.squareup:javapoet:1.11.1
auto-service
auto-service 是由 Google 开源的注解处理器。
依赖: com.google.auto.service:auto-service:1.0
参考
[1] Processor
[2] Annotation Types - jls
[3] 深入理解 Java 注解处理器 APT
[4] 使用 Google 开源库 AutoService 进行组件化开发