APT在编译期间生成代码

2020-08-28  本文已影响0人  资本家大恶人

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

虚处理器AbstractProcessor

利用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

上一篇下一篇

猜你喜欢

热点阅读