美文网首页
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在编译期间生成代码

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