概念
注解处理器(Annotation Processor)是javac内置的一个用于编译时扫描和处理注解(Annotation)的工具。在源代码编译阶段,通过注解处理器,我们可以获取源文件内注解(Annotation)相关内容。
APT(Annotation Process Tool),是一种在代码编译时处理注解,按照一定的规则,生成相应的java文件,多用于对自定义注解的处理,对运行时的性能影响很小。
用途
由于注解处理器可以在程序编译阶段工作,所以我们可以在编译期间通过注解处理器进行我们需要的操作。比较常用的用法就是在编译期间获取相关注解数据,然后动态生成.java源文件(让机器帮我们写代码),通常是自动产生一些有规律性的重复代码,解决了手工编写重复代码的问题,大大提升编码效率。
注解处理器可以生成Java代码,这些生成的Java代码会组成 .java 文件,但不能修改已经存在的Java类(即不能向已有的类中添加方法)。而这些生成的Java文件,会同时与其他普通的手写Java源代码一起被javac编译。
抽象处理器 AbstractProcessor
每一个注解处理器都要继承于AbstractProcessor
import javax.annotation.processing.AbstractProcessor;
public class MyAnnotationProcessor extends AbstractProcessor
{
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment)
{
super.init(processingEnvironment);
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)
{
return false;
}
@Override
public Set<String> getSupportedAnnotationTypes()
{
return super.getSupportedAnnotationTypes();
}
@Override
public SourceVersion getSupportedSourceVersion()
{
return super.getSupportedSourceVersion();
}
}
- init(ProcessingEnvironment processingEnvironment): 每一个注解处理器类都必须有一个空的构造函数。然而,这里有一个特殊的init()方法,它会被注解处理工具调用,并输入ProcessingEnviroment参数。
ProcessingEnviroment提供很多有用的工具类Elements,Types和Filer。 - process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment): 这相当于每个处理器的主函数main()。在这里写你的扫描、评估和处理注解的代码,以及生成Java文件。
输入参数RoundEnviroment,可以让你查询出包含特定注解的被注解元素。
注意:process()函数中不能直接进行异常抛出,否则的话,运行Annotation Processor的进程会异常崩溃,然后弹出一大堆让人捉摸不清的堆栈调用日志显示.
- getSupportedAnnotationTypes(): 这里必须指定,这个注解处理器是注册给哪个注解的。注意,它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称,完整的包名+类名。
在这里定义你的注解处理器注册到哪些注解上。 - getSupportedSourceVersion(): 用来指定你使用的Java版本。通常这里返回SourceVersion.latestSupported()。然而,如果你有足够的理由只支持Java 7的话,你也可以返回SourceVersion.RELEASE_7,推荐使用前者。
在Java 7以后,你也可以使用注解来代替getSupportedAnnotationTypes()和getSupportedSourceVersion()
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes("baijunyu.com.test_annotation.AAAA")
@AutoService(Processor.class)
public class TestProcessor extends AbstractProcessor {
- @AutoService(Processor.class) :向javac注册我们这个自定义的注解处理器,这样,在javac编译时,才会调用到我们这个自定义的注解处理器方法。
AutoService这里主要是用来生成 META-INF/services/javax.annotation.processing.Processor文件的。如果不加上这个注解,那么,你需要自己进行手动配置进行注册。手动注册的方式这里不再阐述,建议直接采用@AutoService(Processor.class)进行自定义注解处理器注册,简洁方便
Gradle引入方式:
implementation 'com.google.auto.service:auto-service:1.0-rc4'
基础工具
在init()中获得如下引用:
private Logger mLogger;
private Elements mElementUtils;
private Filer mFiler;
private Types mTypeUtils;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mTypeUtils = processingEnv.getTypeUtils();
mLogger = new Logger(processingEnvironment.getMessager());
mElementUtils = processingEnvironment.getElementUtils();
mFiler = processingEnvironment.getFiler();
}
- Elements:一个用来处理Element的工具类
- Types:一个用来处理TypeMirror的工具类
- Filer :使用Filer你可以创建文件
- Messager:用于编译时在打印日志信息,在gradle console
在Android Studio使用Annotation Processor
由于Android平台是基于OpenJDK的,而OpenJDK中不包含Annotation Processor的相关代码。因此,在使用Annotation Processor时,必须在新建Module时选择Java Library,处理注解相关的代码都需要在Java Library模块下完成。
整个项目的结构
annotation模块(Java Library) 该模块存放的是我们自定义的注解,是一个Java Library
compiler模块 (Java Library) 依赖annotation模块,处理注解并自动生成代码等,同样也是Java Library
app (Android App) 依赖compiler模块,需要使用annotationProcessor依赖compiler模块
一个简单的例子:
1.创建annotation Module,类型为Java Library,在此模块的定义我自定义注解类
在annotation中自定义一个注解
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface BindView {
int value() default -1;
}
2.创建compiler Module,类型为Java Library,在此模块中定义注解处理器
gradle配置:
- 依赖google.auto.service
implementation 'com.google.auto.service:auto-service:1.0-rc4'
- 依赖annotation
implementation project(':test-annotation')
创建一个自定义Annotation Processor继承于AbstractProcessor
@AutoService(Processor.class)
public class MyAnnotationProcessor extends AbstractProcessor {
private Filer mFiler;
private Messager mMessager;
private Elements mElementUtils;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mFiler = processingEnvironment.getFiler();
mMessager = processingEnvironment.getMessager();
mElementUtils = processingEnvironment.getElementUtils();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotations = new LinkedHashSet<>();
annotations.add(BindView.class.getCanonicalName());
return annotations;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
Set<? extends Element> bindViewElements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
for (Element element : bindViewElements) {
//1.获取包名
PackageElement packageElement = mElementUtils.getPackageOf(element);
String pkName = packageElement.getQualifiedName().toString();
note(String.format("package = %s", pkName));
//2.获取包装类类型
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
String enclosingName = enclosingElement.getQualifiedName().toString();
note(String.format("enclosindClass = %s", enclosingElement));
//因为BindView只作用于filed,所以这里可直接进行强转
VariableElement bindViewElement = (VariableElement) element;
//3.获取注解的成员变量名
String bindViewFiledName = bindViewElement.getSimpleName().toString();
//3.获取注解的成员变量类型
String bindViewFiledClassType = bindViewElement.asType().toString();
//4.获取注解元数据
BindView bindView = element.getAnnotation(BindView.class);
int id = bindView.value();
note(String.format("%s %s = %d", bindViewFiledClassType, bindViewFiledName, id));
//4.生成文件
createFile(enclosingElement, bindViewFiledClassType, bindViewFiledName, id);
return true;
}
return false;
}
private void createFile(TypeElement enclosingElement, String bindViewFiledClassType, String bindViewFiledName, int id) {
String pkName = mElementUtils.getPackageOf(enclosingElement).getQualifiedName().toString();
try {
JavaFileObject jfo = mFiler.createSourceFile(pkName + ".ViewBinding", new Element[]{});
Writer writer = jfo.openWriter();
writer.write(brewCode(pkName, bindViewFiledClassType, bindViewFiledName, id));
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private String brewCode(String pkName, String bindViewFiledClassType, String bindViewFiledName, int id) {
StringBuilder builder = new StringBuilder();
builder.append("package " + pkName + ";\n\n");
builder.append("//Auto generated by apt,do not modify!!\n\n");
builder.append("public class ViewBinding { \n\n");
builder.append("public static void main(String[] args){ \n");
String info = String.format("%s %s = %d", bindViewFiledClassType, bindViewFiledName, id);
builder.append("System.out.println(\"" + info + "\");\n");
builder.append("}\n");
builder.append("}");
return builder.toString();
}
private void note(String msg) {
mMessager.printMessage(Diagnostic.Kind.NOTE, msg);
}
private void note(String format, Object... args) {
mMessager.printMessage(Diagnostic.Kind.NOTE, String.format(format, args));
}
}
注:借助Messager,我们可以在编译时在 gradle console 输出日志.
3.使用注解
app gradle配置:
annotationProcessor project(':test-compiler ')
implementation project(':test-annotation')
在app Module中使用注解
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv)
TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
rebuild一下,可以在Gradle Console窗口中看到打印结果:
据注解获取到的数据还生成了一个java文件: