Android AnnotationProcessor
- 一.项目结构
- 二.定义注解
- 三.实现注解处理器
- (一)依赖
- (二)注解处理器
- (三)处理注解
- 四.使用注解处理器
- (一)依赖
- (二)使用注解
- (三)生成的代码
- 五.注意事项
注解处理器通常可以用在模块间解藕、自动生成代码等地方,比如router路由或者butterknife。效果就是我们在某些地方标注某些注解,在编译时,注解处理器会扫描这些注解的地方,然后生成一些代码,这样做可以实现全局的一些自动化功能,并不用开发者感知,非常方便,有点类似gradle插件。
这里以一个demo演示下通常需要怎么实现注解处理器的功能。
demo的功能很简单:通过注解的方式,自动生成接口和实现类的注册关系,在主项目里可以直接调用接口方法,不用注册。
一.项目结构
首先我们创建一个Android项目,添加两个module。
- app:项目主module
- lib_annotations:定义注解的java-module,通常会把一些功能注解,定义为一个aar
- lib_compiler:定义注解处理器的java-module,不会被打入apk,只在编译时使用
二.定义注解
首先在lib_annotations里定义我们的注解。
@Retention(RetentionPolicy.SOURCE)
@Target(value = ElementType.TYPE)
public @interface ByteService {Class<?> clazz() default Object.class;
}
这里定义了一个ByteService的注解类,有一个clazz参数用于指定对应的服务接口,默认为Object.class。
@Retention(RetentionPolicy.SOURCE)
指定注解只保留在源文件中,不会被保留到class字节码文件中:因为在编译前期通过扫描源文件就使用完了注解。
@Target(value = ElementType.TYPE)
指定该注解只能使用在类上:因为我们的功能是注册接口和实现类的关系。
三.实现注解处理器
接着在lib_compiler里定义你注解处理器。
(一)依赖
dependencies {implementation 'com.google.auto.service:auto-service:1.0-rc4'implementation 'com.squareup:javapoet:1.11.1'implementation project(':lib_annotations')
}
首先引入相关依赖:
- auto-service:用于自动识别注解处理器的功能
- javapoet:用于生成java代码的工具sdk
- lib_annotations:要使用上面定义的注解,引入本地库
(二)注解处理器
然后定义处理类,处理类要继承自AbstractProcessor类。
@AutoService(Processor.class)
public class ByteProcessor extends AbstractProcessor {}
使用@AutoService(Processor.class)
注解,会在编译时,由auto-service库在jar包的/java/main/META-INF/services/下生成一个javax.annotation.processing.Processor文件,内容就是注解处理器类的类名,也是后续编译器识别的标示。
(三)处理注解
我们来看看Processor的具体实现。
private Filer filer;
private Messager messager;
private Map<String, String> mapper = new HashMap<>();@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {super.init(processingEnvironment);filer = processingEnvironment.getFiler();messager = processingEnvironment.getMessager();
}
首先init()方法可以通过全局的ProcessingEnvironment环境对象,获取一些功能对象。
- Filer:用于生成新的java文件的对象
- Messager:用于输出log的对象
@Override
public Set<String> getSupportedAnnotationTypes() {Set<String> res = new HashSet<>();res.add(ByteService.class.getCanonicalName());return res;
}
getSupportedAnnotationTypes()方法用于返回该Processor想要接收处理的注解,要返回全路径类名,通常使用getCanonicalName()方法。该方法也可以通过在Processor类上定义SupportedAnnotationTypes注解的方式指定。
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {//获取该注解的元素Set<? extends Element> sets = roundEnvironment.getElementsAnnotatedWith(ByteService.class);if (sets != null && sets.size() > 0) {for (Element element : sets) {//每一个元素由于只能是类,所以都是TypeElement类型if (element instanceof TypeElement) {//获取定义你该注解的元素(这里是类)的全路径名称String implName = TypeName.get(element.asType()).toString();//对应的接口全路径类名String interName;try {//通过注解的clazz对象直接获取 interName = element.getAnnotation(ByteService.class).clazz().getCanonicalName();} catch (MirroredTypeException mte) {//由于调用clazz对象时,可能因为Class对象还没有被加载,所以抛异常//异常中有相关class对象的信息,直接拿到类名即可interName = TypeName.get(mte.getTypeMirror()).toString();}//如果没有定义你clazz(默认为Object),则取该类默认实现的接口if (Object.class.getCanonicalName().equals(interName)) {List<? extends TypeMirror> typeMirrors = ((TypeElement) element).getInterfaces();interName = TypeName.get(typeMirrors.get(0)).toString();}//放入map中后续生成代码mapper.put(interName, implName);//messager输出logmessager.printMessage(Diagnostic.Kind.NOTE, "Interface: " + interName + " Impl: " + implName);}}//生成代码generate();}return true;
}
注释已经解释的很清楚,大概分几步:
- 获取指定注解的所有元素
- 拿到元素的类名
- 拿到元素标注的接口的类名
- 存入map,输出log
- 开始生成代码
private void generate() {//private constructorMethodSpec cons = MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build();//static mapParameterizedTypeName mapType = ParameterizedTypeName.get(ClassName.get(Map.class), ClassName.get(Class.class), ClassName.get(Object.class));FieldSpec map = FieldSpec.builder(mapType, "services", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL).initializer("new java.util.HashMap<>()").build();//static initFieldSpec init = FieldSpec.builder(Boolean.class, "isInit", Modifier.PRIVATE, Modifier.STATIC).initializer("false").build();//static getServiceMethodSpec.Builder getServiceBuilder = MethodSpec.methodBuilder("getService").addModifiers(Modifier.PUBLIC, Modifier.STATIC);TypeVariableName t = TypeVariableName.get("T");TypeVariableName b = TypeVariableName.get("B").withBounds(t);getServiceBuilder.addTypeVariable(t).addTypeVariable(b);getServiceBuilder.addParameter(ParameterizedTypeName.get(ClassName.get(Class.class), t), "clazz");getServiceBuilder.returns(b);//statementgetServiceBuilder.beginControlFlow("if(!isInit)");generateInitStatement(getServiceBuilder).addStatement("isInit=true").endControlFlow();getServiceBuilder.addStatement("return (B) services.get(clazz)");//classTypeSpec typeSpec = TypeSpec.classBuilder("ServiceManager").addModifiers(Modifier.PUBLIC, Modifier.FINAL).addField(init).addField(map).addMethod(cons).addMethod(getServiceBuilder.build()).build();//fileJavaFile javaFile = JavaFile.builder("com.util.service", typeSpec).build();try {javaFile.writeTo(filer);} catch (IOException e) {}
}
生成代码主要是使用javapoet提供的功能:
- MethodSpec生成方法和构造器
- FieldSpec生成字段
- TypeVariableName生成泛型定义
- ParameterizedTypeName生成类型,可以包含泛型
- TypeSpec生成类
- JavaFile生成文件
- Filer提供写入文件功能
这里注册代码是通过generateInitStatement()方法,把map里的所有对象直接插入到代码里实现的。
private MethodSpec.Builder generateInitStatement(MethodSpec.Builder getServiceBuilder) {for (Map.Entry<String, String> entry : mapper.entrySet()) {getServiceBuilder.addStatement(String.format("services.put(%s.class,new %s())", entry.getKey(), entry.getValue()));}return getServiceBuilder;
}
至此就完成了注解处理器的定义。
四.使用注解处理器
完成类注解处理器,主module就可以使用了。
(一)依赖
dependencies {...annotationProcessor project(':lib_complier')implementation project(':lib_annotations')
}
我们要使用注解,所以要引入lib_annotations库。
引入注解处理器时,使用annotationProcessor即可,在编译时,会自动识别上面说的META-INF里的类名,找到类进行注解处理器的执行。
(二)使用注解
我们定义了两个接口和两个实现类。
@ByteService
public class Service1Impl implements IService1 {@Overridepublic void doFun() {System.out.println("doFun");}
}@ByteService(clazz = IService2.class)
public class Service2Impl implements ITest, IService2 {@Overridepublic void doTest() {System.out.println("doTest");}@Overridepublic void test() {System.out.println("test");}
}
其中Service1Impl由于只有一个接口所以采用默认的注解,Service2Impl有两个接口,指定注解的clazz接口为IService2。
在我们build一次后,就可以直接调用ServiceManager使用。
ServiceManager.getService(IService1.class).doFun();
ServiceManager.getService(IService2.class).doTest();
(三)生成的代码
而注解处理器生成的代码如下。
public final class ServiceManager {private static Boolean isInit = false;private static final Map<Class, Object> services = new java.util.HashMap<>();private ServiceManager() {}public static <T, B extends T> B getService(Class<T> clazz) {if(!isInit) {services.put(com.example.byteapplication.annotation_process.IService1.class,new com.example.byteapplication.annotation_process.Service1Impl());services.put(com.example.byteapplication.annotation_process.IService2.class,new com.example.byteapplication.annotation_process.Service2Impl());isInit=true;}return (B) services.get(clazz);}
}
五.注意事项
- javapoet生成类时,需要注意import相关类,或者通过全路径类名使用参考文章
- 注解处理器也可以debug调试参考文章
- 使用java注解处理器处理不了kotlin相关的代码,可以使用kapt参考文章