美文网首页
注解和APT

注解和APT

作者: leap_ | 来源:发表于2020-01-06 18:34 被阅读0次

注解

一种用于解释java代码的标签,用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。

注解种类:

  • 元注解
  • java自带注解
  • 自定义注解
自定义注解:
语法格式
public @interface MyAnnotation {
    
}
元注解:用于修饰自定义注解
  1. Retention:说明自定义注解的声明周期
 RetentionPolicy.RUNTIME // 注解保留到程序运行时
 RetentionPolicy.CLASS // 注解保留到编译
 RetentionPolicy.SOURCE // 注解保留到源码阶段(编译后.class文件不会有这个注解)
  1. Documented:文档注解,将自定义注解的元素包含到javadoc文档中(不常用)

  2. Target:描述注解的使用范围

    TYPE, // 类、接口、枚举类
 
    FIELD, // 成员变量(包括:枚举常量)
 
    METHOD, // 成员方法
 
    PARAMETER, // 方法参数
 
    CONSTRUCTOR, // 构造方法
 
    LOCAL_VARIABLE, // 局部变量
 
    ANNOTATION_TYPE, // 注解类
 
    PACKAGE, // 可用于修饰:包
 
    TYPE_PARAMETER, // 类型参数,JDK 1.8 新增
 
    TYPE_USE // 使用类型的任何地方,JDK 1.8 新增
  1. Inherited:使自定义注解具有继承性
@Inherited
public @interface InheritAnnotation {
}

@InheritAnnotation
public class Person {

}

class Man extends Person{

}

Man类具有InheritAnnotation注解属性;

  1. Repeatable:表示这个注解可以重复使用
public @interface Persons {
    Person[] value();
}

@Repeatable(Persons.class)
public @interface Person {
    String value() default "";
}

@Person(value = "222")
@Person(value = "111")
class RepeatableAnnotation{

}

注解的属性:

注解只有成员变量,没有方法。需要注意的是,在注解中定义属性时它的类型必须是 8 种基本数据类型,默认值需要用 default 关键值指定

public @interface Person {
    String name();
    String position() default "普工";
    int age();
    boolean isMale();
}

注解属性的提取(运行时注解提取):

使用反射提取类的注解信息;

定义一个Employee注解,用来存放员工的基本信息

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Employee {
    String name();
    String position() default "普工";
    int age();
    boolean isMale();
}

两个员工类,tony,amy,分别指定他们注解的值

@Employee(name = "tony甲",position = "经理",age = 26,isMale = true)
public class Tony {
}

@Employee(name = "amy红",position = "HR",age = 30,isMale = false)
 class Amy{
}

通过反射获取他们注解所带的值

public static void main(String[] args) {
        Tony tony = new Tony();
        Amy amy = new Amy();

        Class tonyClass = tony.getClass();
        Employee tonyClassAnnotation = (Employee) tonyClass.getAnnotation(Employee.class);
        System.out.println(tonyClassAnnotation);

        Class amyClass = amy.getClass();
        Annotation[] amyClassAnnotations = amyClass.getAnnotations();
        Employee amyClassAnnotation = (Employee) amyClassAnnotations[0];
        System.out.println(amyClassAnnotation);

    }
运行结果

注解的作用:

  • 提供信息给编译器:编译器可以利用注解来探测错误和警告信息
  • 编译阶段的处理:软件工具可以用来利用注解信息来生成代码、Html文档或者做其它相应处理。
  • 运行时的处理: 某些注解可以在程序运行的时候接受代码的提取值得注意的是,注解不是代码本身的一部分。

APT (编译期注解提取)

APT(Annotation Processing Tool 的简称),可以在代码编译期解析注解,并且生成新的 Java 文件,减少手动的代码输入。

下面介绍APT技术的几个重要概念

  • AbstractProcess 注解处理器:一个抽象类,用于自定义注解处理器,需要继承这个类,并实现它的四个方法;
  • AutoService 处理器注册服务:谷歌提供的一个自动注册注解处理器的服务,省去写MATA文件的步骤
  • JavaPoet 代码生成工具:一个第三方的API,通过这个API可以生成模板类型的java代码

AbstractProcess 和 AutoService 实现编译期解析注解

  1. 我们需要创建两个java Module annotation和processors

注意这两个都是java Module,annotation用来存放我们的自定义注解,processor用来存放我们的自定义注解处理器;app是我们的工程项目,他们的依赖关系是app依赖annotation和processors,processors依赖annotation;

  1. 创建自定义注解,在项目中引用注解
自定义注解 在项目中使用注解
  1. 创建自定义注解处理器
首先引入谷歌的自动注册服务,AutoService
implementation 'com.google.auto.service:auto-service:1.0-rc4'
implementation 'com.google.auto:auto-common:0.10'
直接添加一个autoService注解就可
自定义注解处理器
@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
    }


    /**
     * 这相当于每个处理器的主函数main(),你在这里写你的扫描、评估和处理注解的代码,以及生成Java文件。
     * 输入参数RoundEnviroment,可以让你查询出包含特定注解的被注解元素
     * @param annotations   请求处理的注解类型
     * @param roundEnv  有关当前和以前的信息环境
     * @return  如果返回 true,则这些注解已声明并且不要求后续 Processor 处理它们;
     *          如果返回 false,则这些注解未声明并且可能要求后续 Processor 处理它们
     */
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

        System.out.println("-----process():-------");

        // 通过我们指定的注解返回与这个注解绑定的element集合
        Set<? extends Element> annotationElements = roundEnv.getElementsAnnotatedWith(MyAnnotation.class);
        // 遍历当前element集合
        for (Element element : annotationElements){
            // 通过element的种类将他转成element的实现类
            if (element.getKind() == ElementKind.CLASS){
                TypeElement typeElement = (TypeElement) element;
                // 通过element的具体的实现类获取注解的值
                System.out.println(typeElement.getAnnotation(MyAnnotation.class).value());
            }
        }

        return false;
    }

    // 规定这个注解处理器处理的注解
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> set = new LinkedHashSet<>();
        set.add(MyAnnotation.class.getCanonicalName());
        return set;
    }

    //指定java版本
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return super.getSupportedSourceVersion();
    }
}
下面重点介绍Element接口

Element 代表程序的元素,在注解处理过程中,编译器会扫描所有的Java源文件,并将源码中的每一个部分都看作特定类型的 Element。它可以代表包、类、接口、方法、字段等多种元素种类,具体看getKind()方法中所指代的种类,每个Element 代表一个静态的、语言级别的构件。

/**
     * 返回此元素的种类:包、类、接口、方法、字段...,如下枚举值
     * PACKAGE, ENUM, CLASS, ANNOTATION_TYPE, INTERFACE,
     * ENUM_CONSTANT, FIELD, PARAMETER, LOCAL_VARIABLE, 
     * EXCEPTION_PARAMETER,METHOD, CONSTRUCTOR, STATIC_INIT, 
     * INSTANCE_INIT, TYPE_PARAMETER, OTHER, RESOURCE_VARIABLE;
     */
    ElementKind getKind();

getKind()用于获取这个Element的具体种类,这些种类都存放在枚举类ElementKind

Element接口有五个默认实现类:
  • PackageElement: 表示一个包程序元素
  • TypeElement: 表示一个类或接口程序元素
  • VariableElement: 表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数
  • ExecutableElement: 表示某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注解类型元素
  • TypeParameterElement: 表示一般类、接口、方法或构造方法元素的泛型参数

在process()中,我们通过当前环境信息RoundEnvironment和我们指定要解析的注解拿到和这个注解相关的Element集合,再遍历element集合通过它的Kind将它转成具体的Element实现类,然后通过这个实现类去获取注解;

相关文章

网友评论

      本文标题:注解和APT

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