那些高端、优雅的注解是怎么实现的<5> --使用Annotaion Processing Tool 自定义注解

一:概述

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

自定义注解系列文章

分析

其实现在最大的痛点是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 地址

七:参考链接

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容