APT注解处理器
APT
APT(Annotation Processing Tool) 是一种处理注释的工具,它对源代码文件进行检测找出
其中的Annotation,根据注解自动生成代码,如果想要自定义的注解处理器能够正常运行,必须要通过
APT工具来进行处理。 也可以这样理解,只有通过声明APT工具后,程序在编译期间自定义注解解释器
才能执行。 通俗理解:根据规则,帮我们生成代码、生成类文件
在使用APT在项目编译时期动态生成我们需要的java文件时,有两种方式,
传统方式、javapoet
项目源码(https://github.com/wangchongwei/apt)
javapoet
以面向对象(OOP)思维,在编译时,动态生成java文件
优点:OOP思维,不需要导包
缺点:倒序写法,从函数->类->包
这种方式和写作思维不同,从调用链尾部到头部。
开源组件butterknife、ARouter都是使用javapoet方式
example:
新建一个工程,
并在工程新建一个javaLib,在此工程新建一个注解类1
2
3
4
5
6
7
8
9@Target(ElementType.TYPE) // 表明注解是添加在类上
@Retention(RetentionPolicy.CLASS) // 表明在编译器执行
public @interface ARouter {
String path(); // 声明注解时 必须参数
String group() default ""; // 因为已经设置了default值,所以这个是选填参数
}
此时我们就完成了一个注解的定义
然后再新建一个javaLib,在此工程新建一个处理注解的类,而这个类就是我们在编译时生成java类的主要代码部分
在此工程build.gradle中添加依赖1
2
3
4
5
6
7
8
9
10
11
12dependencies {
// 背后的服务 能够监听 你是否在编译中.....
// AS3.4.1 + Gradle 5.1.1 + auto-service:1.0-rc4
compileOnly'com.google.auto.service:auto-service:1.0-rc4'
annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'
// 帮助我们通过类调用的形式来生成Java代码 [JavaPoet]
implementation "com.squareup:javapoet:1.9.0"
// 依赖注解module
implementation project(":annotations")
}
然后我们开始写监听到注解时需要生成java类的部分1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95@AutoService(Processor.class) // 启用服务 google 自动服务,一直监控
@SupportedAnnotationTypes({"com.justin.annotationprocessor.ARouter"}) // 注解 包名.类名
@SupportedSourceVersion(SourceVersion.RELEASE_8) // 环境的版本
@SupportedOptions("student") // 接收在app/build.gradle中声明的参数
public class ARouterProcessor extends AbstractProcessor {
private Elements elements;
private Messager messager;
private Filer filer;
private Types typeTool;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
elements = processingEnvironment.getElementUtils();
messager = processingEnvironment.getMessager();
filer = processingEnvironment.getFiler();
typeTool = processingEnvironment.getTypeUtils();
// 获取在app/build.gradle中申明的参数
String value = processingEnvironment.getOptions().get("student");
messager.printMessage(Diagnostic.Kind.NOTE, "=========>" + value);
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
// 此时会运行两次,一次执行,一次检查
messager.printMessage(Diagnostic.Kind.NOTE, "=====> compiler is running");
if(set.isEmpty()) {
// 使用注解的类集合为空
return false;
}
// 获取被 ARouter注解的 "类节点信息"
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ARouter.class);
for(Element element : elements) {
/**
举例我们需要生成这样一个类
package com.example.helloworld;
public final class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, JavaPoet!");
}
public int add(int a, int b){
return 5;
}
}
*/
// javapoet生成java文件的方式时,OOP思维,先生成函数,再生成类,最后生成包
// 1 生成函数
MethodSpec methodSpec = MethodSpec.methodBuilder("main") // 构造一个函数, 函数名
.addModifiers(Modifier.PUBLIC, Modifier.STATIC) // public 、static
.returns(void.class) // 返回值类型
.addParameter(String[].class, "args") // 入参类型
.addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")// 函数执行语句 不需要添加分号
.addStatement("$T.out.println($S)", System.class, "Hello, agent!")
.build();
// 多个函数
MethodSpec methodSpec1 = MethodSpec.methodBuilder("add")
.addModifiers(Modifier.PUBLIC)
.returns(int.class)
.addParameter(int.class, "a")
.addParameter(int.class, "b") // 可以添加多个入参
.addStatement("return 5")
.build();
// 2 生成类
TypeSpec myClass = TypeSpec.classBuilder("HelloWorld") // 构造一个类,类名
.addModifiers(Modifier.PUBLIC, Modifier.FINAL) // 添加申明 public、final
.addMethod(methodSpec) // 绑定函数
.addMethod(methodSpec1) // 绑定多个函数
.build();
// 3、生成包
JavaFile myPackage = JavaFile.builder("com.example.helloworld", myClass).build();
try {
myPackage.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
messager.printMessage(Diagnostic.Kind.NOTE, "=====> 创建HelloWorld类失败,异常原因:" + e.getMessage());
}
}
return false;
}
}
最后使用@ARouter注解
在MainActivity中使用1
2
3
4
5
6
7
8
9@ARouter(path = "main/MainActivity")
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
点击build项目,即会在app/build/ap_generated_sources/debug下生成对应的包以及java文件
在上述方式中,我们只是写成了一个固定的java文件,还没有体现动态生成概念。
下面我们就要改造,动态接收参数来生成代码
只显示动态生成代码部分
1 | // 获取被 ARouter注解的 "类节点信息" |
此时我们就完成了自动获取注解类的包名、类名、注解时的path
自动生成的类也在相同包名下,获取注解时的path,可以做一些操作
传统方式生成java文件
在编译时,一行一行,从头到尾,以写文本的方式写一个java文件
优点:套版格式,思路清晰
缺点:所有代码都需要写,包括导包,如果有任一一个字符错误、分号缺失,都会导致报错。
这种方式需要及其细心
开源组建EventBus就是采用的这种方式
example:
首先自定义注解类:1
2
3
4
5
6@Target(TYPE)
@Retention(CLASS) // 编译期 XUtil==运行期
public @interface Binding {
String router();
}
然后写注解处理类1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56@SupportedSourceVersion(SourceVersion.RELEASE_7) // 环境的版本
@AutoService(Processor.class) // 启用服务
@SupportedAnnotationTypes({"com.derry.arouter_annotations.Binding"}) // 注解
public class BindingProcessor extends AbstractProcessor {
private Elements elementTool;
private Messager messager;
private Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
elementTool = processingEnvironment.getElementUtils();
messager = processingEnvironment.getMessager();
filer = processingEnvironment.getFiler();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
if(set.isEmpty()) {
return false;
}
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(Binding.class);
for (Element element : elements) {
// 获取包名
String packageName = elementTool.getPackageOf(element).getQualifiedName().toString();
String className = element.getSimpleName().toString();
messager.printMessage(Diagnostic.Kind.NOTE, "被@ARetuer注解的类有:" + className);
String findClassName = className + "$$$$$$$Binding";
Binding binding = element.getAnnotation(Binding.class);
try {
JavaFileObject javaFileObject = filer.createSourceFile(packageName + '.' + findClassName, element.getEnclosingElement());
Writer writer = javaFileObject.openWriter();
StringBuilder sb = new StringBuilder();
sb.append("package ").append(packageName).append(";\n\n");
sb.append("public class ").append(findClassName).append("{\n");
sb.append("public String findTargetRouter(){\n");
// sb.append("return " + binding.router().toString()).append(";\n");
sb.append("return \"test\";\n");
sb.append("}\n");
sb.append("}\n");
writer.write(sb.toString());
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
messager.printMessage(Diagnostic.Kind.NOTE, "创建" + findClassName + "类失败");
}
}
return false;
}
}