Java注解

一、什么是注解

注解也叫元数据, 例如我们常见的@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、参考文档

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容