美文网首页
APT在编译期间生成代码

APT在编译期间生成代码

作者: 资本家大恶人 | 来源:发表于2020-08-28 19:16 被阅读0次

Java注解处理器使用详解
注解处理器运行是不会被打包apk,是在运行前生成代码否则会发生jar冲突,手机系统已经存在

虚处理器AbstractProcessor

  • init(ProcessingEnvironment env):
    每一个注解处理器类都必须有一个空的构造函数。然而,这里有一个特殊的init()方法,它会被注解处理工具调用,并输入ProcessingEnviroment参数。ProcessingEnviroment提供很多有用的工具类Elements, TypesFiler。后面我们将看到详细的内容。

  • process(Set<? extends TypeElement> annotations, RoundEnvironment env):
    这相当于每个处理器的主函数main()。你在这里写你的扫描、评估和处理注解的代码,以及生成Java文件。输入参数RoundEnviroment,可以让你查询出包含特定注解的被注解元素。后面我们将看到详细的内容。

  • getSupportedAnnotationTypes():
    这里你必须指定,这个注解处理器是注册给哪个注解的。注意,它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称。换句话说,你在这里定义你的注解处理器注册到哪些注解上。

  • getSupportedSourceVersion():
    用来指定你使用的Java版本。通常这里返回SourceVersion.latestSupported()。然而,如果你有足够的理由只支持Java 6的话,你也可以返回SourceVersion.RELEASE_6。我推荐你使用前者。

利用JavaPoet创建类

 // `JavaFile` 代表 Java 文件
    JavaFile javaFile = JavaFile.builder("com.walfud.howtojavapoet",
      // TypeSpec 代表一个类
      TypeSpec.classBuilder("Clazz")
              // 给类添加一个属性
              .addField(FieldSpec.builder(int.class, "mField", Modifier.PRIVATE)
                                 .build())
              // 给类添加一个方法
              .addMethod(MethodSpec.methodBuilder("method")
                    .addModifiers(Modifier.PUBLIC)
                    .returns(void.class)
                    .addStatement("System.out.println(str)")
                    .build())
              .build())
      .build();

@AutoService(Process.class)
public class MvpProcessor extends AbstractProcessor {
    private Elements mElementsUtils;
    private Messager mMessager;
    private Filer mFiler;

    /**
     * @param processingEnvironment
     * 该方法主要用于一些初始化的操作,
     * 通过该方法的参数ProcessingEnvironment可以获取一些列有用的工具类。
     * 其内部各方法解释如下:
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
//         * 返回用来在元素上进行操作的某些实用工具方法的实现。
//         * Elements是一个工具类,可以处理相关Element(
//         * 包括ExecutableElement, PackageElement, TypeElement, TypeParameterElement, VariableElement)
        mElementsUtils = processingEnvironment.getElementUtils();//元素操作工具类
//         * 返回用来报告错误、警报和其他通知的 Messager。
        mMessager = processingEnvironment.getMessager();// 日志工具类
//         *  用来创建新源、类或辅助文件的 Filer。
        mFiler = processingEnvironment.getFiler();
//
//        返回用来在类型上进行操作的某些实用工具方法的实现。
//        Types getTypeUtils();
//
//        // 返回任何生成的源和类文件应该符合的源版本。
//        SourceVersion getSourceVersion();
//
//        // 返回当前语言环境;如果没有有效的语言环境,则返回 null。
//        Locale getLocale();
//
//        // 返回传递给注释处理工具的特定于 processor 的选项
//        Map<String, String> getOptions();

    }

    /**
     * @return
     * 返回此 Processor 支持的注释类型的名称。
     * 结果元素可能是某一受支持注释类型的规范(完全限定)名称。它也可能是 ” name.” 形式的名称
     * ,表示所有以 ” name.” 开头的规范名称的注释类型集合。最后,自身表示所有注释类型的集合,
     * 包括空集。注意,Processor 不应声明 “*”,除非它实际处理了所有文件;
     * 声明不必要的注释可能导致在某些环境中的性能下降。
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new HashSet<>();
//        1、getCanonicalName() 是获取所传类从java语言规范定义的格式输出。
//        2、getName() 是返回实体类型名称
//        3、getSimpleName() 返回从源代码中返回实例的名称。
        types.add(MvpEntity.class.getCanonicalName());
        return types;
    }

    //    返回此注释 Processor 支持的最新的源版本,该方法可以通过注解@SupportedSourceVersion指定。
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    /**
     * @param set
     * @param roundEnvironment
     * @return
     * 注解处理器的核心方法,处理具体的注解。主要功能基本可以理解为两个
     * 获取同一个类中的所有指定注解修饰的Element;
     * set参数,存放的是支持的注解类型
     * RoundEnvironment参数,可以通过遍历获取代码中所有通过指定注解(例如在ButterKnife中主要就是@BindeView等)
     * 修饰的Element对象。通过Element对象可以获取字段名称,字段类型以及注解元素的值。
     * 创建Java文件;
     * 将同一个类中通过指定注解修饰的所有Element在同一个Java文件中实现初始化,
     * 这样做的目的是让在最终依赖注入时便于操作。
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//        返回一个注解集合
// 即element是代表程序的一个元素,这个元素可以是:包、类/接口、属性变量、方法/方法形参、泛型参数。
// element是java-apt(编译时注解处理器)技术的基础,因此如果要编写此类框架,熟悉element是必须的。
        Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(MvpEntity.class);
        for (Element element : elementsAnnotatedWith) {
//            检查element类型
            if (!checkAnnotationValid(element, MvpEntity.class)) {
                return false;
            }
            TypeElement typeElement = (TypeElement) element;
//            得到该类的类名
            String className = typeElement.getSimpleName().toString();
//            得到该类包名
            String packageName = mElementsUtils.getPackageOf(element).getQualifiedName().toString();

            genneratorClass(className, packageName);
        }
        return false;
    }
//利用JavaPoet创建类
//javaPoet是一款可以自动生成Java文件的第三方依赖
    private void genneratorClass(String className, String packageName) {
//        获得父类
        ParameterizedTypeName superClass = ParameterizedTypeName.get(ClassName.get(packageName, className), TypeVariableName.get("D"));
// 利用JavaPoet创建类
        TypeSpec.Builder typeVariable = TypeSpec.classBuilder("ProxyEntity")
                .addModifiers(Modifier.PUBLIC)//创建修饰符
                .superclass(superClass)//创建父类
                .addTypeVariable(TypeVariableName.get("D"));//添加范型
//      创建该类
        TypeSpec build = typeVariable.build();

//         创建该Java文件
        JavaFile javaFile = JavaFile.builder(packageName, build).build();

        try {
//            写入该类
            javaFile.writeTo(mFiler);
        } catch (IOException e) {
            e.printStackTrace();
        }


    }
//    检查字段
    private boolean checkAnnotationValid(Element annotatedElement, Class clazz) {
//        检查注解是不是类
        if (annotatedElement.getKind() != ElementKind.CLASS) {
            return false;
        }
//        检查是不是private
        if (annotatedElement.getModifiers().contains(Modifier.PRIVATE)) {
            return false;
        }

        return true;
    }


}

你看到在代码的第一行是@AutoService(Processor.class),这是什么?这是一个其他注解处理器中引入的注解。AutoService注解处理器是Google开发的,用来生成META-INF/services/javax.annotation.processing.Processor文件的。是的,你没有看错,我们可以在注解处理器中使用注解。非常方便,难道不是么?在getSupportedAnnotationTypes()中,我们指定本处理器将处理@MvpEntity注解。

ElementsTypeMirrors
init()中我们获得如下引用:

Elements:一个用来处理Element的工具类(后面将做详细说明);
Types:一个用来处理TypeMirror的工具类(后面将做详细说明);
Filer:正如这个名字所示,使用Filer你可以创建文件。
在注解处理过程中,我们扫描所有的Java源文件。源代码的每一个部分都是一个特定类型的Element。换句话说:Element代表程序的元素,例如包、类或者方法。每个Element代表一个静态的、语言级别的构件。在下面的例子中,我们通过注释来说明这个:

我们来一步一步实现process()方法。首先,我们从搜索被注解了@MvpEntity的类开始:

    
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//        返回一个注解集合
// 即element是代表程序的一个元素,这个元素可以是:包、类/接口、属性变量、方法/方法形参、泛型参数。
// element是java-apt(编译时注解处理器)技术的基础,因此如果要编写此类框架,熟悉element是必须的。
        Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(MvpEntity.class);
// 遍历所有被注解了@Factory的元素
        for (Element element : elementsAnnotatedWith) {
//            检查element类型
            if (!checkAnnotationValid(element, MvpEntity.class)) {
                return false;
            }
            TypeElement typeElement = (TypeElement) element;
//            得到该类的类名
            String className = typeElement.getSimpleName().toString();
//            得到该类包名
            String packageName = mElementsUtils.getPackageOf(element).getQualifiedName().toString();

            genneratorClass(className, packageName);
        }
        return false;
    }

这里并没有什么高深的技术。roundEnv.getElementsAnnotatedWith(Factory.class))返回所有被注解了@Factory的元素的列表。你可能已经注意到,我们并没有说“所有被注解了@MvpEntityy的类的列表”,因为它真的是返回Element的列表。请记住:Element可以是类、方法、变量等。所以,接下来,我们必须检查这些Element是否是一个类:

//    检查字段
    private boolean checkAnnotationValid(Element annotatedElement, Class clazz) {
//        检查注解是不是类
        if (annotatedElement.getKind() != ElementKind.CLASS) {
            return false;
        }
//        检查是不是private
        if (annotatedElement.getModifiers().contains(Modifier.PRIVATE)) {
            return false;
        }

        return true;
    }

为什么要这么做?我们要确保只有class元素被我们的处理器处理。前面我们已经知道到类是用TypeElement表示。我们为什么不这样判断呢if (! (annotatedElement instanceof TypeElement) )?这是错误的,因为接口(interface)类型也是TypeElement。所以在注解处理器中,我们要避免使用instanceof,而是配合TypeMirror使用EmentKind或者TypeKind

相关文章

  • APT在编译期间生成代码

    api 依赖(可以传递依赖) 应用场景:当app 想要通过一个lib1间接依赖lib1依赖的lib2时可以通过ap...

  • APT在编译期间生成代码

    Java注解处理器使用详解注解处理器运行是不会被打包apk,是在运行前生成代码否则会发生jar冲突,手机系统已经存...

  • APT学习笔记--JavaPoet

    简介 APT(Annotation Process Tool),是一种编译期间处理注解,按照一定规则生成代码的技术...

  • Android自动生成代码的2种方式

    利用 APT 技术在编译期生成代码 简介: APT(Annotation Processing Tool 的简称)...

  • android dagger2 注入mvp架构

    dagger2简单应用用一个mvp架构来做例子apt编译时生成代码apt自动生成代码 再为dagger2提供注入 ...

  • Android APT(编译时代码生成)最佳实践

    Android APT(编译时代码生成)最佳实践 【备注】只用于个人收藏

  • 带你了解APT

    介绍 APT就是(Annotation Processing Tool )的简称,就是可以在代码编译期间对注解进行...

  • Android模块开发之APT技术

    APT,就是Annotation Processing Tool 的简称,就是可以在代码编译期间对注解进行处理,并...

  • 编译插桩

    编译插桩是指在代码编译期间修改或新增代码 可以两个地方进行编译插桩 1、java编译为class时 APT、And...

  • 安卓AOP实战:APT打造极简路由

    特点:1、0个类0行代码(除了apt及生成的仅仅一个类的代码)2、0反射0性能损耗,基于编译期间注解处理器3、支持...

网友评论

      本文标题:APT在编译期间生成代码

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