美文网首页
使用Annotation Processor在编译期生成模板代码

使用Annotation Processor在编译期生成模板代码

作者: 有没有口罩给我一个 | 来源:发表于2019-02-26 11:05 被阅读0次

    1、Android注解快速入门和实用解析
    2、Annotation Processor重复造轮子ARouter
    3、编译时注解带你封装微信支付

    Annotation Processor Tool (APT)是一种处理注释的工具,它对源代码文件进行检测找出其中的Annotation,根据注解自动生成代码,如果想要自定义的注解处理能够正常运行,必须要通过APT工具来进行处理。Java 的Annotation Processor是非常有用的功能,很多常用的库和框架都使用了 Annotation Processor 来生成代码,比如Butter Knife 、ARouter等都是用来生成 代码的。

    结构化语言

     <html>
      <body>
      </body>
    </html>
    

    比如Html就是典型的Element 结构化语言,body标签不能卸载html之上,因为这些是有规定的,那么Java也是一种结构体语言:

    package com.wfy.common;   // PackageElement 包元素节点
    
    public class Order implements Serializable { // TypeElement 类或接口元素/节点
    
    int orderId = 99; //VariableElement  字段,枚举常量,方法或构造函数参数,局部变量或异常参数元素/节点
    
    @Override
    public String toString() {//ExecutableElement 方法元素/节点
        return "Order{" +
                "orderId=" + orderId +
                '}';
        }
    }
    

    java也是一种结构体语言,来说说它的各个节点类:

    • PackageElement 一个包程序元素节点;
    • TypeElement 类或接口元素节点;
    • VariableElement 字段,枚举常量,方法和构造函数参数,局部变量或异常参数元素节点;
    • ExecutableElement 方法元素节点;
    • TypeParameterElement 表示泛型类,接口,方法或构造函数元素的形式类型参数节点。

    常用API

    属性名 解释
    getEnclosedElements() 返回该Element直接包含的子元素
    getEnclosingElements() 返回该Element的父Element,与上一个方法相反
    getKind() 返回Element的类型,判断是哪种Element
    getModifiers() 获取修饰关键字,public static final等关键字
    getSimpleName() 获取Element的名字,不带包名
    getQuelfierdName() 获取全名,如果是类的话,包含完整的包名路径
    getParamters() 获取方法的参数元素,每个元素是一个VariableElement
    getRetrunType() 获取方法的返回值类型
    getConstantValue() 如果属性变量被final修饰,则可以使用该方法获取它的值

    最后我在介绍几个常用的类:

    Annotation Processor Tool (APT)

    是一种处理注解的工具,它对源代码文件进行检测找出其中的Annotation,根据注解自动生成代码,如果想要自定义注解处理器能够正常运行,必须要通过APT工具进行处理。

    本文通过如何使用注解来自动生成Music和User类,这些类并没有实际的使用意义;

    注解:

    Android Studio 中创建一个 Java module,名字为 Annotation。在这个模块中创建这个自定义的 MyAnnotation注解类:

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
        * 只在源代码中存在,编译后没有在字节码中,所以最最终的运行时是没有影响的
       *
     */
    @Target(ElementType.TYPE) //作用于类上
    @Retention(RetentionPolicy.SOURCE)//在源码可见
    public @interface MyAnnotation {
    }
    

    注解处理器

    注解处理器的功能就是用来读取代码中的注解然后来生成相关的代码。

    创建一个 Java module 名字为 “compiler”。 该模块在编译的时候,来获取哪些类使用了MyAnnotation 注解,并生成代码。该模块并不在 Android 项目中引用,只存在于编译的时候。所以这个模块的 Java 版本号可以随意指定(Java 8 、9)

    @SupportedAnnotationTypes("com.xx.lib.MyAnnotation")
    public final class MyProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment env) {
        Collection<? extends Element> annotatedElements = env.getElementsAnnotatedWith(MyAnnotation.class);
        //转换类型
        List<TypeElement> types = new ImmutableList.Builder<TypeElement>()
                .addAll(ElementFilter.typesIn(annotatedElements))
                .build();
        for (TypeElement type : types) {
            processType(type);
        }
        return true;
    }
    
    private void processType(TypeElement type) {
        String className = generatedClassName(type);
        StringBuffer sb = new StringBuffer();
        sb.append("public class " + type.getSimpleName() + "{");
        sb.append("private int age;");
        sb.append("private String name;");
        sb.append("}");
        String source = sb.toString();
        writeSourceFile(className, source, type);
    }
    
    private void writeSourceFile(String className, String text, TypeElement originatingType) {
        try {
            JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(className, originatingType);
            Writer writer = sourceFile.openWriter();
            try {
                writer.write(text);
            } finally {
                writer.close();
            }
        } catch (IOException e) {
        }
    }
    
    private String generatedClassName(TypeElement type) {
        String name = type.getSimpleName().toString();
        while (type.getEnclosingElement() instanceof TypeElement) {
            type = (TypeElement) type.getEnclosingElement();
            name = type.getSimpleName() + "_" + name;
        }
        String pkg = TypeUtil.packageNameOf(type);
        String dot = pkg.isEmpty() ? "" : ".";
        return pkg + dot + name;
    }
    }
    

    对于该类有几点要求:
    1. 需要继承至 AbstractProcessor
    2. 需要使用类的全称(包含包名)来指定其支持的注解类型(com.xx.lib.MyAnnotation)
    3. 实现 process() 函数,在该函数中来处理所支持的注解类型并生成需要的代码。

    如果没有其他处理器需要继续处理该注解,则 process() 返回 true。针对我们这个情况,只有 MyProcessor需要处理 MyAnnotation注解,所以该函数返回 true。

    注解处理器类编写完后,需要创建一个 java META_INF 文件来告诉系统具有注解处理功能。Java 代码在编译的时候,系统编译器会查找所有的 META_INF 中的注册的注解处理器来处理注解。

    在 Android studio 的 compiler 项目中创建如下目录:

    compiler/src/main/resources/META_INF/services
    

    在 services 目录下面创建一个名字为 “javax.annotation.processing.Processor” 的文本文件:

    该文件中每行一个注解处理器的全名:

    com.xx.compiler.MyProcessor
    

    注解处理器就创建好了

    在 Android Studio 中使用

    在 app/build.gradle 中添加前面创建的两个模块为依赖项:

     compileOnly project(':Annotation')
     annotationProcessor project(':compiler')
    

    注意上面 MyAnnotation项目使用的是 compileOnly 依赖,这是由于 compileOnly 中的代码只在编译的时候存在,并不会打包到最终的应用中去,所以可以使用 compileOnly 。compiler 项目为注解编译器,通过使用 插件来指定 annotationProcessor 。

    接下来就可以使用了:

      @MyAnnotation
      public class User {
      }
    

    相关文章

      网友评论

          本文标题:使用Annotation Processor在编译期生成模板代码

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