编译期注解的处理技术我们也叫APT,全名Annotation Processing Tool。很多优秀的开源框架使用到的主要技术就是APT,比如GreenDao、ARouter、ButterKnife等。它可以让我们在编译时读取配置信息,直接生成Java代码,然后将生成的Java代码再次打包进行编译。生成代码的过程也用到另外一个框架,javapoet。
首先我们要继承AbstractProcessor这个类,重写它的init()和process()方法。也可以使用getSupportedAnnotationTypes()方法来代替@SupportedAnnotationTypes注解,用来指定这个注解处理器用来处理哪些注解。ProcessingEnvironment和RoundEnvironment可以用来获取一些处理环境和周边环境信息。
import com.google.auto.service.AutoService;
import com.lwh.flavors.annotation.handler.AnnotationHandler;
import com.lwh.flavors.annotation.handler.DifferenceHandler;
import com.lwh.flavors.annotation.handler.WrapperHandler;
import com.lwh.flavors.writer.JavaWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
@AutoService(Processor.class)
@SupportedAnnotationTypes(
{
"com.lwh.flavors.annotation.Difference",
"com.lwh.flavors.annotation.Wrapper"
})
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class DecorateProcessor extends AbstractProcessor {
private List<AnnotationHandler> mHandlers = new ArrayList<>();
private JavaWriter mWriter;
private Map<String, List<Element>> mElementsMap = new HashMap<>();
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
registerHandler(new DifferenceHandler());
registerHandler(new WrapperHandler());
mWriter = new JavaWriter(processingEnv);
}
protected void registerHandler(AnnotationHandler handler) {
mHandlers.add(handler);
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (AnnotationHandler handler : mHandlers) {
handler.attachProcessingEnvironment(processingEnv);
mElementsMap.putAll(handler.handleAnnotation(roundEnv));
}
mWriter.generate(mElementsMap);
return true; //处理完成了,return true就好
}
}
最终真正的处理肯定是通过AnnotationHandler,我们继承AnnotationHandler来做出具体的处理。通过调用roundEnv.getElementsAnnotatedWith()方法来获取项目中所有配置了该编译期注解的元素Element。比如TypeElement就是配置了该注解的类的一些元素信息,这些信息是编译层面的,跟运行期的对象没有关系。
import com.lwh.flavors.annotation.Difference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
public class DifferenceHandler implements AnnotationHandler {
private ProcessingEnvironment processingEnv;
@Override
public void attachProcessingEnvironment(ProcessingEnvironment env) {
this.processingEnv = env;
}
@Override
public Map<String, List<Element>> handleAnnotation(RoundEnvironment env) {
Map<String, List<Element>> annotationMap = new HashMap<>();
Set<? extends Element> elementSet = env.getElementsAnnotatedWith(Difference.class);
for (Element element : elementSet) {
TypeElement typeElement = (TypeElement) element;
String packageName = getPackageName(processingEnv, typeElement);
String className = packageName + "." + typeElement.getSimpleName().toString();
List<Element> cacheElements = annotationMap.get(className);
if (cacheElements == null) {
cacheElements = new ArrayList<>();
annotationMap.put(className, cacheElements);
}
cacheElements.add(typeElement);
}
return annotationMap;
}
private String getPackageName(ProcessingEnvironment env, Element element) {
return env.getElementUtils().getPackageOf(element).getQualifiedName().toString();
}
}
然后我们就是要使用javapoet这个框架来帮我们写代码了。
import com.lwh.flavors.MultiFlavors;
import com.lwh.flavors.annotation.Difference;
import com.lwh.flavors.annotation.Flavor;
import com.lwh.flavors.interfaces.DecoratorFactory;
import com.lwh.flavors.interfaces.IDifference;
import com.lwh.flavors.annotation.DifferenceInterface;
import com.lwh.flavors.annotation.Wrapper;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.TypeVariableName;
import com.squareup.javapoet.WildcardTypeName;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.List;
import java.util.Map;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
public class JavaWriter implements AbstractWriter {
private ProcessingEnvironment mProcessingEnv;
private Messager mMessager;
private Filer mFiler;
public JavaWriter(ProcessingEnvironment env) {
this.mProcessingEnv = env;
this.mMessager = env.getMessager();
this.mFiler = mProcessingEnv.getFiler();
}
@Override
public void generate(Map<String, List<Element>> map) {
for (Map.Entry<String, List<Element>> entry : map.entrySet()) {
List<Element> elements = entry.getValue();
for (Element element : elements) {
Difference difference = element.getAnnotation(Difference.class);
Wrapper wrapper = element.getAnnotation(Wrapper.class);
if (difference != null) {
handleAnnotation(difference, element);
}
if (wrapper != null) {
handleAnnotation(wrapper, element);
}
}
}
}
private void handleAnnotation(Difference difference, Element element) {
String proxyName = difference.proxyName();
TypeElement typeElement = (TypeElement) element;
TypeVariableName c = TypeVariableName.get("C", IDifference.class);
TypeVariableName d = TypeVariableName.get("D", IDifference.class);
MethodSpec.Builder newDecoratorMtdBuilder = MethodSpec.methodBuilder("newDecorator");
newDecoratorMtdBuilder.addModifiers(Modifier.PUBLIC);
newDecoratorMtdBuilder.addTypeVariable(c);
newDecoratorMtdBuilder.addTypeVariable(d);
newDecoratorMtdBuilder.addParameter(c, "component");
newDecoratorMtdBuilder.addParameter(ParameterizedTypeName.get(ClassName.get(Class.class), c), "componentClazz");
MethodSpec.Builder getDecoratorClassMtdBuilder = MethodSpec.methodBuilder("getDecoratorClass");
getDecoratorClassMtdBuilder.addModifiers(Modifier.PUBLIC)
.returns(ParameterizedTypeName.get(ClassName.get(Class.class), WildcardTypeName.subtypeOf(IDifference.class)));
boolean needReturnNull = false;
List<? extends AnnotationMirror> annotationMirrors = typeElement.getAnnotationMirrors();
for (AnnotationMirror annotationMirror : annotationMirrors) {
DeclaredType annotationType = annotationMirror.getAnnotationType();
Element ae = annotationType.asElement();
Flavor flavor = ae.getAnnotation(Flavor.class);
String s = ae.getSimpleName().toString();
if (flavor == null) {
continue;
}
if (proxyName.equalsIgnoreCase(s)) {
List<? extends TypeMirror> interfaces = typeElement.getInterfaces();
for (TypeMirror typeMirror : interfaces) {
Types types = mProcessingEnv.getTypeUtils();
Element e = types.asElement(typeMirror);
DifferenceInterface differenceInterface = e.getAnnotation(DifferenceInterface.class);
if (differenceInterface != null) {
newDecoratorMtdBuilder.addCode("try {\n $T constructor = getDecoratorClass().getConstructor(componentClazz);\n", Constructor.class);
newDecoratorMtdBuilder.addStatement(" constructor.setAccessible(true)");
newDecoratorMtdBuilder.addStatement(" return (D) constructor.newInstance(component)");
newDecoratorMtdBuilder.addCode("} catch($T e) {\n e.printStackTrace();\n}\n", Exception.class);
getDecoratorClassMtdBuilder.addStatement("return $T.class", ClassName
.bestGuess(differenceInterface.packageName()+"."+differenceInterface.moduleName() + s));
needReturnNull = true;
}
}
}
}
if (!needReturnNull) {
getDecoratorClassMtdBuilder.addStatement("return null");
}
newDecoratorMtdBuilder.addStatement("return null");
newDecoratorMtdBuilder.returns(d);
String packageName = MultiFlavors.getPackageName(mProcessingEnv, element);
String className = typeElement.getSimpleName().toString();
className += "$Factory";
TypeSpec typeSpec = TypeSpec.classBuilder(className)
.addSuperinterface(DecoratorFactory.class)
.addModifiers(Modifier.PUBLIC)
.addMethod(newDecoratorMtdBuilder.build())
.addMethod(getDecoratorClassMtdBuilder.build())
.build();
JavaFile javaFile = JavaFile.builder(packageName, typeSpec)
.addFileComment("These codes are generated by Dora automatically. Do not modify!")
.build();
try {
javaFile.writeTo(mProcessingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
}
private void handleAnnotation(Wrapper wrapper, Element element) {
String flavorName = wrapper.flavorName();
Types types = mProcessingEnv.getTypeUtils();
TypeElement typeElement = (TypeElement) element;
List<? extends AnnotationMirror> annotationMirrors = typeElement.getAnnotationMirrors();
for (AnnotationMirror annotationMirror : annotationMirrors) {
DeclaredType annotationType = annotationMirror.getAnnotationType();
Element ae = annotationType.asElement();
Flavor flavor = ae.getAnnotation(Flavor.class);
if (flavor == null) {
continue;
}
List<? extends TypeMirror> interfaces = typeElement.getInterfaces();
for (TypeMirror typeMirror:interfaces) {
Element interfaceElement = types.asElement(typeMirror);
DifferenceInterface differenceInterface = interfaceElement.getAnnotation(DifferenceInterface.class);
if (differenceInterface != null) {
String packageName = differenceInterface.packageName();
String moduleName = differenceInterface.moduleName();
String s = ae.getSimpleName().toString();
if (s.equalsIgnoreCase(flavorName)) {
MethodSpec methodSpec = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(ClassName.bestGuess(interfaceElement.toString()), "base")
.addStatement("super(base)")
.build();
TypeSpec typeSpec = TypeSpec.classBuilder(moduleName + s)
.addModifiers(Modifier.PUBLIC)
.superclass(ClassName.bestGuess(typeElement.toString()))
.addMethod(methodSpec)
.build();
JavaFile javaFile = JavaFile.builder(packageName, typeSpec)
.addFileComment("These codes are generated by Dora automatically. Do not modify!")
.build();
try {
javaFile.writeTo(mProcessingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
}
这些Mirror就是真正的源代码级别的镜像信息,TypeMirror、AnnotationMirror。javapoet通过MethodSpec和TypeSpec来构建方法、类这样的代码,最后通过JavaFile这个类的writeTo(filer)方法写入文件,filer知道将代码生成在哪个地方,Filer的对象可能会报红线,但是是假报错,咱们无视就好。前面无法平息因该框架太妙的激动的心情,忘了最重要一点,就是环境搭建,在最后补上。
apply plugin: 'java'
dependencies {
implementation 'com.google.auto.service:auto-service:1.0-rc2'
implementation 'com.squareup:javapoet:1.9.0'
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
}
这个注解处理器的项目需要使用annotationProcessor来依赖,kotlin项目使用kapt。
阅读量:831
点赞量:0
收藏量:0