什么是注解(编译期)——APT-灵析社区

chole

编译期注解的处理技术我们也叫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