注解处理器通常可以用在模块间解藕、自动生成代码等地方,比如router路由或者butterknife。效果就是我们在某些地方标注某些注解,在编译时,注解处理器会扫描这些注解的地方,然后生成一些代码,这样做可以实现全局的一些自动化功能,并不用开发者感知,非常方便,有点类似gradle插件。
这里以一个demo演示下通常需要怎么实现注解处理器的功能。
demo的功能很简单:通过注解的方式,自动生成接口和实现类的注册关系,在主项目里可以直接调用接口方法,不用注册。
首先在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')
}
首先引入相关依赖:
然后定义处理类,处理类要继承自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环境对象,获取一些功能对象。
@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输出log
messager.printMessage(Diagnostic.Kind.NOTE, "Interface: " + interName + " Impl: " + implName);
}
}
//生成代码
generate();
}
return true;
}
注释已经解释的很清楚,大概分几步:
private void generate() {
//private constructor
MethodSpec cons = MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build();
//static map
ParameterizedTypeName 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 init
FieldSpec init = FieldSpec.builder(Boolean.class, "isInit", Modifier.PRIVATE, Modifier.STATIC).initializer("false").build();
//static getService
MethodSpec.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);
//statement
getServiceBuilder.beginControlFlow("if(!isInit)");
generateInitStatement(getServiceBuilder).addStatement("isInit=true").endControlFlow();
getServiceBuilder.addStatement("return (B) services.get(clazz)");
//class
TypeSpec typeSpec = TypeSpec.classBuilder("ServiceManager")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addField(init)
.addField(map)
.addMethod(cons)
.addMethod(getServiceBuilder.build())
.build();
//file
JavaFile javaFile = JavaFile.builder("com.util.service", typeSpec).build();
try {
javaFile.writeTo(filer);
} catch (IOException e) {
}
}
生成代码主要是使用javapoet提供的功能:
这里注册代码是通过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 {
@Override
public void doFun() {
System.out.println("doFun");
}
}
@ByteService(clazz = IService2.class)
public class Service2Impl implements ITest, IService2 {
@Override
public void doTest() {
System.out.println("doTest");
}
@Override
public 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);
}
}