Java注解

注解是对代码的一种增强,可以在代码编译或者程序运行期间获取注解的信息,然后根据这 些信息做各种牛逼的事情。

定义注解

在定义注解前可以先问自己几个问题。

  1. 这个注解用来做什么 ?

  2. 这个注解需要什么参数 ?

  3. 注解需要可以用在哪里 ?

  4. 注解会被保留到什么时候 ?

  1. 可以直接加在后面

语法

public @interface MyAnnotation {}

注解中的参数

public @interface 注解名称{
    [public] 参数类型 参数名称1() [default 参数默认值];
    [public] 参数类型 参数名称2() [default 参数默认值];
    [public] 参数类型 参数名称n() [default 参数默认值];
}

注解中可以定义多个参数,参数的定义有一下的特点:

  1. 访问修饰符必须为 public,默认为 public 。

  2. 参数类型只能是基本数据类型、String、Class、枚举类型、注解类型。

  3. 参数名称后面的 () 不是定义方法参数的地方,也不能在括号始终定义如何参数

  4. default 代表默认值。

  5. 如果没有默认值,在使用注解的时候必须给该参数赋值。

@Target 指定使用范围

使用 @Target 注解定义注解的使用范围

@Target(value = {ElementType.TYPE,ElementType.METHOD})
public @interface MyAnnotation {}

上面这种定义说明可以使用在 类、接口、注解类型、枚举类型以及方法上面。

如果在指定要的注解上面不使用 @Target 表示可以使用在如何地方

全部范围

public enum ElementType {
    /* 类、接口、枚举、注解上面 */
    TYPE,
    /* 字段上 */
    FIELD,
    /* 方法上 */
    METHOD,
    /* 方法的参数上 */
    PARAMETER,
    /* 构造函数上 */
    CONSTRUCTOR,
    /* 本地变量上 */
    LOCAL_VARIABLE,
    /* 注解上 */
    ANNOTATION_TYPE,
    /* 包上 */
    PACKAGE,
    /* 类型参数上 */
    TYPE_PARAMETER,
    /* 类型名称上 */
    TYPE_USE
}

@Retention 保留策略

我们先来看一下 JAVA 程序的 3 个过程

  1. 源码阶段

  2. 源码被编译为字节码之后变成 class 文件

  3. 字节码被虚拟机加载然后运行

public enum RetentionPolicy {
	/* 注解只保留在源码中,编译为字节码之后就丢失了,也就是 class 文件中就不存在了 */
	SOURCE,
	/* 注解只保留在源码和字节码中,运行阶段会丢失 */
	CLASS,
	/* 源码、字节码、运行期间都存在 */
	RUNTIME
}

@Inherit 注解继承

让子类可以继承父类中被 @Inherited 修饰的注解,注意是继承父类中的,如果接口中的注解也使 用 @Inherited 修饰了,那么接口的实现类是无法继承这个注解的

实验

public class InheritTest {
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @interface A {}
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @interface B {}

    @A
    interface I {};
    @B
    static class C{}

    class TestClass extends C implements I { }

    public static void main(String[] args) {
        for (Annotation annotation : TestClass.class.getAnnotations()) {
            System.out.println(annotation);
        }
    }
}

输出

@InheritTest$B()

从输出中可以看出类可以继承父类上被 @Inherited 修饰的注解,而不能继承接口上被 @Inherited 修饰 的注解

@Repeatable 重复使用注解

定义

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(As1.class) // 2
@interface A {

};

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface As1 {
    A[] value(); // 1
}
  1. 容器注解中必须有个 value 类型的参数,参数类型为子注解类型的数组。

  2. 在注解上加上 @Repeatable 注解,@Repeatable 中 value 的值为容器注解。**注意这里容器注解的 Repeatable 一定要大于或等于原注解**

@AliasFor 注解进行增强

实验

public class AliasForTest {

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @interface A {
        String value() default "a";
    }

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @A
    @interface B {

        String value() default "b";

        @AliasFor(annotation = A.class, value = "value")

        String aValue();

    }

    @B(value = "xiaou", aValue = "123")
    class C {}

    @Test
    public void test1() {
System.out.println(AnnotatedElementUtils.getMergedAnnotation(C.class, B.class));
      System.out.println(AnnotatedElementUtils.getMergedAnnotation(C.class, A.class));

    }

}

输出结果

@org.test.AliasForTest.B(aValue="123", value="xiaou")

@org.test.AliasForTest.A(value="123")

从结果看出来 B 注解的 aValue = A 注解的 value 值为什么出现了这种情况关键在于这行代码。

// 指定 B 注解中 aValue 参数作为 A 中 value 参数的别名
@AliasFor(annotation = A.class, value = "value")
String aValue();

@AliasFor 注解的 annotation 参数指定的注解需要加载当前注解上面。