简单的演示一下如何使用autoService和javapoet编译时生成代码 并 运行时调用生成的代码
1. 创建2个java Library 的 module
在app的build.gradle里建立依赖:
dependencies {
...
annotationProcessor project(':testlib') //注解处理库
implementation project(':test_annotation') //注解库
}
2. 在test_annotation里创建注释类:
@Target(ElementType.TYPE)
//注解的生命周期
//RetentionPolicy.SOURCE 源码阶段
//RetentionPolicy.CLASS 编译阶段
//RetentionPolicy.RUNTIME 运行阶段
@Retention(RetentionPolicy.CLASS)
public @interface PrintMe{
String value();
}
因为是编译时生成代码,所以选择编译阶段
在app中使用这个注解:
@PrintMe(value = "这是个测试")
public class MainActivity extends AppCompatActivity {
...
}
为了方便反射来回调生成的类,我们再建一个BasePrint接口:
public interface BasePrint {
void printMe(String str);
}
3.在testlib里使用autoService进行注释处理
创建依赖:
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
implementation 'com.squareup:javapoet:1.11.1'
implementation project(':test_annotation')
}
使用autoService进行注释处理,新建一个PrintMeProcessor类,对注释进行处理:
public class PrintMeProcessor extends AbstractProcessor{
...
@Override
public boolean process(Set extends TypeElement> set, RoundEnvironment roundEnvironment) {
if (!Utils.isEmpty(set)) {
//获取所有被PrintMe注释的节点列表
Set extends Element> elements = roundEnvironment.getElementsAnnotatedWith(PrintMe.class);
if (!Utils.isEmpty(elements)) {
try {
System.out.println("===="+elements.toString());
//处理数据,找出注释的节点是activity的子类
dealData(elements);
//使用javapoet生成代码
genPrint();
} catch (Exception e) {
e.printStackTrace();
}
}
return true;
}
return false;
}
}
找出注释的节点是activity的子类,并获取注释里的值
private void dealData(Set extends Element> elements) {
TypeElement activity = processingEnv.getElementUtils().getTypeElement(Consts.ACTIVITY);
//节点自描述 Mirror
TypeMirror type_Activity = activity.asType();
for (Element element : elements) {
TypeMirror tm = element.asType();
PrintMe route = element.getAnnotation(PrintMe.class);
if(typeUtils.isSubtype(tm,type_Activity)){
//取得注释里的value值
value = route.value();
}
}
}
4. 使用javapoet生成代码
public void genPrint(){
//创建函数的参数
ParameterSpec parameterSpec = ParameterSpec.builder(String.class, "str")
.build();
//创建函数
//$T 代指 TypeName 类
//$N 代指变量或方法名称
//$S 代指字符串
MethodSpec methodSpec = MethodSpec.methodBuilder("printMe")
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.addParameter(parameterSpec)
.addStatement("System.out.println($S+str)",value)
.build();
//创建类
TypeSpec typeSpec = TypeSpec.classBuilder("PrintImpl")
.addModifiers(PUBLIC)
.addSuperinterface(BasePrint.class)
.addMethod(methodSpec)
.build();
//com.test 是包名
JavaFile file = JavaFile.builder("com.test", typeSpec).build();
try {
file.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
}
这里使用BasePrint的接口只是为了用反射来调用生成的这个类
5. 查看生成的代码
make project 一下,就可以在下面的路径找到生成的代码:
打开可以看见:
package com.test;
import com.test_annotation.BasePrint;
import java.lang.Override;
import java.lang.String;
public class PrintImpl implements BasePrint {
@Override
public void printMe(String str) {
System.out.println("这是个测试"+str);
}
}
6. 反射来调用生成的代码
在test_annotation中创建一个类:
public class UseNum {
public static void use(Class cls){
try {
//反射生成的类进行函数调用
((BasePrint) (Class.forName("com.test.PrintImpl").getConstructor().newInstance())).printMe(cls.getSimpleName());
} catch (Exception e) {
e.printStackTrace();
}
}
}
包名和类名可以创建一个常量类来存放,方便统一管理,这里为了简单演示就写一块了。
最后,怎么使用呢? 只要在MainActivity里调用这个方法就可以了
//这个就和butterknife的bind方法是一个原理
UseNum.use(this.getClass());
总结
- annotationProcessor 的库只有AbstractProcessor有用,里面放的其他文件在app里是无法检索到的(或者说这个库编译时有用,运行时是没用的)
- 大部分的第三方框架都是使用这种方式注入代码的。如butterknife、dragger、ARouter等
- javaPoet其实就是对apt的封装
源码地址:https://github.com/leaf-fade/Test