注解的分类

1.自定义注解

public @interface 注解名 {
  public 属性类型 属性名() default 默认值;
}
  • 如果注解中只有一个value属性(特殊属性名),使用注解时,value名称可以不写!

例如:有一个注解:

public @interface MyTest1 {
    public String value();
}

可以写成:

@MyTest1("hello")
public class MyTestAnnotation {
}

如果再新增一个方法,且没有返回默认值:

public @interface MyTest1 {
    public String value();
    public String name();
}

那么用注解不能省略属性名:

@MyTest1(value = "hello", name = "world")
public class MyTestAnnotation {
}

通过编译上述的MyTest1.java文件我们可以看到:

  • 显而易见的,注解本质上就是一个接口,继承了java.lang.annotation.Annotation,这就是为什么内部的修饰符只能是public,即使不写也默认也是public,而接口方法的默认访问权限就是pubilc。注解是不能继承也不能实现其他类或接口的,它本身就是一个元数据。

  • @注解(...):其实就是一个实现类对象,实现了该注解以及Annotation接口。

2.元注解

  • 定义:用于定义注解的注解,通常用于注解的定义上,标明该注解的使用范围、生效范围等。

  • 包含以下五种:@Retention、@Target、@Documented、@Inherited、@Repeatable。

1.@Target注解★★★★★

  • 作用:声明被修饰的注解只能在哪些位置使用。

  • 定义如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation interface
     * can be applied to.
     * @return an array of the kinds of elements an annotation interface
     * can be applied to
     */
    ElementType[] value();
}

可见value是一个数组,可以有多个取值,说明同一个注解可以同时用于标注在不同的元素上。

同时value的值可取:

  • 这里定义一个MyTest2注解和一个MyTestAnnotation2类用于测试:

@Target({ElementType.TYPE, ElementType.CONSTRUCTOR})
public @interface MyTest2 {
}

@MyTest2
public class MyTestAnnotation2 {

    @MyTest2 //报错,'@MyTest2' not applicable to field
    private String name;

    @MyTest2
    public MyTestAnnotation2() {
    }
}

2.@Retention注解★★★★★

  • 作用:声明注解的保留周期。

  • 定义如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

从编写Java代码到运行主要周期为源文件Class文件运行时数据,@Retention则标注了自定义注解的信息要保留到哪个阶段,分别对应的value取值为SOURCECLASSRUNTIME

  • RetentionPolicy是个枚举类,有三种策略:

  1. SOURCE:只作用在源码阶段,字节码文件中不存在,生成的class文件中就没有该信息了。

  2. CLASS(默认值):class文件中会保留注解,但是jvm加载运行时就没有了。

  3. RUNTIME(开发常用):一直保留到运行阶段。

  • 下面通过例子来验证一下:

定义三种不同策略的注解和一个测试类,

@Retention(RetentionPolicy.SOURCE)
public @interface SourceTest {
}

@Retention(RetentionPolicy.CLASS)
public @interface ClassTest {
}

@Retention(RetentionPolicy.RUNTIME)
public @interface RuntimeTest {
}

public class MyTestAnnotation3 {

    @SourceTest
    public void sourceTest(){}

    @ClassTest
    public void classTest(){};

    @RuntimeTest
    public void runtimeTest(){};

}

通过反编译获得MyTestAnnotation3类的字节码:

public void sourceTest();
  descriptor: ()V
  flags: (0x0001) ACC_PUBLIC
  Code:
    stack=0, locals=1, args_size=1
       0: return
    LineNumberTable:
      line 6: 0
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       1     0  this   Lcom/annotation/MyTestAnnotation3;

public void classTest();
  descriptor: ()V
  flags: (0x0001) ACC_PUBLIC
  Code:
    stack=0, locals=1, args_size=1
       0: return
    LineNumberTable:
      line 8: 0
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       1     0  this   Lcom/annotation/MyTestAnnotation3;
  RuntimeInvisibleAnnotations:
    0: #17()
      com.annotation.ClassTest

public void runtimeTest();
  descriptor: ()V
  flags: (0x0001) ACC_PUBLIC
  Code:
    stack=0, locals=1, args_size=1
       0: return
    LineNumberTable:
      line 10: 0
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       1     0  this   Lcom/annotation/MyTestAnnotation3;
  RuntimeVisibleAnnotations:
    0: #20()
      com.annotation.RuntimeTest

我们可以看到:
1. 编译器并没有记录下sourceTest方法的注解信息
2. 编译器分别使用了RuntimeInvisibleAnnotations和RuntimeVisibleAnnotations属性去记录了classTest和runtimeTest的注解信息。

3.@Documented注解

  • 作用:生成文档信息的时候保留注解,对类作辅助说明。

  • 定义如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}

这里不在赘述,可以参考:java 注解示例(@Documented

4.@Inherited注解★★

  • 作用:当一个类使用了带有 @Inherited 注解的注解时,这个注解将被这个类的所有子类继承。

  • 这里需要注意的是只有当子类继承父类的时候,注解才会被继承,类实现接口,或者接口继承接口,都是无法获得父接口上的注解声明的。

  • 定义如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
  • 示例:

假设我们有一个注解 @InheritedTest,我们希望这个注解能够被继承,在定义一个使用该注解的父类People,然后子类Man继承People:

@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface InheritedTest {
}

@InheritedTest
public class People {
//
}

public class Man extends People{
//
}

再通过反射来检查注解是否被继承:

public class Man extends People{
    public static void main(String[] args) {
        Class<Man> manClass = Man.class;
        Annotation[] annotations = manClass.getAnnotations();
        for(Annotation annotation : annotations){
            System.out.println(annotation);
        }
    }
}
//输出:@com.annotation.InheritedTest()

5.@Repeatable注解★★

  • 作用:允许开发者在同一个程序元素上多次使用相同的注解。

  • 定义如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
    /**
     * Indicates the <em>containing annotation interface</em> for the
     * repeatable annotation interface.
     * @return the containing annotation interface
     */
    Class<? extends Annotation> value();
}
  • 这是java8之后引入的,在此之前,如果需要模拟重复注解的效果,可以使用注解的数组属性:

在注解中定义一个数组属性来存储多个值,这样就可以在单个注解实例中表示多个值。这种方法不需要额外的注解,但是它并不真正地重复注解本身,而是在单个注解中存储多个值。

模拟用户角色,这里定义了一个注解,其中包含一个角色名的数组:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Roles {
    Role[] value();
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Role {
    String value();
}

使用时可以这样:

@Roles({
        @Role("admin"),
        @Role("user")
})
public class Person {
    //
}
  • 引入之后,可以这么写:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Roles {
    Role[] value();  // 必须叫value,这是规定的属性名
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Roles.class)
public @interface Role {
    String value();
}

这样就可以在类上重复使用@Role

@Role("admin")
@Role("user")
public class Person {
    //
}

3.标准注解

JDK中内置了以下注解:

1. @Override:标记一个方法是覆写父类方法。

public class Parent {
    public void test() {
    }
}

public class Child extends Parent {
  @Override
  public void test() {
  }
}

2. @Deprecated:用于标明被修饰的类或类成员、类方法已经废弃、过时,不建议使用。

@Deprecated
class TestClass {
//
}

3.@SuppressWarnings:指示编译器去忽略注释解中声明的警告。

  • 抑制单类型的警告

@SuppressWarnings("unchecked")
public void test1(String item) {
    @SuppressWarnings("rawtypes")
    List<Object> l1 = new ArrayList();
    l1.add(item);
}
  • 抑制多类型的警告

@SuppressWarnings(value={"unchecked", "rawtypes"})
public void test2(String item) {
    List<Object> l2 = new ArrayList();
    l2.add(item);
}
  • 抑制所有类型的警告

@SuppressWarnings("all")
public void test3(String item) {
    List l3 = new ArrayList();
    l3.add(item);
}

4.@Functionallnterface:java8支持,标识一个匿名函数或函数式接口。

@FunctionalInterface
public interface ComplexFunctionalInterface {
    void doWork();

    default void defaultMethod() {
        System.out.println("This is a default method");
    }

    static void staticMethod() {
        System.out.println("This is a static method");
    }
}

public class Main {
    public static void main(String[] args) {
        ComplexFunctionalInterface cfi = () -> System.out.println("Doing work with Lambda expression");
        cfi.doWork();
        cfi.defaultMethod();
        ComplexFunctionalInterface.staticMethod();
    }
}

在这个例子中,ComplexFunctionalInterface是一个函数式接口,它包含一个抽象方法doWork(),一个默认方法defaultMethod()和一个静态方法staticMethod()。我们可以使用Lambda表达式来创建这个接口的实例,并调用这些方法。

注解的解析

  • 注解的解析,就是判断类上、方法上、成员变量上是否存在注解,并把注解里的内容给解析出来。

  • 想要对注解中的数据进行解析,需要借助:AnnotatedElement接口,Class、Method、Field,Constructor都实现了AnnotatedElement接口,它们都拥有解析注解的能力。

  • 注解解析的步骤:(注解是书写在:类、方法、变量上)

    1. 利用反射技术获取注解作用的对象:类、方法、变量、构造方法。

    2. 判断对象上是否有自定义注解存在。

    3. 有:获取对象上的自定义注解。

    4. 使用获取到的自定义注解对象,拿到注解中的属性值。

    注意:注解解析必须保证自定义注解生命周期在RUNTIME(程序运行中)

  • 示例:

  1. 定义注解MyTest4,要求如下:

    • 包含属性:String value()

    • 包含属性:double aaa(),默认值为100

    • 包含属性:String[] bbb()

    • 限制注解使用的位置:类和成员方法上

    • 指定注解的有效范围:一直到运行时

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest4 {
    String value();
    double aaa() default 100;
    String[] bbb();
}
  1. 定义一个类叫:Demo,在类中定义一个test1方法,并在该类和其方法上使用MyTest4注解.

@MyTest4(value = "沙威玛", aaa = 21.1, bbb = {"fantastic", "fabulous"})
public class Demo {
    @MyTest4(value = "汉堡包", aaa =12.1, bbb = {"good", "nice"})
    public void test1(){
    }
}
  1. 定义AnnotationTest3测试类,解析Demo类中的全部注解。

@Test
public void parseClass(){
    // 1.先得到Class对象
    Class demoClass = Demo.class;
    // 2.解析类上的注解
    // 判断类上是否包含某个注解
    if(demoClass.isAnnotationPresent(MyTest4.class)){
        MyTest4 myTest4 = (MyTest4) demoClass.getDeclaredAnnotation(MyTest4.class);
        System.out.println(myTest4.value()); // 输出:沙威玛
        System.out.println(myTest4.aaa()); // 输出:21.1
        System.out.println(Arrays.toString(myTest4.bbb())); // 输出:[fantastic, fabulous]
    }
}

@Test
public void parseMethod() throws NoSuchMethodException{
    // 1.先得到Class对象
    Class demoClass = Demo.class;
    Method method = demoClass.getDeclaredMethod("test1");
    // 2.解析方法上的注解
    // 判断方法对象上是否包含某个注解
    if(method.isAnnotationPresent(MyTest4.class)){
        MyTest4 myTest4 = method.getDeclaredAnnotation(MyTest4.class);
        System.out.println(myTest4.value()); // 输出:汉堡包
        System.out.println(myTest4.aaa()); // 输出:12.1
        System.out.println(Arrays.toString(myTest4.bbb())); // 输出:[good, nice]
    }
}

参考来自:10、Java级技术:注解