美文网首页Android开发经验谈
那些高端、优雅的注解是怎么实现的<5> --使用Annotaio

那些高端、优雅的注解是怎么实现的<5> --使用Annotaio

作者: ifjgm | 来源:发表于2019-10-12 22:13 被阅读0次

    一:概述

    上一篇我们用普通的方式完成了披萨店的代码,下面我们用注解的方式去实现,彻底解决这个需求的痛点。

    自定义注解系列文章

    分析

    其实现在最大的痛点是if else 语句。如果可以自动生成就好了。通过上面的描述,我们知道自定义注解可以自动生成我们需要的代码。要实现这个需求我们还需要三个开源库的帮忙

    • android-apt
      Android Studio原本是不支持注解处理器的, 但是用android-apt这个插件后, 我们就可以使用注解处理器了, 这个插件可以自动的为生成的代码创建目录, 让生成的代码编译到APK里面去, 而注解处理器本身的代码并不打包到APK包里。 因为这部分代码只是编译的时候需要用来生成代码, 最终运行的时候是没有用的。
      也就是说它主要有两个目的:
    • 允许配置注解处理器,但注解处理器的代码不会添加到最后的APK或library中.使用 annotationProcessor添加注解处理器为依赖。
    • 设置源路径,使注解处理器生成的代码能被Android Studio正确的引用.
    • Google Auto
      Google Auto的主要作用是注解Processor类,并对其生成META-INF的配置信息, 可以让你不用去写META-INF这些配置文件,只要在自定义的Processor上面加上@AutoService(Processor.class)
    • javapoet
      javapoet:A Java API for generating java source files.它可以帮助我们通过类调用的形式来生成代码。

    二 :模块分割

    综上所述,我们应该把注解的代码和业务逻辑的代码分开。注解的定义代码和注解的解析代码也分开。这样我们就可以定义为三个模块。module app 、module api、module compiler。

    • module app :业务相关逻辑代码
    • module api :定义注解。这个模块会被app 模块和 compiler 模块引用
    • module compiler :定义注解的解析方法。也就是定义 processor.这个模块不会打包到应用中。在编译过程中会生成我们定义的java 代码。这些代码会被打包到apk 中。
    注意:一定要使用jdk1.7,1.8对注解的支持有bug。

    app module

    需要在 gradle 中依赖 api module 和 compiler module .
    由于 compiler module 是生成代码的模块。所以需要用annotationProcessor 依赖。

    apply plugin: 'com.android.application'
    
    android {
        compileSdkVersion 28
        defaultConfig {
            applicationId "com.example.tuoanlan.androidannotationdemo"
            minSdkVersion 26
            targetSdkVersion 28
            versionCode 1
            versionName "1.0"
            testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        }
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
        }
    
    }
    
    
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        implementation 'com.android.support:appcompat-v7:28.0.0'
        implementation 'com.android.support.constraint:constraint-layout:1.1.3'
        testImplementation 'junit:junit:4.12'
        androidTestImplementation 'com.android.support.test:runner:1.0.2'
        androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
        
        //依赖api module
        implementation project(':api')
    
    //      依赖本地生成的Annoation project
        annotationProcessor project(':compiler')
    }
    

    注意:这里提个醒,有些文章说 apt 的依赖方式应该像下面这样,但注意 gradle 3.0 以后的版本,不适用这种方式,会报错的。所以根据你的gradle 版本,来选择适合的 apt 依赖方式。

    apply plugin: 'com.android.application'
    // apt(gradle 3.0 后的错误的配置)
    apply plugin: 'com.neenbedankt.android-apt'
    android {
        compileSdkVersion 28
        defaultConfig {
            applicationId "com.example.tuoanlan.androidannotationdemo"
            minSdkVersion 26
            targetSdkVersion 28
            versionCode 1
            versionName "1.0"
            testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        }
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
        }
    
    }
    
    
    
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        implementation 'com.android.support:appcompat-v7:28.0.0'
        implementation 'com.android.support.constraint:constraint-layout:1.1.3'
        testImplementation 'junit:junit:4.12'
        androidTestImplementation 'com.android.support.test:runner:1.0.2'
        androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    
        // 配置apt(gradle 3.0 后的错误的配置)
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    
        //依赖api module
        implementation project(':api')
    
    }
    
    

    报错信息如下

    android-apt plugin is incompatible with the Android Gradle plugin.  Please use 'annotationProcessor' configuration instead.
    

    在app module里我们定义各种披萨。比如

    /**
     * 披萨品种-提拉米苏
     */
    
    @Factory(
            id = "Tiramisu",
            type = Meal.class
    )
    public class Tiramisu implements Meal {
        @Override
        public float getPrice() {
            return 4.5f;
        }
    }
    

    注意:@Factory 注解在 api 模块里定义的

    api module

    api module 我们定义为 java-library


    image

    gradle 内容如下 。

    apply plugin: 'java-library'
    
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
    }
    //使用jdk1。7
    sourceCompatibility = JavaVersion.VERSION_1_7
    targetCompatibility = JavaVersion.VERSION_1_7
    

    定义注解@Factory

    @Target(ElementType.TYPE)//可用在:类、接口(包括注释类型)或枚举声明
    @Retention(RetentionPolicy.CLASS)//生命周期为class ,编译成.class 文件仍然存在
    public @interface Factory {
    
        /**
         * The name of the factory
         */
        Class type();
    
        /**
         * The identifier for determining which item should be instantiated
         */
        String id();
    }
    

    compiler module

    由于compiler中会解析注解并生成我们需要的代码。所以需要用到auto-servicejavapoet库,所以不要忘记添加依赖。另外compiler 模块中会用到 api 模块的注解,所以也需要添加 api 模块的依赖。整个 gradle 配置如下。

    apply plugin: 'java-library'
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
    
        //依赖 module api
        implementation project(':api')
    
        // 主要作用是注解Processor类,并对其生成META-INF的配置信息,
        // 可以让你不用去写META-INF这些配置文件,
        // 只要在自定义的Processor上面加上@AutoService(Processor.class)
        compile group: 'com.google.auto.service', name: 'auto-service', version: '1.0-rc6'
    
        //A Java API for generating .java source files.
        // 可以更方便的生成代码,它可以帮助我们通过类调用的形式来生成代码。
        compile 'com.squareup:javapoet:1.11.1'
    }
    
    //使用jdk1。7
    sourceCompatibility = JavaVersion.VERSION_1_7
    targetCompatibility = JavaVersion.VERSION_1_7
    

    三:定义储存注解信息的bean 类

    compiler module 中当我们读取到注解中所携带的信息后,需要把信息储存在类里方便我们调用。所以我们定义FactoryAnnotatedClass类。用来储存@Factory注解的相关信息。

    package com.example.compiler;
    
    import com.example.api.Factory;
    
    import javax.lang.model.element.TypeElement;
    import javax.lang.model.type.DeclaredType;
    import javax.lang.model.type.MirroredTypeException;
    
    public class FactoryAnnotatedClass {
    
        /**
         * TypeElement 信息
         * */
        private TypeElement annotatedClassElement;
    
       /**
        *{@link Factory#type()}指定的类型合法全名 ,在我这个项目里指 app 项目里的 Meal
        * */
        private String qualifiedSuperClassName;
    
    
        private String simpleTypeName;
    
        /**
         * {@link Factory#id()}中指定的id
         * */
        private String id;
    
        /**
         * 在 FactoryAnnotatedClass 中,我们保存被注解类的数据,比如合法的类的名字,以及 @Factory 注解本身的一些信息。
         * 也就是说每一个使用了 @Factory 注解的类都对应一个 FactoryAnnotatedClass 类
         * */
    
        public FactoryAnnotatedClass(TypeElement classElement) throws IllegalArgumentException {
    
    
            //由于实质上就是被注解的类(比如本项目中的  Tiramisu)
            this.annotatedClassElement = classElement;
    
            //获取到注解
            Factory annotation = classElement.getAnnotation(Factory.class);
    
            //取出注解的中得id
            id = annotation.id();
    
    
            //如果id 为空则抛出错误
            if ("".equalsIgnoreCase(id)) {
                throw new IllegalArgumentException(
                        String.format("id() in @%s for class %s is null or empty! that's not allowed",
                                Factory.class.getSimpleName(), classElement.getQualifiedName().toString()));
            }
    
            // Get the full QualifiedTypeName
    
            try {
    
                //取出注解的中得 type,它是个 class,我们这个项目指定的type 为 Meal,也就是 Tiramisu 等类实现的接口
                Class<?> clazz = annotation.type();
    
                // 返回底层阶级Java语言规范中定义的标准名称。
                qualifiedSuperClassName = clazz.getCanonicalName();
    
                //获取简单的类名(Meal的简名)
                simpleTypeName = clazz.getSimpleName();
    
    
            } catch (MirroredTypeException mte) {
                DeclaredType classTypeMirror = (DeclaredType) mte.getTypeMirror();
                TypeElement classTypeElement = (TypeElement) classTypeMirror.asElement();
                qualifiedSuperClassName = classTypeElement.getQualifiedName().toString();
                simpleTypeName = classTypeElement.getSimpleName().toString();
            }
        }
    
        /**
         * 获取在{@link Factory#id()}中指定的id
         * return the id
         */
        public String getId() {
            return id;
        }
    
        /**
         * 获取在{@link Factory#type()}指定的类型合法全名 ,本项目为Meal 的标准合法全名
         *
         * @return qualified name
         */
        public String getQualifiedFactoryGroupName() {
            return qualifiedSuperClassName;
        }
    
    
        /**
         * 获取在{@link Factory#type()}{@link Factory#type()}指定的类型的简单名字
         *
         * @return qualified name
         */
        public String getSimpleFactoryGroupName() {
            return simpleTypeName;
        }
    
        /**
         * 获取被  TypeElement(被@Factory 注解的类的 TypeElement)
         */
        public TypeElement getTypeElement() {
            return annotatedClassElement;
        }
    }
    
    

    四: 定义储存工厂类信息的类

    本例子仅仅自动生成 MealFactory 的代码,但如果后面我们需要生产其他 Factory 呢?,如drinksFactory 呢?所以我们需要定义一个类用来储存相关信息。

    package com.example.compiler;
    
    import com.example.api.Factory;
    import com.squareup.javapoet.JavaFile;
    import com.squareup.javapoet.MethodSpec;
    import com.squareup.javapoet.TypeName;
    import com.squareup.javapoet.TypeSpec;
    
    import java.io.IOException;
    import java.util.LinkedHashMap;
    import java.util.Map;
    
    import javax.annotation.processing.Filer;
    import javax.lang.model.element.Modifier;
    import javax.lang.model.element.PackageElement;
    import javax.lang.model.element.TypeElement;
    import javax.lang.model.util.Elements;
    
    public class FactoryGroupedClasses {
    
        /**
         * 将被添加到生成的工厂类的名字中
         */
        private static final String SUFFIX = "Factory";
    
    
        /**
         * 本例指 Meal 的合法全名
         */
        private String qualifiedClassName;
    
        /**
         *
         * */
        private Map<String, FactoryAnnotatedClass> itemsMap =
                new LinkedHashMap<String, FactoryAnnotatedClass>();
    
    
    
    
        //用 Meal 的合法全名作为参数,生成 FactoryGroupedClasses
        public FactoryGroupedClasses(String qualifiedClassName) {
            this.qualifiedClassName = qualifiedClassName;
        }
    
        /**
         * 添加方法,向itemMap 中插入
         * {@link FactoryAnnotatedClass}
         */
        public void add(FactoryAnnotatedClass toInsert) throws ProcessingException {
    
    
            /**
             *查询 {@link itemsMap } 中是否有该id 的 FactoryAnnotatedClass
             * */
            FactoryAnnotatedClass existing = itemsMap.get(toInsert.getId());
            if (existing != null) {
    
                // Alredy existing
                throw new ProcessingException(toInsert.getTypeElement(),
                        "Conflict: The class %s is annotated with @%s with id ='%s' but %s already uses the same id",
                        toInsert.getTypeElement().getQualifiedName().toString(), Factory.class.getSimpleName(),
                        toInsert.getId(), existing.getTypeElement().getQualifiedName().toString());
            }
    
            itemsMap.put(toInsert.getId(), toInsert);
    
        }
    
        /**
         *
         *
         * */
        public void generateCode(Elements elementUtils, Filer filer) throws IOException {
            //通过工厂接口的合法全类名获取到工厂接口的 TypeElement(本例的工厂为 Meal)
            TypeElement superClassName = elementUtils.getTypeElement(qualifiedClassName);
    
            //获取生成的class 名称(本例生成的为 MealFactory)
            String factoryClassName = superClassName.getSimpleName() + SUFFIX;
    
    
    
            //获取包元素
            PackageElement pkg = elementUtils.getPackageOf(superClassName);
    
            //获取包名
            String packageName = pkg.isUnnamed() ? null : pkg.getQualifiedName().toString();
    
            //生成一个方法名:create,参数:String 类型  叫id,返回值
            MethodSpec.Builder method = MethodSpec.methodBuilder("create")
                    .addModifiers(Modifier.PUBLIC)
                    .addParameter(String.class, "id")
                    .returns(TypeName.get(superClassName.asType()));
    
            // check if id is null(生成判断id 是否为空的判断逻辑)
            method.beginControlFlow("if (id == null)")
                    .addStatement("throw new IllegalArgumentException($S)", "id is null!")
                    .endControlFlow();
    
            // Generate items map
            //遍历被注解的类的封装类 FactoryAnnotatedClass
    
            for (FactoryAnnotatedClass item : itemsMap.values()) {
    
                method.beginControlFlow("if ($S.equals(id))", item.getId())
                        .addStatement("return new $L()", item.getTypeElement().getQualifiedName().toString())
                        .endControlFlow();
            }
    
    
            method.addStatement("throw new IllegalArgumentException($S + id)", "Unknown id = ");
    
            TypeSpec typeSpec = TypeSpec.classBuilder(factoryClassName).addMethod(method.build()).build();
    
            // Write file
            JavaFile.builder(packageName, typeSpec).build().writeTo(filer);
        }
    }
    
    
    

    五:实现 AbstractProcessor

    实现 AbstractProcessor,解析注解 @Factory

    
    
    
    //这个不要忘记了哦
    @AutoService(Processor.class)
    
    public class FactoryProcessor extends AbstractProcessor {
    
        //处理TypeMirror的工具类
        private Types typeUtils;
    
        //处理Element的工具类
        private Elements elementUtils;
    
        // 用来创建你要创建的文件
        private Filer filer;
    
        //提供给注解处理器一个报告错误、警告以及提示信息的途径。
        private Messager messager;
    
    
    
    
        //类名和  FactoryGroupedClasses 的映射(一个接口映射一个 FactoryGroupedClasses,本例 Meal 的类名映射一个 FactoryGroupedClasses)
        private Map<String, FactoryGroupedClasses> factoryClasses = new LinkedHashMap<String, FactoryGroupedClasses>();
    
    
        /**
         * 需要强调的是 Element 代表一个元素,包、类、方法、变量 这些都是 Element
         * 在注解的处理过程中会扫描所有的java源文件。源代码中的每一个部分都是一个特定类型的 Element
         * 可见 Foo 类,特意新建了一个类来说明
         */
    
    
        //初始化操作的方法,RoundEnvironment会提供很多有用的工具类Elements、Types和Filer等。
        @Override
        public synchronized void init(ProcessingEnvironment processingEnv) {
            super.init(processingEnv);
            typeUtils = processingEnv.getTypeUtils();
            elementUtils = processingEnv.getElementUtils();
            filer = processingEnv.getFiler();
            messager = processingEnv.getMessager();
    
    
        }
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return SourceVersion.latestSupported();
        }
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            Set<String> annotataions = new LinkedHashSet<>();
            annotataions.add(Factory.class.getCanonicalName());
            return annotataions;
        }
    
    
    
    
    
    
        //这相当于每个处理器的主函数main()。在该方法中去扫描、评估、处理以及生成Java文件。
    
        //    因此 Element代表的是源代码。TypeElement代表的是源代码中的类型元素,
        // 然而TypeElement并不包含类本身的信息
        // 你可以从TypeElement中获取类的名字,但是你获取不到类的信息,例如它的父类。
        // 这种信息需要通过TypeMirror获取。你可以通过调用elements.asType()获取元素的TypeMirror。
    
        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
            // roundEnv.getElementsAnnotatedWith(Factory.class))返回所有被注解了@Factory的元素的列表
            //所有元素列表。也就是包括 类、包、方法、变量等
            //roundEnv.getElementsAnnotatedWith(Factory.class)
    
    
            //遍历所有的元素列表
            for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(Factory.class)) {
    
                //由于返回的是所有元素,所以还要判断该元素是不是一个类
                if (annotatedElement.getKind() != ElementKind.CLASS) {
                    error(annotatedElement, "Only classes can be annotated with @%s",
                            Factory.class.getSimpleName());
                    return true; // 退出处理
                }
    
    
                // Element 的元素类型是 ElementKind.CLASS,所以可以直接强制转换(实际上就是被@Factory 注解的类 )
    
                TypeElement typeElement = (TypeElement) annotatedElement;
    
    
                try {
                    //生成 FactoryAnnotatedClass,将 typeElement保存的 FactoryAnnotatedClass 中,
                    // 之后用于判断该元素是否为符合我们要求的元素
                    FactoryAnnotatedClass factoryAnnotatedClass = new FactoryAnnotatedClass(typeElement);
    
                    /**
                     *
                     * 检测类是否符合要求:
                     * 1。是一个公开类
                     * 2。 只要一个公开的构造函数
                     * 3。 不是抽象类
                     * 4。继承于特定的类型,
                     * */
                    if (!isValidClass(factoryAnnotatedClass)) {
                        return true; // 已经打印了错误信息,退出处理过程
                    }
    
                    //从缓存Map 里获取
                    FactoryGroupedClasses factoryClass =
                            factoryClasses.get(factoryAnnotatedClass.getQualifiedFactoryGroupName());
    
    
                    if (factoryClass == null) {
    
                        //Meal 的合法全名
                        String qualifiedGroupName = factoryAnnotatedClass.getQualifiedFactoryGroupName();
    
                        //用 Meal 的合法全名作为参数,生成 FactoryGroupedClasses
                        factoryClass = new FactoryGroupedClasses(qualifiedGroupName);
    
                        //用  Meal 的合法全名作为key  FactoryGroupedClasses的实例作为值存入Map
                        factoryClasses.put(qualifiedGroupName, factoryClass);
                    }
    
                    // 如果和其他的@Factory标注的类的id相同冲突,
                    // 抛出IdAlreadyUsedException异常
    
                    //将包含被@Factory 注解的类的信息的 FactoryAnnotatedClass
                    // 的实例加入到 FactoryGroupedClasses 中的 map 中(经过多次循环,本项目会存入三个
                    //分别为   Tiramisu ,Margherita,Calzone)
    
    
                    factoryClass.add(factoryAnnotatedClass);
    
                } catch (IllegalArgumentException e) {
                    // @Factory.id()为空 --> 打印错误信息
                    error(typeElement, e.getMessage());
                } catch (ProcessingException e) {
                    error(e.getElement(), e.getMessage());
    
                }
    
    
    
            }
            // 为每个工厂生成Java文件
            //本例只有一个工厂 Meal 工厂
            try {
                for(FactoryGroupedClasses factoryClass : factoryClasses.values()) {
                    factoryClass.generateCode(elementUtils, filer);
                }
    
                // Clear to fix the problem
                factoryClasses.clear();
            } catch (IOException e) {
                error(null, e.getMessage());
            }
            return true;
        }
    
    
    
    
    
        /**
         * 使用{@link  #messager } 工具打印错误消息
         */
        private void error(Element e, String msg, Object... args) {
            messager.printMessage(
                    Diagnostic.Kind.ERROR,
                    String.format(msg, args),
                    e);
        }
    
    
        /**
         * 检查类{@link FactoryAnnotatedClass 是否符合要求}
         * <p>
         * 检测类是否符合要求:
         * 1。是一个公开类
         * 2。 只要一个公开的构造函数
         * 3。 不是抽象类
         * 4。继承于特定的类型,
         */
        private boolean isValidClass(FactoryAnnotatedClass item) {
    
            // 转换为TypeElement, 含有更多特定的方法(就是被@Factory 注解的类的类型)
            TypeElement classElement = item.getTypeElement();
    
    
            //是否是一个公开的类(class 前面的修饰语是否包含 PUBLIC)
            if (!classElement.getModifiers().contains(Modifier.PUBLIC)) {
                error(classElement, "The class %s is not public.",
                        classElement.getQualifiedName().toString());
                return false;
            }
    
            // 检查是否是一个抽象类(被 @Factory 注解的不能是个抽象类)
            if (classElement.getModifiers().contains(Modifier.ABSTRACT)) {
                error(classElement, "The class %s is abstract. You can't annotate abstract classes with @%",
                        classElement.getQualifiedName().toString(), Factory.class.getSimpleName());
                return false;
            }
    
            // 检查继承关系: 必须是@Factory.type()指定的类型子类(获取 Meal  TypeElement(类型元素))
            TypeElement superClassElement =
                    elementUtils.getTypeElement(item.getQualifiedFactoryGroupName());
    
    
            //是否是个接口 interface(这里指 Meal )
            if (superClassElement.getKind() == ElementKind.INTERFACE) {
    
    
                // 检查接口是否实现了
                //被 @Factory 注解的类实现的接口是否包含 superClassElement (这里指 Meal)
                if (!classElement.getInterfaces().contains(superClassElement.asType())) {
                    error(classElement, "The class %s annotated with @%s must implement the interface %s",
                            classElement.getQualifiedName().toString(), Factory.class.getSimpleName(),
                            item.getQualifiedFactoryGroupName());
                    return false;
                }
            }
            //如果()
            else {
                // 检查子类 ,classElement 是被@Factory 注解的类
                TypeElement currentClass = classElement;
                while (true) {
                    TypeMirror superClassType = currentClass.getSuperclass();
    
                    if (superClassType.getKind() == TypeKind.NONE) {
                        // 到达了基本类型(java.lang.Object), 所以退出
                        error(classElement, "The class %s annotated with @%s must inherit from %s",
                                classElement.getQualifiedName().toString(), Factory.class.getSimpleName(),
                                item.getQualifiedFactoryGroupName());
                        return false;
                    }
    
                    //父类就是要求的父类(这里指 Meal)
                    if (superClassType.toString().equals(item.getQualifiedFactoryGroupName())) {
                        // 找到了要求的父类
                        break;
                    }
    
                    // 在继承树上继续向上搜寻
                    currentClass = (TypeElement) typeUtils.asElement(superClassType);
                }
            }
    
            // 检查是否提供了默认公开构造函数(@被 Factory 注解的类是否 有 公开的构造 函数)
            for (Element enclosed : classElement.getEnclosedElements()) {
                if (enclosed.getKind() == ElementKind.CONSTRUCTOR) {
                    ExecutableElement constructorElement = (ExecutableElement) enclosed;
    
    
                    //构造函数是公开的,切构造函数参数为0
                    if (constructorElement.getParameters().size() == 0 && constructorElement.getModifiers()
                            .contains(Modifier.PUBLIC)) {
                        // 找到了默认构造函数
                        return true;
                    }
                }
            }
    
            // 没有找到默认构造函数
            error(classElement, "The class %s must provide an public empty default constructor",
                    classElement.getQualifiedName().toString());
            return false;
        }
    }
    
    
    

    提醒:在 FactoryProcessor 类的上方,不要忘记添加 @AutoService(Processor.class)

    六:解析注解

    关于 AbstractProcessor 中的各个函数的功能,我们已经在上一篇文章中做过介绍。这里只关注整个解析过程。

    第一步:获取工具类

    init方法里,ProcessingEnvironment 给我们提供了很多非常有用的工具类。

     //初始化操作的方法,RoundEnvironment会提供很多有用的工具类Elements、Types和Filer等。
        @Override
        public synchronized void init(ProcessingEnvironment processingEnv) {
            super.init(processingEnv);
             //处理TypeMirror的工具类
            typeUtils = processingEnv.getTypeUtils();
            
              //处理Element的工具类
            elementUtils = processingEnv.getElementUtils();
            
            // 用来创建你要创建的文件
            filer = processingEnv.getFiler();
            
            //提供给注解处理器一个报告错误、警告以及提示信息的途径。
            messager = processingEnv.getMessager();
    
    
        }
    
    第二步:遍历被@Factory 注解的元素

    在方法 process 中,可以通过 roundEnv.getElementsAnnotatedWith(Factory.class))返回所有被注解了@Factory的元素的列表。

     for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(Factory.class)) {
     ......
     }
    
    第三步:判断被注解的是否为类(class)

    因为元素 element 可以是 类、包、方法、变量等,根据我们的业务逻辑,被注解的是一个类,所以需要做个判断。

      //由于返回的是所有元素,所以还要判断该元素是不是一个类
                if (annotatedElement.getKind() != ElementKind.CLASS) {
                    error(annotatedElement, "Only classes can be annotated with @%s",
                            Factory.class.getSimpleName());
                    return true; // 退出处理
                }
    

    由于annotatedElement 的元素类型是 ElementKind.CLASS,所以 annotatedElement 是一个 TypeElement,所以可以强制转换。因为 new FactoryAnnotatedClass 的时候需要传入 TypeElement 类型的参数。

    第四步:判断被注解的是否为符合要求的类

    根据我们的业务需要,被注解的类需要满足如下条件

    • 必须是个公开类
    • 只有一个公开的构造函数
    • 不是抽象类
    • 继承于特定的类型(例子中我们继承了 Meal 类)
      上一步我们把 annotatedElement 转化为 TypeElement后构建了 FactoryAnnotatedClass,在 FactoryAnnotatedClass 中我们取出了 TypeElement中包含的被注解类相关信息.
      private boolean isValidClass(FactoryAnnotatedClass item) {
    
            // 转换为TypeElement, 含有更多特定的方法(就是被@Factory 注解的类的类型)
            TypeElement classElement = item.getTypeElement();
    
    
            //是否是一个公开的类(class 前面的修饰语是否包含 PUBLIC)
            if (!classElement.getModifiers().contains(Modifier.PUBLIC)) {
                error(classElement, "The class %s is not public.",
                        classElement.getQualifiedName().toString());
                return false;
            }
    
            // 检查是否是一个抽象类(被 @Factory 注解的不能是个抽象类)
            if (classElement.getModifiers().contains(Modifier.ABSTRACT)) {
                error(classElement, "The class %s is abstract. You can't annotate abstract classes with @%",
                        classElement.getQualifiedName().toString(), Factory.class.getSimpleName());
                return false;
            }
    
            // 检查继承关系: 必须是@Factory.type()指定的类型子类(获取 Meal  TypeElement(类型元素))
            TypeElement superClassElement =
                    elementUtils.getTypeElement(item.getQualifiedFactoryGroupName());
    
    
            //是否是个接口 interface(这里指 Meal )
            if (superClassElement.getKind() == ElementKind.INTERFACE) {
    
    
                // 检查接口是否实现了
                //被 @Factory 注解的类实现的接口是否包含 superClassElement (这里指 Meal)
                if (!classElement.getInterfaces().contains(superClassElement.asType())) {
                    error(classElement, "The class %s annotated with @%s must implement the interface %s",
                            classElement.getQualifiedName().toString(), Factory.class.getSimpleName(),
                            item.getQualifiedFactoryGroupName());
                    return false;
                }
            }
            //如果()
            else {
                // 检查子类 ,classElement 是被@Factory 注解的类
                TypeElement currentClass = classElement;
                while (true) {
                    TypeMirror superClassType = currentClass.getSuperclass();
    
                    if (superClassType.getKind() == TypeKind.NONE) {
                        // 到达了基本类型(java.lang.Object), 所以退出
                        error(classElement, "The class %s annotated with @%s must inherit from %s",
                                classElement.getQualifiedName().toString(), Factory.class.getSimpleName(),
                                item.getQualifiedFactoryGroupName());
                        return false;
                    }
    
                    //父类就是要求的父类(这里指 Meal)
                    if (superClassType.toString().equals(item.getQualifiedFactoryGroupName())) {
                        // 找到了要求的父类
                        break;
                    }
    
                    // 在继承树上继续向上搜寻
                    currentClass = (TypeElement) typeUtils.asElement(superClassType);
                }
            }
    
            // 检查是否提供了默认公开构造函数(@被 Factory 注解的类是否 有 公开的构造 函数)
            for (Element enclosed : classElement.getEnclosedElements()) {
                if (enclosed.getKind() == ElementKind.CONSTRUCTOR) {
                    ExecutableElement constructorElement = (ExecutableElement) enclosed;
    
    
                    //构造函数是公开的,切构造函数参数为0
                    if (constructorElement.getParameters().size() == 0 && constructorElement.getModifiers()
                            .contains(Modifier.PUBLIC)) {
                        // 找到了默认构造函数
                        return true;
                    }
                }
            }
    
            // 没有找到默认构造函数
            error(classElement, "The class %s must provide an public empty default constructor",
                    classElement.getQualifiedName().toString());
            return false;
        }
    
    第五步:构建 FactoryGroupedClasses 并存入 Map 中
    • 使用即将构造为工厂类的接口名称的合法全名构造(本例为Meal的合法全名,之后生成的工厂类为 MealFactory) FactoryGroupedClasses 。在FactoryGroupedClasses 中,我们定义了一个Map类型的全局变量 。通过调用 FactoryGroupedClassesadd 方法,用前面构建的 FactoryAnnotatedClass 实例的id作为keyFactoryAnnotatedClass 实例作为值存储在该 Map 中.
        public void add(FactoryAnnotatedClass toInsert) throws ProcessingException {
    
    
            /**
             *查询 {@link itemsMap } 中是否有该id 的 FactoryAnnotatedClass
             * */
            FactoryAnnotatedClass existing = itemsMap.get(toInsert.getId());
            if (existing != null) {
    
                // Alredy existing
                throw new ProcessingException(toInsert.getTypeElement(),
                        "Conflict: The class %s is annotated with @%s with id ='%s' but %s already uses the same id",
                        toInsert.getTypeElement().getQualifiedName().toString(), Factory.class.getSimpleName(),
                        toInsert.getId(), existing.getTypeElement().getQualifiedName().toString());
            }
    
            itemsMap.put(toInsert.getId(), toInsert);
    
        }
    
    • 由于可能还有其他工厂类需要构建(比如我们这里构建了MealFactory,但以后可能还需要构建 DrinkFactory),所以在 FactoryProcessor 中我们定义一个Map 类型的全局变量来储存 FactoryGroupedClasses 的实例。
                    //从缓存Map 里获取
                    FactoryGroupedClasses factoryClass =
                            factoryClasses.get(factoryAnnotatedClass.getQualifiedFactoryGroupName());
    
    
                    if (factoryClass == null) {
    
                        //Meal 的合法全名
                        String qualifiedGroupName = factoryAnnotatedClass.getQualifiedFactoryGroupName();
    
                        //用 Meal 的合法全名作为参数,生成 FactoryGroupedClasses
                        factoryClass = new FactoryGroupedClasses(qualifiedGroupName);
    
                        //用  Meal 的合法全名作为key  FactoryGroupedClasses的实例作为值存入Map
                        factoryClasses.put(qualifiedGroupName, factoryClass);
                    }
    
    第六步:构建工厂类
    • 遍历 FactoryProcessor 中的Map类型的全局变量,生成不同的工厂类(本例就一个Meal 工厂类)
           for(FactoryGroupedClasses factoryClass : factoryClasses.values()) {
                    factoryClass.generateCode(elementUtils, filer);
                }
    
    • 注意:通过Processor 的源码,我们知道,虽然注解处理器只会初始化一次。但 process()可能会多次调用。在每个处理循环中,注解处理器会去处理存在该注解的所有文件,包括先前处理循环中生成的文件(这些文件可能也包含注解)。如果 factoryClasses 数据没有清空,会重复生成代码,此时则会报错。所以在每个处理循环中需要清空map。但是具体会执行几次,无法确认。可以通过RoundEnvironment,的RoundEnvironment.processingOver() 获知是否到了最后一个处理周期了。
    • 调用 factoryClass.generateCode 生成代码。
     public void generateCode(Elements elementUtils, Filer filer) throws IOException {
            //通过工厂接口的合法全类名获取到工厂接口的 TypeElement(本例的工厂为 Meal)
            TypeElement superClassName = elementUtils.getTypeElement(qualifiedClassName);
    
            //获取生成的class 名称(本例生成的为 MealFactory)
            String factoryClassName = superClassName.getSimpleName() + SUFFIX;
    
    
    
            //获取包元素
            PackageElement pkg = elementUtils.getPackageOf(superClassName);
    
            //获取包名
            String packageName = pkg.isUnnamed() ? null : pkg.getQualifiedName().toString();
    
            //生成一个方法名:create,参数:String 类型  叫id,返回值
            MethodSpec.Builder method = MethodSpec.methodBuilder("create")
                    .addModifiers(Modifier.PUBLIC)
                    .addParameter(String.class, "id")
                    .returns(TypeName.get(superClassName.asType()));
    
            // check if id is null(生成判断id 是否为空的判断逻辑)
            method.beginControlFlow("if (id == null)")
                    .addStatement("throw new IllegalArgumentException($S)", "id is null!")
                    .endControlFlow();
    
            // Generate items map
            //遍历被注解的类的封装类 FactoryAnnotatedClass
    
            for (FactoryAnnotatedClass item : itemsMap.values()) {
    
                method.beginControlFlow("if ($S.equals(id))", item.getId())
                        .addStatement("return new $L()", item.getTypeElement().getQualifiedName().toString())
                        .endControlFlow();
            }
    
    
            method.addStatement("throw new IllegalArgumentException($S + id)", "Unknown id = ");
    
            TypeSpec typeSpec = TypeSpec.classBuilder(factoryClassName).addMethod(method.build()).build();
    
            // Write file
            JavaFile.builder(packageName, typeSpec).build().writeTo(filer);
        }
    

    generateCode() 方法中使用 Square javapoet 库生成代码,当然也可以使用拼接的方式生成代码。但使用 javapoet 会更加便利。

    生成代码

    现在我们可以使用我们的注解,生成我们想要的代码了。在Terminal 执行
    ./gradlew build,等待构建成功,即可。那生产的源码在哪里呢?

    image
    打开看看和我们自己写的工厂类方法一摸一样。好了,以后我们都像大神了,哈哈。美梦.jpg
    image

    怎么用?

    其实感觉这都不用讲了,像调正常代码一样调用就可以了。

    public class PizzaStore {
        public static void main(String[] args) {
            MealFactory mealFactory = new MealFactory();
            Meal meal = mealFactory.create(readPizzaNameFromConsole());
            System.out.println("Bill: $" + meal.getPrice());
        }
    
        private static String readPizzaNameFromConsole() {
            Scanner s = new Scanner(System.in);
            System.out.println("请输入披萨名称:");
            String lin = "";
            lin = s.nextLine();
            System.out.println("读取披萨名称结束!!");
            return lin;
        }
    }
    

    嗯,就是这么调用,如果你把我代码clone下来了,找到这个类应该就可以跑下,试一试。好了这篇有点长,看累了就休息会吧!github 地址

    七:参考链接

    另外感谢下面这会大神的分享,膜拜!

    相关文章

      网友评论

        本文标题:那些高端、优雅的注解是怎么实现的<5> --使用Annotaio

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