美文网首页
APT 的使用

APT 的使用

作者: 你怕是很皮哦 | 来源:发表于2020-07-28 10:03 被阅读0次

    简介

    APT 全称 Annotation Processing Tool,即注解处理器。更确切的说,它是 javac 的一部分,能够在编译期扫描和处理注解,并生成文件。

    那么使用 APT 有什么好处呢?

    1. 将一些通用的重复的代码通过 APT 生成,减少开发工作量,提高开发效率;
    2. 在不考虑编译期耗时的情况下,相较于在运行期通过反射处理的方式,更能提高程序运行效率。

    现在很多著名的三方库都使用了 APT 技术,比如 butterknifeARouterdagger 等。

    要使用 APT,首先得了解 AbstractProcessor.java ,所有自定义的注解处理器都需要继承这个类。

    public abstract class AbstractProcessor implements Processor {
    
        protected ProcessingEnvironment processingEnv;
        private boolean initialized = false;
    
        public Set<String> getSupportedAnnotationTypes() {
                SupportedAnnotationTypes sat = this.getClass().getAnnotation(SupportedAnnotationTypes.class);
                if  (sat == null) {
                    if (isInitialized())
                        processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
                                                                 "No SupportedAnnotationTypes annotation " +
                                                                 "found on " + this.getClass().getName() +
                                                                 ", returning an empty set.");
                    return Collections.emptySet();
                }
                else
                    return arrayToSet(sat.value());
            }
    
        public SourceVersion getSupportedSourceVersion() {
            SupportedSourceVersion ssv = this.getClass().getAnnotation(SupportedSourceVersion.class);
            SourceVersion sv = null;
            if (ssv == null) {
                sv = SourceVersion.RELEASE_6;
                if (isInitialized())
                    processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
                                                             "No SupportedSourceVersion annotation " +
                                                             "found on " + this.getClass().getName() +
                                                             ", returning " + sv + ".");
            } else
                sv = ssv.value();
            return sv;
        }
    
        public synchronized void init(ProcessingEnvironment processingEnv) {
            if (initialized)
                throw new IllegalStateException("Cannot call init more than once.");
            Objects.requireNonNull(processingEnv, "Tool provided null ProcessingEnvironment");
    
            this.processingEnv = processingEnv;
            initialized = true;
        }
    
        public abstract boolean process(Set<? extends TypeElement> annotations,
                                        RoundEnvironment roundEnv);
    
        protected synchronized boolean isInitialized() {
            return initialized;
        }
    
    }
    

    有四个重要的方法。

    1. init() 初始化方法;
    2. process() 注解处理器处理注解和生成文件的地方,一般逻辑都会写在这里;
    3. getSupportedAnnotationTypes() 返回当前注解处理器能够处理的注解信息;
    4. getSupportedSourceVersion() 返回当前注解处理器支持的版本,没有特殊要求,一般都会使用 SourceVersion.latestSupported()

    ProcessingEnvironment

    它是一个接口,通过它可以获取到配置信息和一些常用的工具类。

    public interface ProcessingEnvironment {
        // 获取配置信息
        Map<String,String> getOptions();
    
        // 打印日志的工具类,也可以用 System.out.println()
        Messager getMessager();
    
        // 创建文件的工具类
        Filer getFiler();
    
        // Element相关的工具类
        Elements getElementUtils();
    
        // Type相关的工具类
        Types getTypeUtils();
    
        // 获取源码版本
        SourceVersion getSourceVersion();
    
    }
    
    1. getOptions() 可以获取到配置信息,比如 ARouterbuild.gradle 文件中配置的 AROUTER_MODULE_NAME 信息;
    Map<String, String> options = processingEnv.getOptions();
    if (MapUtils.isNotEmpty(options)) {
        // KEY_MODULE_NAME 就是 "AROUTER_MODULE_NAME"
        moduleName = options.get(KEY_MODULE_NAME);
        generateDoc = VALUE_ENABLE.equals(options.get(KEY_GENERATE_DOC_NAME));
    }
    
    1. getMessager() 可以获取到日志工具类,通过 Messager 可以在打印一些日志信息,当然你也可以直接使用 System.out.println() 来输出日志;
    processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,  "日志信息");
    
    1. getFiler() 可以获取到 Filer 工具类,用来创建源文件,字节码文件等;
    2. getElementUtils() 可以获取到 Elements 工具类,这是一个比较有用的工具类;
    // 获取包元素
    PackageElement getPackageElement(CharSequence name);
    PackageElement getPackageOf(Element type);
    // 是否过时
    boolean isDeprecated(Element e);
    // 根据全路径获取到某个类的 TypeElement 元素,这是非常有用的
    TypeElement getTypeElement(CharSequence name);
    ...
    
    1. getTypeUtils() 获取到 Types 工具类,这是一个比较有用的工具类;
    // t1 是否是 t2 的子类
    boolean isSubtype(TypeMirror t1, TypeMirror t2);
    ...
    

    Element

    Element 是注解处理器中比较重要存在。所有经过注解处理器扫描后的元素都会被封装成 Element

    Element 是一个接口,有五个实现类,分别代表了不同类型的元素,举个栗子。

    // 1. 包,被封装为 PackageElement 
    package com.ppdai;
    
    /* 
     * 2. 类,被封装为 TypeElement
     * 3. 泛型,被封装为 TypeParameterElement
     */
    public class Example<T> {
        // 4. 变量,被分装为 VariableElement
        private int a;
    
        // 5. 方法,被封装为 ExecutableElement
        public void b() {
        }
    }
    
    1. TypeElement 一个类或接口的元素,如果注解处理器处理的对象是类或者接口,那么这个元素将被封装为 TypeElemnet
    2. Packagelement 表示包元素;
    3. VariableElement 表示变量、枚举、方法参数;
    4. ExecutableElement 表示构造函数、方法;
    5. TypeParameterElement 泛型元素。

    实践

    一般注解处理器都会由三个部分组成,compileannotationapi

    1. compile 一般编写注解处理器相关;
    2. annotation 一般编写一些注解和一些基础类,接口等;
    3. api 一般会编写暴露给上层业务的封装,工具等。

    比如ARouterButterKnife 的结构。

    ARouter.png Butterknife.png

    接下来我们一步一步实现自己的注解处理器。

    定义 Annotation

    这里仿写 ARouter@Autowired 注解。

    新建一个 java library module,并定义自己的 @Autowired 注解。

    @Retention(RetentionPolicy.SOURCE)
    @Target(ElementType.FIELD)
    public @interface Autowired {
        String name();
    
        String desc() default "";
    }
    

    它的结构如下所示。

    annotation.png

    定义 Annotation Processor

    想要在编译期对注解进行处理,并生成对应的文件,需要实现 AbstractProcessor

    新建一个 java library module,定义 AutowiredProcessor 继承自 AbstractProcessor,并重写 getSupportedAnnotationTypes() 添加对 @Autowired 注解的支持。

    public class AutowiredProcessor extends AbstractProcessor {
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnv) {
            super.init(processingEnv);
        }
    
        @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
            return false;
        }
    
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return SourceVersion.latestSupported();
        }
    
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            HashSet<String> set = new HashSet<>();
            set.add(Autowired.class.getCanonicalName());
            return set;
        }
    
    }
    

    因为每个类都可以有多个使用 @Autowired 注解的属性,这里新增一个 categories() 先对所有使用了 @Autowired 的属性进行分类,存放到 map 里面。

    // 用来存放分类后的数据
    private HashMap<TypeElement, List<Element>> map = new HashMap<>();
    
    private void categories(Set<? extends Element> set) {
        for (Element element : set) {
            TypeElement typeElement = (TypeElement) element.getEnclosingElement();
            if (map.containsKey(typeElement)) {
                map.get(typeElement).add(element);
            } else {
                List<Element> list = new ArrayList<>();
                list.add(element);
                map.put(typeElement, list);
            }
        }
    }
    

    分类后 key 是类对应的 TypeElementvalue 是当前类里面所有使用了 @Autowired 注解的属性对应的 Element

    然后新增一个 generate() 用来处理分类后的数据,并生成对应的文件。简单起见,这里只对 Activity 里面的逻辑进行了处理。

    private void generateFile() {
        for (Map.Entry<TypeElement, List<Element>> entry : map.entrySet()) {
            TypeElement typeElement = entry.getKey();
            List<Element> elementList = entry.getValue();
    
            PackageElement packageElement = elementUtils.getPackageOf(typeElement);
            // 获取包名
            String packageName = packageElement.getQualifiedName().toString();
    
            String sourceClassName = typeElement.getSimpleName().toString();
            // 定义生成的文件名
            String genClassName = String.format("%s$$ARouter$$Autowired", sourceClassName);
    
            // 方法
            MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("inject")
                    .addAnnotation(Override.class)
                    .addModifiers(Modifier.PUBLIC)
                    .addParameter(Object.class, "target")
                    .addStatement("$T inject = ($T) target", ClassName.get(typeElement), ClassName.get(typeElement));
    
            // 类名$$ARouter$$Autowired
            TypeSpec.Builder typeBuilder = TypeSpec.classBuilder(genClassName)
                    .addModifiers(Modifier.PUBLIC)
                    .addSuperinterface(ISyringe.class);
    
            TypeMirror activity = elementUtils.getTypeElement("android.app.Activity").asType();
    
            for (Element element : elementList) {
                Autowired autowired = element.getAnnotation(Autowired.class);
                String key = autowired.name();
    
                TypeMirror typeMirror = element.asType();
                String fieldName = element.getSimpleName().toString();
    
                if (typeUtils.isSubtype(typeElement.asType(), activity)) {
                    String source = "inject.getIntent()";
                    switch (typeMirror.getKind().toString()) {
                        case "BOOLEAN":
                            methodBuilder.addStatement("inject.$L = $L.getBooleanExtra($S, inject.$L)", fieldName, source, key, fieldName);
                            break;
                        case "LONG":
                            methodBuilder.addStatement("inject.$L = $L.getLongExtra($S, inject.$L)", fieldName, source, key, fieldName);
                            break;
                        // ...
                        default:
                    }
                }
            }
    
            try {
                JavaFile.builder(packageName, typeBuilder.addMethod(methodBuilder.build()).build()).build().writeTo(filer);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

    注册 Annotation Processor

    这里提供两种注册方式,手动注册和自动注册。

    手动注册

    1. src/main 下新建一个 resources 的目录;
    2. resources 下新建一个 META-INF 的目录;
    3. META-INF 下新建一个 services 的目录;
    4. services 下新建一个 javax.annotation.processing.Processor 的文件,并将要注册的 Annotation Processor 的全路径写入。

    它的结构大致是这样子的。

    注册Processor.png

    自动注册

    google 提供了 auto-service 库来简化注册过程。

    修改 build.gradle 文件,添加依赖关系。

    implementation 'com.google.auto.service:auto-service-annotations:1.0-rc7'
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7'
    

    修改 AutowiredProcessor,在类上添加注解 @AutoService 即可。

    @AutoService(Processor.class)
    public class AutowiredProcessor extends AbstractProcessor {
        // ...
    }
    

    其实 @AutoService 也是通过 Annotation Processor 来实现的。具体实现我们可以查看 AutoServiceProcessor.java 文件,它的调用链如下 process() -> processImpl() -> generateConfigFiles() ,当 @AutoService 注解处理完的时候,会调用 generateConfigFiles(), 我们可以看看 generateConfigFiles() 方法的具体实现。

    private void generateConfigFiles() {
      Filer filer = processingEnv.getFiler();
    
      for (String providerInterface : providers.keySet()) {
        // 熟悉吧,这里就是我们前面创建的 src/main/resources/META-INF/services/javax.annotation.processing.Processor
        String resourceFile = "META-INF/services/" + providerInterface;
        log("Working on resource file: " + resourceFile);
        try {
          SortedSet<String> allServices = Sets.newTreeSet();
          try {
            FileObject existingFile = filer.getResource(StandardLocation.CLASS_OUTPUT, "",
                resourceFile);
            log("Looking for existing resource file at " + existingFile.toUri());
            Set<String> oldServices = ServicesFiles.readServiceFile(existingFile.openInputStream());
            log("Existing service entries: " + oldServices);
            allServices.addAll(oldServices);
          } catch (IOException e) {
            log("Resource file did not already exist.");
          }
    
          Set<String> newServices = new HashSet<String>(providers.get(providerInterface));
          if (allServices.containsAll(newServices)) {
            log("No new service entries being added.");
            return;
          }
    
          allServices.addAll(newServices);
          log("New service file contents: " + allServices);
          FileObject fileObject = filer.createResource(StandardLocation.CLASS_OUTPUT, "",
              resourceFile);
          OutputStream out = fileObject.openOutputStream();
          ServicesFiles.writeServiceFile(allServices, out);
          out.close();
          log("Wrote to: " + fileObject.toUri());
        } catch (IOException e) {
          fatalError("Unable to create " + resourceFile + ", " + e);
          return;
        }
      }
    }
    

    定义 API

    创建一个 android library module,定义一个工具类,来调用生成 XX$$ARouter$$Autowired.javainject()

    public class PPdaiHelper {
    
        public static void inject(Object target) {
            String className = target.getClass().getName() + "$$ARouter$$Autowired";
            try {
                ISyringe iSyringe = (ISyringe) Class.forName(className).getConstructor().newInstance();
                iSyringe.inject(target);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
    }
    

    修改 app 下面的 build.gradle 文件,增加对 Annotation Processor 的使用。

    dependencies {
        //...
        implementation project(":ppdai-api")
        kapt project(":ppdai-compile")
    }
    

    做完这些,自定义的 Annotation Processor 就完成了。接下来就是验证了。简单起见,我们编写了两个 Activity,其中一个使用 @Autowired 注解进行数据传递。

    class Main2Activity : AppCompatActivity() {
    
        @Autowired(name = "id")
        @JvmField
        var id: Long = 0L
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            PPdaiHelper.inject(this)
    
            Log.d("ppdai", "id : $id")
        }
    
    }
    

    跳转 Main2Activity.java 的地方增加参数 id 的传递。

    val intent = Intent(this, Main2Activity::class.java)
    intent.putExtra("id", 1000L)
    startActivity(intent)
    

    然后编译项目,看看我们的注解处理器生成的文件。

    package com.ppdai.annotationprocessor;
    
    import com.ppdai.core.ISyringe;
    import java.lang.Object;
    import java.lang.Override;
    
    public class Main2Activity$$ARouter$$Autowired implements ISyringe {
      @Override
      public void inject(Object target) {
        Main2Activity inject = (Main2Activity) target;
        inject.id = inject.getIntent().getLongExtra("id", inject.id);
      }
    }
    

    运行代码,打开 Logcat,点击跳转,查看日志如下。

    验证.png

    传送门

    Github 项目地址
    ARouter 之@Autowired源码分析

    相关文章

      网友评论

          本文标题:APT 的使用

          本文链接:https://www.haomeiwen.com/subject/zlgbkktx.html