美文网首页
注解和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