注解与APT技术探索(一)

一 注解

Java 注解(Annotation)又称 Java 标注,是 JDK1.5 引入的一种注释机制。是元数据的一种形式,提供有关于程序但不属于程序本身的数据。注解对它们注解的代码的操作没有直接影响

二 元注解

在定义注解时,注解类也能够使用其他的注解声明。对注解类型进行注解的注解类,我们称之为 meta-annotation(元注解)。声明的注解允许作用于哪些节点使用@Target声明;保留级别由@Retention 声明。其中保留级别如下。

  • RetentionPolicy.SOURCE 标记的注解仅保留在源级别中,并被编译器忽略
  • RetentionPolicy.CLASS 标记的注解在编译时由编译器保留,但 Java 虚拟机(JVM)会忽略。
  • RetentionPolicy.RUNTIME 标记的注解由 JVM 保留,因此运行时环境可以使用它

SOURCE < CLASS < RUNTIME,即CLASS包含了SOURCE,RUNTIME包含SOURCE、CLASS。

三 注解的应用场景

根据注解的保留级别不同,对注解的使用自然存在不同场景。由注解的三个不同保留级别可知,注解作用于:
源码、字节码与运行时

注解使用场景.png

APT注解处理器

APT全称为:”Anotation Processor Tools”,意为注解处理器。顾名思义,其用于处理注解。编写好的Java源文 件,需要经过 javac 的编译,翻译为虚拟机能够加载解析的字节码Class文件。注解处理器是 javac 自带的一个工 具,用来在编译时期扫描处理注解信息。你可以为某些注解注册自己的注解处理器。 注册的注解处理器由 javac 调起,并将注解信息传递给注解处理器进行处理。

注解处理器是对注解应用最为广泛的场景。在Glide、EventBus3、Butterknifer、Tinker、ARouter等等常用 框架中都有注解处理器的身影。但是你可能会发现,这些框架中对注解的定义并不是 SOURCE 级别,更多的 是 CLASS 级别,别忘了:CLASS包含了SOURCE,RUNTIME包含SOURCE、CLASS

APT的使用详解

  1. 新建一个Module,里面新建一个注解

注意这里是 java-library

package com.base.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface Route {
    /**
     *路由的路径,标识一个路由节点
     */
    String path();
    /**
     * 将路由节点进行分组,可以实现按组动态加载
     */
    String group() default "";
}
复制代码
  1. 新建一个Module,创建一个注解处理器
package com.base.compiler.processor;
import com.base.annotation.Route;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
//1.这里是APT注解处理器与定义的注解相关联
@SupportedAnnotationTypes({"com.base.basejavapro.annotation.Route","com.base.annotation.Route"})
public class RouteProcessor extends AbstractProcessor {

    private Filer mFiler;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        mFiler = processingEnv.getFiler();//2.初始化一个项目编译文件路径
    }

    
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
        Messager messager = processingEnv.getMessager();
  messager.printMessage(Diagnostic.Kind.NOTE,"=============RouteProcessor==================:"+set.size());  //编译期日志打印

        Set<? extends Element> routeElements = roundEnv.getElementsAnnotatedWith(Route.class);// 3.获取所有添加注解的元素集合
        for (Element element : routeElements) {// 4.遍历 添加注解的元素

            Route route = element.getAnnotation(Route.class);
            String path = route.path();
            String group = route.group();
            String newClassName = path + "$$Hx";
          //5.通过字符拼接方法
            StringBuilder builder = new StringBuilder()
                    .append("package com.hongx.processor.auto;\n\n")
                    .append("public class ")
                    .append(newClassName)
                    .append(" {\n\n") // open class
                    .append("\tpublic String getMessage() {\n") // open method
                    .append("\t\treturn "");
            builder.append(path).append(group).append(" !\n");
            builder.append("";\n") // end return
                    .append("\t}\n") // close method
                    .append("}\n"); // close class
            try {
                JavaFileObject source = mFiler.createSourceFile("com.hongx.processor.auto."+newClassName);
                Writer writer = source.openWriter();
                writer.write(builder.toString());
                writer.flush();
                writer.close();
            } catch (IOException e) {

            }

            if (!set.isEmpty()) {
                createRoute(mFiler,newClassName,path);
            }
        }
        return false;
    }
   
   //6.通过JavaPoet
    public static void createRoute(Filer filer,String className,String path) {
        //使用 MethodSpec 主方法main生成
        MethodSpec main = MethodSpec.methodBuilder("getMessage")      //主方法的名称
                .addModifiers(Modifier.PUBLIC)
                .returns(String.class)
                .addStatement("return $S",path)
                .build();
        // 使用 TypeSpec 生成 HelloWorld 类
        TypeSpec typeSpec = TypeSpec.classBuilder(className)   //主类的名称
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                .addMethod(main)
                .addJavadoc("注释注解代码块")
                .build();
        try {
            JavaFile javaFile = JavaFile.builder("com.javapoet.processor.auto", typeSpec)
                    .build();
            /**
             * 代码写入控制台
             */
            javaFile.writeTo(System.out);
            /**
             * 代码写入文件 E:\FastEc\latte_compiler\src\main\java
             */
            javaFile.writeTo(filer);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
复制代码

3.手动创建配置文件 javax.annotation.processing.Processor

配置文件.png

在main目录下创建resources/META-INF/services/javax.annotation.processing.Processor 文件
其内容就是自定义注解处理器的全类名

4.运行结果
通过编译,在编译目录产生了目标文件 1、2,以及

执行步骤
执行步骤.png
执行结果

执行结果.png

代码已上传Github

代码下载

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享