美文网首页
Java注解

Java注解

作者: 淡季的风 | 来源:发表于2021-05-23 17:07 被阅读0次

    一、什么是注解

    注解也叫元数据, 例如我们常见的@Override和@Deprecated等。注解是JDK1.5引入的一个特性, 用于对类、方法、字段、参数、构造器、包、局部变量等进行注解。

    一般常用的注解可以分为三类:
    1、Java自带的标准注解: 包括@Override(标明重写某个方法)和@Deprecated(标明某个方法被废弃)、@SuppressWarnings(标明要忽略的警告), 使用这些注解后编译器就会进行检查。
    2、元注解, 元注解是定义注解的注解:包括@Retention(标明注解的被保留的阶段)、@Target(标明注解使用的范围)、@Inherited(标明注解可被继承)、@Documented(标明是否生成文档)。
    3、自定义注解

    二、注解的用途

    • 编译检查: 提供信息给编译器,编译器可以利用注解来检测出错误或警告信息, 打印出日志
    • 编译时动态处理: 利用注解编译期间动态生成代码或生成文档或其他的自动化处理。
    • 运行时动态处理: 运行时通过代码里标示的元数据动态处理, 如通过反射注入实例等。

    三、 注解的组成

    1、 注解的架构

    Annotation架构图如下:


    image.png

    从上面看出(实际可以自己编译一下注解的class文件),
    1)注解都是继承自java.lang.annoation.Annoation接口:

    package java.lang.annotation;
    
    public interface Annotation {
        boolean equals(Object var1);
    
        int hashCode();
    
        String toString();
    
        Class<? extends Annotation> annotationType();
    }
    

    2)每一个注解由一个或多个ElementType和RetentionPolicy构成(必须声明RetentionPolicy, 否则获取不到注解)
    Example:

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target(ElementType.TYPE)
    //@Retention(RetentionPolicy.RUNTIME)
    public @interface MyAnAnnotationType {
        String value() default "haha";
    }
    
    
    image.png

    可以看到如果定义的注解未声明RetentionPolicy, 被注解的类等是无法通过反射获得Annoation实例的。
    经实验,其他的元注解可有可无,如果没定义会使用默认的类型。

    3)除过@Target和@Retention, 还有@Docuemented、@Inherited、@Repeatable、@Native多个元注解。


    image.png
    1. 注解不可以被继承
      注解是不可以被继承的, 也就是说我们定义的注解不可以通过extends的方式被另外一个注解继承。

    2、元注解

    元注解顾名思义我们可以理解为注解的注解, 它是作用在注解中,方便我们使用注解实现想要实现的功能。
    元注解包括: @Retention、@Target、@Inherited、@Documented、@Repeatable(JDK1.8引入)、@Native(JDK1.8引入)共6个。

    2.1、@Target
    @Target表示注解修饰的目标, 使用@Target表明我们的注解起作用的范围, 包括类、字段、方法、参数、构造器、包、注解等, 通过java.lang.annoation.ElementType表示:

    @Target:

    package java.lang.annotation;
    
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.ANNOTATION_TYPE})
    public @interface Target {
        ElementType[] value();
    }
    从Target的定义可以看到, 它的注解目标是注解,它的值是一个ElementType数组。
    
    

    ElementType:

    package java.lang.annotation;
    
    public enum ElementType {
        TYPE,
        FIELD,
        METHOD,
        PARAMETER,
        CONSTRUCTOR,
        LOCAL_VARIABLE,
        ANNOTATION_TYPE,
        PACKAGE,
        TYPE_PARAMETER,
        TYPE_USE,
        MODULE;
    
        private ElementType() {
        }
    }
    
    • ElementType.TYPE: 作用于类、接口、注解、枚举等
    • ElementType.FIELD: 作用于属性字段、枚举的属性等
    • ElementType.METHOD: 作用于方法
    • ElementType.PARAMETER: 作用于参数
    • ElementType.CONSTRUCTOR: 作用于构造器
    • ElemengType.LOCAL_VARIABLE: 作用于局部变量
    • Element Type.ANNOATION_TYPE: 作用于注解
    • ElementType.PACKAGE: 作用于包
    • ElementType.TYPE_PARAMETER: 作用于类型泛型, 如泛型参数等

    2.2、@Retention
    @Retention表示注解保留的阶段, 它表示注解是保留在源码阶段(编译器)、编译阶段(类加载)还是运行阶段(类运行), 共有3种含义:

    package java.lang.annotation;
    
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.ANNOTATION_TYPE})
    public @interface Retention {
        RetentionPolicy value();
    }
    
    

    可以看到Retention的值是一个RetentionPolicy类型, RetentionPolicy是一个枚举,共有3种定义:

    package java.lang.annotation;
    
    public enum RetentionPolicy {
        SOURCE,
        CLASS,
        RUNTIME;
    
        private RetentionPolicy() {
        }
    }
    
    
    • SOURCE: 注解仅保留在源码中, 编译器会丢弃, 编译后的class字节码文件中不包含。
    • CLASS: 注解仅保留在字节码中, 编译器不会丢弃, 编译后的class文件中包含, JVM加载字节码时不会加载。
    • RUNTIME: 注解会在class文件中存在, 运行期间可以通过反射获取

    2.3、@Inherited
    @Inherited的英文意思是继承, 一个被@Inherited注解的注解修饰的父类, 如果它的子类没有被其他注解注释, 则它的子类也继承了父类的注解。
    Example:

    @Inherited
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyAnAnnotationType {
        String value() default "haha";
    }
    
    @MyAnAnnotationType("123")
    public class Test {
    }
    
    public class Test3 extends Test{
        public static void main(String[] args) {
            MyAnAnnotationType type = Test3.class.getAnnotation(MyAnAnnotationType.class);
            System.out.println(type.value());
        }
    }
    
    输出: 123
    
    

    2.4、 @Documented
    @Documented的英文意思是文档, 它可以将注解中包含的元素包含到javadoc中去。

    2.5、@Repeatable
    @Repeatable的意思是可重复的, 被@Repeatable修饰的注解可以在同一个地方使用多次,在此之前在同一个地方一个注解只能使用一次。
    Repeatable源码如下:

    package java.lang.annotation;
    
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.ANNOTATION_TYPE})
    public @interface Repeatable {
        Class<? extends Annotation> value();
    }
    
    

    @Repeatable的使用要点:

    • @Repeatable的参数为被装饰的容器类的class对象。
    • 在需要重复使用的注解上修饰@Repeatable。
    • 容器包含一个value方法, 返回一个被修饰注解的数组。

    Example: 一个Group有多个People, 如果没有@Repeatable注解, 那么我们需要这样定义:

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface Peoples {
        String[] value();
    }
    
    @Peoples({"1", "2"})
    public class Group {
        public static void main(String[] args) {
            Peoples ps = Group.class.getAnnotation(Peoples.class);
            System.out.println(ps.value().length);
            People[] pss = Group.class.getAnnotationsByType(People.class);
            System.out.println(pss.length);
        }
    }
    

    看起来比较麻烦, 且复杂场景下比较难以实现。

    如果有@Repeatable注解, 我们可以这样做:

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @Repeatable(Peoples.class)
    public @interface People {
        String value();
    }
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface Peoples {
        People[] value();
    }
    
    @People("1")
    @People("2")
    public class Group {
        public static void main(String[] args) {
            Peoples ps = Group.class.getAnnotation(Peoples.class);
            System.out.println(ps.value().length);
            People[] pss = Group.class.getAnnotationsByType(People.class);
            System.out.println(pss.length);
        }
    }
    

    看起来会简洁很多, 实际上这只是一个语法糖。可以参考一篇写的比较好的文章: https://juejin.cn/post/6844904142041792525

    2.6、 @Native
    @Native用来修饰一个字段, 代表引用本地常量。

    四、 注解的使用

    1、定义注解

    1.1、注解的属性
    注解的属性和类的变量差不多,只是注解中的属性都是成员变量, 并且注解中没有方法, 只有成员变量。变量名就是注解括号中对应的参数名, 返回值就是注解括号中对应的类型。

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.ANNOTATION_TYPE})
    public @interface Target {
        ElementType[] value();
    }
    

    @Target的属性就是value, 是一个java.lang.annoation.ElementType类型。

    1.2、 注解的属性类型
    注解的属性类型可以是下面的任何一种类型:

    • 基本数据类型
    • String类型
    • 枚举类型
    • 注解类型
    • Class类型
    • 以上几种类型的一维数组类型。

    注解的参数应该如何设定:

    • 注解的类型必须是以上几种之一。
    • 只能用public或默认(default)访问修饰符修饰。
    • 如果只有一个参数, 最好使用value来表示。
    • 直接的参数可以设置默认值, 方式是参数后面加 default 默认值, 例如: String value default "default";

    1.3、注解的声明方式
    注解使用@interface声明, 如下 :

      public @interface interfaceName{
         // 方法体
      }
    

    一般来说, 定义注解需要定义@Target和@Retention, 如下:

    @Target(作用目标)
    @Retention(保留阶段)
    public @interface interfaceName{
       // 方法体
    }
    

    1.4、注解属性的默认值
    注解元素必须有确定的值,要么在定义注解的时候定义, 要么在使用注解的时候声明, 非基本类型的注解元素值不可以为null。
    声明默认值的方式如下:

    public @interface interfaceName{
       String value() default "";
       int age() default -1;
    }
    

    1.5、 注解不可以被继承
    注解不能通过extends的方式继承另外一个注解。

    1.6、自定义一个注解

    @Documented
    @Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
    @Retention(RetentionPolicy.CLASS)
    public @interface MyAnnotationType {
        String business();
        
        long minValue() default 10L;
        
        long maxValue() default 10000L;
    }
    

    2、提取注解

    注解定义后, 需要在使用的时候提取出来才能产生作用, 注解的提取是通过反射来实现。

    注解的提取主要有以下几种方式:

    • <A extends Annotation> getAnnotation(Class<A> annotationClass): 通过注解的class获取一个注解实例
     public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
            Objects.requireNonNull(annotationClass);
            return (Annotation)this.annotationData().annotations.get(annotationClass);
        }
    
    • Annotation[] getAnnotations() : 获取所有的注解
     public Annotation[] getAnnotations() {
            return AnnotationParser.toArray(this.annotationData().annotations);
        }
    
    • <A extends Annotation> A[] getAnnotationsByType(Class<A> annotationClass): 通过注解的class获取注解数组
      public <A extends Annotation> A[] getAnnotationsByType(Class<A> annotationClass) {
            Objects.requireNonNull(annotationClass);
            Class.AnnotationData annotationData = this.annotationData();
            return AnnotationSupport.getAssociatedAnnotations(annotationData.declaredAnnotations, this, annotationClass);
        }
    

    同时,还有几个方法,判断是否存在对应的注解对象:

    • boolean isAnnotationPresent(Class<? extends Annotation> annotationClass): 判断是不是指定注解
      public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
            return super.isAnnotationPresent(annotationClass);
        }
    
    • boolean isAnnotation(): 判断是不是注解
      public boolean isAnnotation() {
            return (this.getModifiers() & 8192) != 0;
        }
    

    Example:

    @Documented
    @Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyAnnotationType {
        String business();
    
        long minValue() default 10L;
    
        long maxValue() default 10000L;
    }
    
    @MyAnnotationType(business = "user")
    public class Test4 {
        @MyAnnotationType(business = "user", minValue = 100L)
        private long value;
    
        @MyAnnotationType(business = "user", minValue = 50L)
        public long getValue(){
            return value;
        }
    
        public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {
            boolean isMyAnnotationType = Test4.class.isAnnotationPresent(MyAnnotationType.class);
            if(isMyAnnotationType){
                MyAnnotationType myAnnotationType = Test4.class.getAnnotation(MyAnnotationType.class);
                System.out.println(myAnnotationType.business());
            }
    
            Field field = Test4.class.getDeclaredField("value");
            if(field != null){
                MyAnnotationType fieldAnnotationType = field.getAnnotation(MyAnnotationType.class);
                System.out.println(fieldAnnotationType.minValue());
            }
    
            Method method = Test4.class.getDeclaredMethod("getValue");
            if(method!=null){
                MyAnnotationType[] myAnnotationTypes = method.getAnnotationsByType(MyAnnotationType.class);
                for(MyAnnotationType annotationType: myAnnotationTypes){
                    System.out.println(annotationType.maxValue());
                }
            }
        }
    }
    
    

    输出:


    image.png

    这个样例比较简单, 可以参考: https://www.jianshu.com/p/e374f938278c

    3、 注解处理器

    注解处理器(Annotation Processor)是javac的一个工具, 注解处理器用来在编译时扫描和处理注解。
    Java有默认的注解处理器, 使用者也可以自定义注解处理器, 注册后使用注解处理器注解, 并最终达到注解本身起到的作用。

    注解处理器的作用:

    • 添加编译规则, 做编译检查, 如@Override。
    • 修改已有的Java源文件, 如javaagent破解软件。
    • 生成新的Java源文件。

    3.1、注解处理器的原理

    image.png

    如上图所示, Java源代码的编译经过了3个步骤:

    1. 将源文件解析为抽象语法树。
    2. 调用已经注册的注解处理器。
    3. 生成字节码。

    如果在第二步调用注解处理器生成了新的源文件, 那么编译器将重复第1、2步, 解析并处理新生成的源文件。每次重复我们称之为1轮(Round)。每一轮的输入都是已有的源文件, 循环往复,最终直到没有新的源文件产生, 全部处理成字节码。

    Java中, 所有的注解处理器(Annotation Processor)都要实现javax.annotation.processing.Processor接口:

    package javax.annotation.processing;
    
    import java.util.Set;
    import javax.lang.model.SourceVersion;
    import javax.lang.model.element.AnnotationMirror;
    import javax.lang.model.element.Element;
    import javax.lang.model.element.ExecutableElement;
    import javax.lang.model.element.TypeElement;
    
    public interface Processor {
        Set<String> getSupportedOptions();
    
        Set<String> getSupportedAnnotationTypes();
    
        SourceVersion getSupportedSourceVersion();
    
        void init(ProcessingEnvironment var1);
    
        boolean process(Set<? extends TypeElement> var1, RoundEnvironment var2);
    
        Iterable<? extends Completion> getCompletions(Element var1, AnnotationMirror var2, ExecutableElement var3, String var4);
    }
    
    

    该接口有4个重要的方法:

    • init(ProcessingEnviroment var1):
      init方法用来存放注解处理器的初始化代码。之所以不用构造器,是因为在Java编译器中, 注解处理器的实例是通过反射生成的。也正是因为反射API, 每个注解处理器都需要定义一个无参构造器。ProcessingEnviroment提供很多有用的工具类Elements, Types和Filer。

    • getSupportedAnnotationTypes()
      getSupportedAnnotationTypes方法返回该注解处理器支持的注解类型。 它的返回值是一个注解类型的集合, 包含本处理器支持处理的注解类的合法全称, 代表本注解处理器可以处理哪些注解。

    • getSupportedSourceVersion()
      指定注解处理器支持的Java版本, 一般返回SourceVersion.latestSupported()。通常这个版本和所使用Java编译器的版本保持一致。

    • process(Set<? extends TypeElement> var1, RoundEnvironment var2)
      process方法是最为关键的注解处理方法,相当于注解处理器的main()函数。扫描、评估和处理注解的代码, 以及生成Java源文件。 第一个参数 annotations 就是当前注解处理器支持的注解集合,第二个 roundEnv 表示当前的 APT 环境,其中提供了很多API,可以通过它获取到注解元素的一些信息。其中最重要的就是 getElementsAnnotatedWith 方法,通过它可以获取到所有使用了该注解的元素。

    3.2、注解处理器的实现
    JDK提供了一个实现Processor接口的抽象类AbstractProcessor。该抽象类实现了init、getSupportedAnnotationTypes、getSupportedSourceVersion3个方法。它的子类可通过注解SupportedAnnotationTypes、SupportedSourceVersion注解来声明注解处理器支持的注解类和Java版本。

    我们尝试实现一个注解处理器, 这里定义了一个Getter注解, 但是实际上我们并没有实现具体的处理逻辑,只是试验以下整个过程。
    3.2.1、 实现一个注解

    package org.example.good.java.annotation.annotation.lombok;
    
    import java.lang.annotation.*;
    
    @Documented
    @Inherited
    @Target({ElementType.FIELD, ElementType.TYPE})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Getter {
    }
    

    3.2.2、 实现注解处理器

    package org.example.good.java.annotation.processor.lombok;
    
    import javax.annotation.processing.*;
    import javax.lang.model.SourceVersion;
    import javax.lang.model.element.Element;
    import javax.lang.model.element.TypeElement;
    import javax.tools.Diagnostic;
    import java.util.Set;
    
    
    @SupportedAnnotationTypes({"org.example.good.java.annotation.annotation.lombok.Getter"})
    @SupportedSourceVersion(SourceVersion.RELEASE_8)
    public class LombokProcessor extends AbstractProcessor {
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnv) {
            super.init(processingEnv);
            Messager messager = processingEnv.getMessager();
            messager.printMessage(Diagnostic.Kind.NOTE, "LombokProcessor ...... init.");
    
        }
    
        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
            Messager messager = processingEnv.getMessager();
            messager.printMessage(Diagnostic.Kind.NOTE, String.format("annotation size: %d", set.size()));
    
            if(set.isEmpty()){
                return false;
            }
    
            for(TypeElement annotation: set) {
                Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(annotation);
                messager.printMessage(Diagnostic.Kind.NOTE, String.format("Annotation %s has annotated %d class.",
                        annotation.getSimpleName(), elements.size()));
                for(Element element: elements){
                    messager.printMessage(Diagnostic.Kind.NOTE, String.format("Annotated class: %s.", element.getSimpleName()));
                }
            }
            return true;
        }
    }
    
    
    • messager是一个工具类, 用来打印日志。
    • process方法中,参数set是TypeElement的集合, 代表待处理的注解, 它有多个子类, 分别代表类、方法、字段等。
    • process方法中, RoundEnvironment 参数可以通过注解获取被该注解修饰的元素。
    • 可以通过SupportedAnnotationTypes注解定义注解处理器支持处理的注解, 是一个数组。
    • 可以通过SupportedSourceVersion注解定义注解处理器支持处理的Java版本。

    3.2.3、定义一个被注解的类

    package org.example.good.java.model.vo;
    
    import org.example.good.java.annotation.annotation.lombok.Getter;
    
    @Getter
    public class Person {
        private String name;
        private int age;
    
        public Person(){}
    
        public Person(String name, int age){
            this.name = name;
            this.age = age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    

    3.2.4 定义Main类

    package org.example.good.java;
    
    import org.example.good.java.model.vo.Person;
    
    public class Main {
        public static void main(String[] args) {
            Person person = new Person();
            person.setName("jack");
            person.setAge(1);
            System.out.println(person);
        }
    }
    
    

    至此, 一个注解处理器的开发部分就完成了。

    3.3、注解处理器的注册
    注解处理器使用前需要先注册, 需要注意的是注解处理器在使用过程中,需要进行:
    1)首先编译注解处理器源代码
    2)使用注解处理器作为编译参数编译应用程序源代码
    3)运行编译好的应用程序
    否则会报出注解处理器找不到的错误:

    服务配置文件不正确, 或构造处理程序对象javax.annotation.processing.Processor: Provider org.example.good.java.annotation.processor.lombok.LombokProcessor not found时抛出异常错误
    

    注解处理器的注册方法有多种, 以上面的代码为例,分别叙述。

    3.3.1、通过javac命令行注册

    1. 编译注解处理器
      编译命令: javac -encoding UTF-8 -d dst processor路径
    javac -encoding UTF-8 -d out\production  src\mai
    n\java\org\example\good\java\annotation\processor\lombok\LombokProcessor.java
    

    此时,会在目标目录下生成processor的class文件, 以我的为例:


    image.png
    1. 利用注解处理器编译应用程序源代码
      编译命令:javac -encoding UTF-8 -cp processor编译好的代码位置 -processor processor类路径 -s 源代码路径 需要编译的应用程序源代码位置
    javac -encoding UTF-8 -cp out\production -processor org.example.good.java.annotation.processor.lombok.LombokProcessor -d out\
    production -s src\main\java src\main\java\org\example\good\java\model\vo\Person.java src\main\java\org\example\good\java\Main.java src\main\java\org\example\good\java\annot
    ation\annotation\lombok\Getter.java
    

    编译过程中, 会输出注解处理器中的打印日志, 以我的为例:


    image.png

    分别输出了LombokProcessor过程中init和process方法里面打印的日志内容。

    编译完成后, 整体目录如下:


    image.png
    1. 执行应用程序
    java -cp out\production org.example.good.java.Main
    

    执行输出:


    image.png

    由于我们在process方法并没有真的实现业务操作, 所以这块整体的结果和没有被注解修饰一样, 后续可以考虑实现类似lombok的Get方法。

    3.3.2、通过spi配置注册
    SPI全称Service Provider Interface, 是一种服务发现机制。通过在classpath路径下的META-INF/services文件夹查找文件, 自动加载文件里定义的类。

    这一机制为很多框架扩展提供了可能, 比如在Dubbo、JDBC都使用到了该机制。

    通过该方法注解处理器时需要预先将编译好的注解处理器的class文件压缩入java包中,并在jar包的配置文件中记录该注解处理器的保命和类名, 配置方式为在resources文件中创建META-INF/services目录, 然后在该目录下创建javax.annotation.processing.Processor文件, 该文件中记录的为自定义注解处理器的全限定类名。

    如图:


    image.png
    image.png

    3.3.2.1、定义及注解处理器
    1.创建maven工程
    定义一个maven工程,用来定义注解及注解处理器。

    image.png
    1. 定义注解
      定义一个注解@Getter, 此处声明注解的修饰目标时类、枚举、接口、字段等, 保留阶段为源码阶段。此处并未定义注解的成员, 这个注解用来为pojo的字段生成get方法。


      image.png
    
    @Documented
    @Inherited
    @Target({ElementType.FIELD, ElementType.TYPE})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Getter {
    }
    
    
    1. 定义注解处理器
      该注解处理器作为示例, 并未实现真正的处理逻辑。仅在process方法中打印出被注解的成员。
    
    @SupportedAnnotationTypes({"org.good.annotation.Getter"})
    @SupportedSourceVersion(SourceVersion.RELEASE_11)
    public class LombokProcessor extends AbstractProcessor {
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnv) {
            super.init(processingEnv);
            Messager messager = processingEnv.getMessager();
            messager.printMessage(Diagnostic.Kind.NOTE, "LombokProcessor ...... init.");
    
        }
    
        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
            Messager messager = processingEnv.getMessager();
            messager.printMessage(Diagnostic.Kind.NOTE, String.format("annotation size: %d", set.size()));
    
            if(set.isEmpty()){
                return false;
            }
    
            for(TypeElement annotation: set) {
                Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(annotation);
                messager.printMessage(Diagnostic.Kind.NOTE, String.format("Annotation %s has annotated %d class.",
                        annotation.getSimpleName(), elements.size()));
                for(Element element: elements){
                    messager.printMessage(Diagnostic.Kind.NOTE, String.format("Annotated class: %s.", element.getSimpleName()));
                }
            }
            return true;
        }
    }
    
    

    4、注册注解处理器
    在resources中, 创建META-INF/services目录, 并在该目录下创建对应的javax.annotation.processing.Procesor文件, 该文件中写入需要注册的注解处理器全限定类名。

    image.png

    5、 使用maven工具进行编译该工程
    直接编译会发生错误, 报告注解处理器找不到, 因为配置的注解处理器是要在编译阶段使用javax.annotation.processing.Procesor中定义的注解处理器去处理注解, 显然这个时候注解处理器还未编译好。

    image.png
    服务配置文件不正确, 或构造处理程序对象javax.annotation.processing.Processor: Provider org.good.processor.LombokProcessor not found时抛出异常错误
    
    

    这个时候,我们需要显示的告诉javac为当前项目忽略annotation processor, 如下:

       <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.1</version>
                    <configuration>
                        <proc>none</proc>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    

    再执行编译就编译成功了。


    image.png

    3.3.2.2 使用注解处理器

    1. 创建maven工程


      image.png
    2. 创建注解的使用

    package org.good.example.core;
    
    import org.good.annotation.Getter;
    
    @Getter
    public class Person {
        private String name;
        private int age;
    
        public Person(){}
    
        public Person(String name, int age){
            this.name = name;
            this.age = age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    
    1. 配置注解处理器
    <dependencies>
            <dependency>
                <groupId>org.example</groupId>
                <artifactId>good-java-annotation</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.1</version>
    
                    <configuration>
                        <annotationProcessorPaths>
                            <path>
                                <groupId>org.example</groupId>
                                <artifactId>good-java-annotation</artifactId>
                                <version>1.0-SNAPSHOT</version>
                            </path>
                        </annotationProcessorPaths>
                        <showWarnings>true</showWarnings>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
    1. 执行编译
      需要注意,pom.xml中需要配置, 要不然注解处理器中的日志打印不出来。其次注解处理器中日志若设置为Error级别, 会认为编译失败。
     <configuration>
          <showWarnings>true</showWarnings>
     </configuration>
    
    image.png

    可以看到注解处理器中定义的日志打印出来了, 并且识别到Person类被@Getter注解了。由于注解处理器中并没有实现真正添加get方法的逻辑, 所以显示不出效果, 以后考虑在实现。

    3.3.3、 通过google autoservice实现注解处理器的注册
    Google Service的实现原理和spi一样, Geoole Service通过@AutoService注解帮开发者实现了编写META-INF/services的过程, 它的底层原理也是应用的SPI 的规范, 实现了一个AutoServiceProcessor编译阶段获取被@AutoService注解的注解处理器, 自动生成相应的services文件。

    1. 定义注解@Getter
    2. 定义注解处理器
    @AutoService(Processor.class)
    @SupportedAnnotationTypes({"org.good.annotation.Getter"})
    @SupportedSourceVersion(SourceVersion.RELEASE_11)
    public class LombokProcessor extends AbstractProcessor {
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnv) {
            super.init(processingEnv);
            Messager messager = processingEnv.getMessager();
            messager.printMessage(Diagnostic.Kind.NOTE, "LombokProcessor ...... init.");
    
        }
    
        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
            Messager messager = processingEnv.getMessager();
            messager.printMessage(Diagnostic.Kind.NOTE, String.format("annotation size: %d", set.size()));
    
            if(set.isEmpty()){
                return false;
            }
    
            for(TypeElement annotation: set) {
                Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(annotation);
                messager.printMessage(Diagnostic.Kind.NOTE, String.format("Annotation %s has annotated %d class.",
                        annotation.getSimpleName(), elements.size()));
                for(Element element: elements){
                    messager.printMessage(Diagnostic.Kind.NOTE, String.format("Annotated class: %s.", element.getSimpleName()));
                }
            }
            return true;
        }
    }
    
    1. 配置注解处理器
    <dependencies>
            <dependency>
                <groupId>com.googe.auto.service</groupId>
                <artifactId>auto-service</artifactId>
                <version>1.0</version>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.1</version>
    
                    <configuration>
                        <annotationProcessorPaths>
                            <path>
                                 <groupId>com.googe.auto.service</groupId>
                                 <artifactId>auto-service</artifactId>
                                 <version>1.0</version>
                            </path>
                        </annotationProcessorPaths>
                        <showWarnings>true</showWarnings>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
    1. 编译
      编译后的效果和使用spi编译的效果一致。

    4、总结与实践

    Java中的注解和Python的装饰器作用差不多, Java注解主要通过字节码和反射来影响应用程序的行为。 注解的作用主要影响编译和运行两个阶段, 编译器注解可以通过注册注解处理器来进行代码检查、字节码修改生成, 运行期注解则通过反射添加额外的业务逻辑。

    5、参考文档

    相关文章

      网友评论

          本文标题:Java注解

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