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
12
dependencies {
// 背后的服务 能够监听 你是否在编译中.....
// 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
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
// 获取被 ARouter注解的 "类节点信息"
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ARouter.class);
for(Element element : elements) {
// 动态生成java代码
// 获取组件class的包路径
String packageName = elementTool.getPackageOf(element).getQualifiedName().toString();
// 获取注解的类名
String className = element.getSimpleName().toString();
// 获取到注解以及注解时的参数
ARouter aRouter = element.getAnnotation(ARouter.class);
String path = aRouter.path();

/**
模板:
public class MainActivity3$$$$$$$$$ARouter {
public static Class findTargetClass(String path) {
return path.equals("/app/MainActivity3") ? MainActivity3.class : null;
}
}
*/
// 1、方法
MethodSpec method = MethodSpec.methodBuilder("findTargetClass")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(Class.class)
.addParameter(String.class, "path")
.addStatement("return path.equals($S) ? $T.class : null",
path,
ClassName.get((TypeElement) element) // 获取注解类的class对象
)
.build();
// 2 类
TypeSpec mineClass = TypeSpec.classBuilder(className + "$$$$$$$$$ARouter")
.addModifiers(Modifier.PUBLIC)
.addMethod(method)
.build();
// 3 包
JavaFile minePackage = JavaFile
.builder(packageName, mineClass)
.build();

try {
minePackage.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
messager.printMessage(Diagnostic.Kind.NOTE, "创建" + className + "类失败");
}
}

此时我们就完成了自动获取注解类的包名、类名、注解时的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;
}
}