目录

注解

关于注解首先引入官方文档的一句话:Java 注解用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。Java 注解是从 Java5 开始添加到 Java 的。看完这句话也许你还是一脸懵逼,接下我将从注解的定义、元注解、注解属性、自定义注解、注解解析、JDK 提供的注解这几个方面再次了解注解(Annotation)

定义

日常开发中新建Java类,我们使用class、interface比较多,而注解和它们一样,也是一种类的类型,他是用的修饰符为 @interface

https://gitee.com/lienhui68/picStore/raw/master/null/20200801152033.png

注解类的写法

  • 我们新建一个注解MyTestAnnotation
1
2
3
public @interface MyTestAnnotation {

}
  • 接着我们就可以在类或者方法上作用我们刚刚新建的注解
1
2
3
4
5
6
@MyTestAnnotation
public class test {
   @MyTestAnnotation
   public static void main(String[] args){
   }
}
  • 以上我们只是了解了注解的写法,但是我们定义的注解中还没写任何代码,现在这个注解毫无意义,要如何使注解工作呢?接下来我们接着了解元注解。

元注解

元注解顾名思义我们可以理解为注解的注解,它是作用在注解中,方便我们使用注解实现想要的功能。元注解分别有@Retention、 @Target、 @Document、 @Inherited和@Repeatable(JDK1.8加入)五种。

@Retention

  • Retention英文意思有保留、保持的意思,它表示注解存在阶段是保留在源码(编译期),字节码(类加载)或者运行期(JVM中运行)。在@Retention注解中使用枚举RetentionPolicy来表示注解保留时期
  • @Retention(RetentionPolicy.SOURCE),注解仅存在于源码中,在class字节码文件中不包含
  • @Retention(RetentionPolicy.CLASS), 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得
  • @Retention(RetentionPolicy.RUNTIME), 注解会在class字节码文件中存在,在运行时可以通过反射获取到
  • 如果我们是自定义注解,则通过前面分析,我们自定义注解如果只存着源码中或者字节码文件中就无法发挥作用,而在运行期间能获取到注解才能实现我们目的,所以自定义注解中肯定是使用 @Retention(RetentionPolicy.RUNTIME)
1
2
3
4
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTestAnnotation {

}

@Target

  • Target的英文意思是目标,这也很容易理解,使用@Target元注解表示我们的注解作用的范围就比较具体了,可以是类,方法,方法参数变量等,同样也是通过枚举类ElementType表达作用类型
  • @Target(ElementType.TYPE) 作用接口、类、枚举、注解
  • @Target(ElementType.FIELD) 作用属性字段、枚举的常量
  • @Target(ElementType.METHOD) 作用方法
  • @Target(ElementType.PARAMETER) 作用方法参数
  • @Target(ElementType.CONSTRUCTOR) 作用构造函数
  • @Target(ElementType.LOCAL_VARIABLE)作用局部变量
  • @Target(ElementType.ANNOTATION_TYPE)作用于注解(@Retention注解中就使用该属性)
  • @Target(ElementType.PACKAGE) 作用于包
  • @Target(ElementType.TYPE_PARAMETER) 作用于类型泛型,即泛型方法、泛型类、泛型接口 (jdk1.8加入)
  • @Target(ElementType.TYPE_USE) 类型使用.可以用于标注任意类型除了 class (jdk1.8加入)
  • 一般比较常用的是ElementType.TYPE类型
1
2
3
4
5
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyTestAnnotation {

}

@Document

Document的英文意思是文档。它的作用是能够将注解中的元素包含到 Javadoc 中去。

@Inherited

  • Inherited的英文意思是继承,但是这个继承和我们平时理解的继承大同小异,一个被@Inherited注解了的注解修饰了一个父类,如果他的子类没有被其他注解修饰,则它的子类也继承了父类的注解
  • 下面我们来看个@Inherited注解例子
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**自定义注解*/
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyTestAnnotation {
}
/**父类标注自定义注解*/
@MyTestAnnotation
public class Father {
}
/**子类*/
public class Son extends Father {
}
/**测试子类获取父类自定义注解*/
public class test {
   public static void main(String[] args){

      //获取Son的class对象
       Class<Son> sonClass = Son.class;
      // 获取Son类上的注解MyTestAnnotation可以执行成功
      MyTestAnnotation annotation = sonClass.getAnnotation(MyTestAnnotation.class);
   }
}

@Repeatable

Repeatable 自然是可重复的意思。@Repeatable 是 Java 1.8 才加进来的,所以算是一个新的特性。

什么样的注解会多次应用呢?通常是注解的值可以同时取多个。

举个例子,一个人他既是程序员又是产品经理,同时他还是个画家。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// 容器注解
@interface Persons {
    Person[]  value();
}

// @Repeatable 后面括号中的类相当于一个容器注解
@Repeatable(Persons.class)
@interface Person{
    String role default "";
}

// 重复使用Person注解
@Person(role="artist")
@Person(role="coder")
@Person(role="PM")
public class SuperMan{
}

注意上面的代码,@Repeatable 注解了 Person。而 @Repeatable 后面括号中的类相当于一个容器注解。

什么是容器注解呢?就是用来存放其它注解的地方。它本身也是一个注解。

我们再看看代码中的相关容器注解。

1
2
3
@interface Persons {
    Person[]  value();
}

按照规定,它里面必须要有一个 value 的属性,属性类型是一个被 @Repeatable 注解过的注解数组,注意它是数组。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
    /**
     * Indicates the <em>containing annotation type</em> for the
     * repeatable annotation type.
     * @return the containing annotation type
     */
    Class<? extends Annotation> value();
}

如果不好理解的话,可以这样理解。Persons 是一张总的标签,上面贴满了 Person 这种同类型但内容不一样的标签。把 Persons 给一个 SuperMan 贴上,相当于同时给他贴了程序员、产品经理、画家的标签。

我们可能对于 @Person(role=”PM”) 括号里面的内容感兴趣,它其实就是给 Person 这个注解的 role 属性赋值为 PM ,大家不明白正常,马上就讲到注解的属性这一块。

注解的属性

注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。

1
2
3
4
5
6
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
    int id();
    String msg();
}

注解成员变量赋值

上面代码定义了 TestAnnotation 这个注解中拥有 id 和 msg 两个属性。在使用的时候,我们应该给它们进行赋值。

赋值的方式是在注解的括号内以 param = value 形式,多个属性之前用 “,“隔开。

1
2
3
@TestAnnotation(id=3,msg="hello annotation")
public class Test {
}

注解中属性可以有默认值,默认值需要用 default 关键值指定。比如:

1
2
3
4
5
6
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
    public int id() default -1;
    public String msg() default "Hi";
}

TestAnnotation 中 id 属性默认值为 -1,msg 属性默认值为 Hi。 它可以这样应用。

1
2
@TestAnnotation()
public class Test {}

因为有默认值,所以无需要再在 @TestAnnotation 后面的括号里面进行赋值了,这一步可以省略。

另外,还有一种情况。如果一个注解内仅仅只有一个名字为 value 的属性时,应用这个注解时可以直接接属性值填写到括号内。

1
2
3
public @interface Check {
    String value();
}

上面代码中,Check 这个注解只有 value 这个属性。所以可以这样应用。

1
2
@Check("hi")
int a;

这和下面的效果是一样的

1
2
@Check(value="hi")
int a;

最后,还需要注意的一种情况是一个注解没有任何属性。比如

1
public @interface Perform {}

那么在应用这个注解的时候,括号都可以省略。

1
2
@Perform
public void testMethod(){}

注解的本质

注解的本质就是一个Annotation接口

1
2
3
4
5
6
/**Annotation接口源码*/
public interface Annotation {
    boolean equals(Object obj);
    int hashCode();
    Class<? extends Annotation> annotationType();
}

通过以上源码,我们知道注解本身就是Annotation接口的子接口,也就是说注解中其实是可以有属性和方法,但是接口中的属性都是static final的,对于注解来说没什么意义,而我们定义接口的方法就相当于注解的属性,也就对应了前面说的为什么注解只有属性成员变量,其实他就是接口的方法,这就是为什么成员变量会有括号,不同于接口我们可以在注解的括号中给成员变量赋值。

注解属性类型

注解属性类型可以有以下列出的类型

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

注解的提取

前面我们说了很多注解如何定义,放在哪,现在我们可以开始学习注解属性的提取了,这才是使用注解的关键,获取属性的值才是使用注解的目的。

如果获取注解属性,当然是反射啦,主要有三个基本的方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
/**是否存在对应 Annotation 对象*/
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
  return GenericDeclaration.super.isAnnotationPresent(annotationClass);
}

/**获取 Annotation 对象*/
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
  Objects.requireNonNull(annotationClass);
  return (A) annotationData().annotations.get(annotationClass);
}

/**获取所有 Annotation 对象数组*/   
public Annotation[] getAnnotations() {
  return AnnotationParser.toArray(annotationData().annotations);
}    

getAnnotations得到的是包括继承(@Inherited)的所有注释, getDeclaredAnnotations得到的是当前成员所有的注释,不包括继承的。

下面结合前面的例子,我们来获取一下注解属性,在获取之前我们自定义的注解必须使用元注解@Retention(RetentionPolicy.RUNTIME)

注解的提取演示

类注解

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package com.eh.ftd.annotations;

import java.lang.annotation.*;

/**
 * todo
 *
 * @author David Li
 * @create 2020/08/01 15:54
 */
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ClassAnnotation {
    int id();

    String name();
}

属性注解

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package com.eh.ftd.annotations;

import java.lang.annotation.*;

/**
 * todo
 *
 * @author David Li
 * @create 2020/08/01 15:59
 */
@Target(ElementType.FIELD)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface Limit {
    int bottom();

    int top();
}

再来个容器注解

Role

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package com.eh.ftd.annotations;

import java.lang.annotation.*;

/**
 * todo
 *
 * @author David Li
 * @create 2020/08/01 16:01
 */
@Repeatable(People.class)
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Role {
    String value() default "工程师";
}

People

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package com.eh.ftd.annotations;

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

/**
 * 容器注解
 *
 * @author David Li
 * @create 2020/08/01 16:02
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface People {
    Role[] value();
}

方法注解

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.eh.ftd.annotations;

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

/**
 * todo
 *
 * @author David Li
 * @create 2020/08/01 16:07
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogLevel {
    enum Level {
        INFO, DEBUG
    }

    Level value();
}

使用注解的类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.eh.ftd.annotations;

/**
 * todo
 *
 * @author David Li
 * @create 2020/08/01 16:09
 */
@ClassAnnotation(id = 1, name = "user")
public class User {

    public User(int age) {
        this.age = age;
    }

    @Limit(bottom = 0, top = 100)
    private int age;

    @Role
    @Role("运动员")
    @Role("画家")
    private String role;

    @LogLevel(LogLevel.Level.DEBUG)
    public void log() {
        System.out.println("this is a log");
    }

    public int getAge() {
        return age;
    }
}

注解处理类APT

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package com.eh.ftd.annotations;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * Annotation Processing Tool
 *
 * @author David Li
 * @create 2020/08/01 16:08
 */
public class AptDemo {
    public static void main(String[] args) {
        User user = new User(101);
        // 处理类注解
        Class c = user.getClass();
        System.out.println("获取类的注解");
        if (c.isAnnotationPresent(ClassAnnotation.class)) {
            ClassAnnotation classAnnotation = (ClassAnnotation) c.getAnnotation(ClassAnnotation.class);
            System.out.println("id:" + classAnnotation.id());
            System.out.println("name:" + classAnnotation.name());
        }

        // 处理属性注解
        System.out.println("获取字段的注解");
        Field[] fields = c.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            Limit limit = field.getAnnotation(Limit.class);

            if (limit != null) {
                if (user.getAge() > limit.top() || user.getAge() < limit.bottom()) {
                    System.err.println("年龄超过限制");
                } else {
                    System.out.println("年龄符合要求");
                }
            }

            // 注意这里使用容器注解接受重复注解
            People people = field.getAnnotation(People.class);
            if (people != null) {
                Role[] roles = people.value();
                System.out.println("用户角色有:");
                for (Role role : roles) {
                    System.out.println(role);
                }
            }
        }

        // 处理方法注解
        System.out.println("获取方法的注解");
        try {
            Method method = c.getMethod("log");
            LogLevel logLevel = method.getAnnotation(LogLevel.class);
            if (logLevel != null) {
                System.out.println("日志级别:" + logLevel);
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

    }
}

运行结果

https://gitee.com/lienhui68/picStore/raw/master/null/20200801163619.png

注解的作用

提供信息给编译器: 编译器可以利用注解来检测出错误或者警告信息,打印出日志。

编译阶段时的处理: 软件工具可以用来利用注解信息来自动生成代码、文档或者做其它相应的自动处理。

运行时处理: 某些注解可以在程序运行的时候接受代码的提取,自动做相应的操作。

正如官方文档的那句话所说,注解能够提供元数据,上面例子中处理获取注解值的过程是我们开发者直接写的注解提取逻辑,处理提取和处理 Annotation 的代码统称为 APT(Annotation Processing Tool)。上面转账例子中的main方法就可以理解为APT工具类。

参考

Java 注解完全解析