Bootstrap

8.spring系列- java注解

问题

  1. 注解是干什么的?
  2. 一个注解可以使用多次吗?如何使用?
  3. @Inherited是做什么的?
  4. @Target中的TYPE_PARAMETER和TYPE_USER用在什么地方?
  5. 泛型中如何使用注解?
  6. 注解定义可以实现继承吗?
  7. spring对注解有哪些增强?@Aliasfor注解是干什么的?

什么是注解

代码中注释大家都熟悉吧,注释是给开发者看的,可以提升代码的可读性和可维护性,但是对于java编译器和虚拟机来说是没有意义的,编译之后的字节码文件中是没有注释信息的;而注解和注释有点类似,唯一的区别就是注释是给人看的,而注解是给编译器和虚拟机看的,编译器和虚拟机在运行的过程中可以获取注解信息,然后可以根据这些注解的信息做各种想做的事情。比如:大家对@Override应该比较熟悉,就是一个注解,加在方法上,标注当前方法重写了父类的方法,当编译器编译代码的时候,会对@Override标注的方法进行验证,验证其父类中是否也有同样签名的方法,否则报错,通过这个注解是不是增强了代码的安全性。

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

注解语法

public @interface MyAnnotation {
}

注解中定义的参数

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

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

  1. 访问修饰符必须为public,不写默认为public
  2. 该元素的类型只能是基本数据类型、String、Class、枚举类型、注解类型(体现了注解的嵌套效果)以及上述类型的一位数组
  3. 该元素的名称一般定义为名词,如果注解中只有一个元素,请把名字起为value(后面使用会带来便利操作)
  4. 参数名称后面的()不是定义方法参数的地方,也不能在括号中定义任何参数,仅仅只是一个特殊的语法
  5. default代表默认值,值必须和第2点定义的类型一致
  6. 如果没有默认值,代表后续使用注解时必须给该类型元素赋值

@Target 指定注解的使用范围

看一下@Target源码:

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

继续看一下ElementType源码:

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

指定注解的保留策略:@Retention

java程序的3个过程

  1. 源码阶段
  2. 源码被编译为字节码之后变成class文件
  3. 字节码被虚拟机加载然后运行

那么自定义注解会保留在上面的哪个阶段,则是有@Retention注解来指定。

看下@Retention源码

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

RetentionPolicy源码:

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

综合案例

@Target(value = {
        ElementType.TYPE,
        ElementType.METHOD,
        ElementType.FIELD,
        ElementType.PARAMETER,
        ElementType.CONSTRUCTOR,
        ElementType.LOCAL_VARIABLE
})
@Retention(RetentionPolicy.RUNTIME)
@interface Ann6 {
    String value();

    ElementType elementType();
}

@Ann6(value = "我用在类上", elementType = ElementType.TYPE)
public class UseAnnotation6 {
    @Ann6(value = "我用在字段上", elementType = ElementType.FIELD)
    private String a;

    @Ann6(value = "我用在构造方法上", elementType = ElementType.CONSTRUCTOR)
    public UseAnnotation6(@Ann6(value = "我用在方法参数上", elementType = ElementType.PARAMETER) String a) {
        this.a = a;
    }

    @Ann6(value = "我用在了普通方法上面", elementType = ElementType.METHOD)
    public void m1() {
        @Ann6(value = "我用在了本地变量上", elementType = ElementType.LOCAL_VARIABLE) String a;
    }
}

@Target(ElementType.TYPE_PARAMETER)

@Target(value = {
        ElementType.TYPE_PARAMETER
})
@Retention(RetentionPolicy.RUNTIME)
@interface Ann7 {
    String value();
}

public class UseAnnotation7<@Ann7("T0是在类上声明的一个泛型类型变量") T0, @Ann7("T1是在类上声明的一个泛型类型变量") T1> {

    public <@Ann7("T2是在方法上声明的泛型类型变量") T2> void m1() {
    }

    public static void main(String[] args) throws NoSuchMethodException {
        for (TypeVariable typeVariable : UseAnnotation7.class.getTypeParameters()) {
            print(typeVariable);
        }

        for (TypeVariable typeVariable : UseAnnotation7.class.getDeclaredMethod("m1").getTypeParameters()) {
            print(typeVariable);
        }
    }

    private static void print(TypeVariable typeVariable) {
        System.out.println("类型变量名称:" + typeVariable.getName());
        Arrays.stream(typeVariable.getAnnotations()).forEach(System.out::println);
    }
}

运行效果:

类型变量名称:T0
@com.javacode2018.lesson001.demo18.Ann7(value=T0是在类上声明的一个泛型类型变量)
类型变量名称:T1
@com.javacode2018.lesson001.demo18.Ann7(value=T1是在类上声明的一个泛型类型变量)
类型变量名称:T2
@com.javacode2018.lesson001.demo18.Ann7(value=T2是在方法上声明的泛型类型变量)

AnnotatedElement常用方法

 // 该元素如果存在指定类型的注解,则返回这些注解
 <T extends Annotation> T getAnnotation(Class<T> annotationClass);
 // 返回此元素上存在的所有注解,包含从父类继承的
 Annotation[] getAnnotations();
 // 判断此元素上是否存在指定类型的注解
 default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
        return getAnnotation(annotationClass) != null;
  }
// 返回直接存在于此元素上的所有注解,不包括父类的注解
 Annotation[] getDeclaredAnnotations();

案例

两个自定义注解:

@Target({ElementType.PACKAGE,
        ElementType.TYPE,
        ElementType.FIELD,
        ElementType.CONSTRUCTOR,
        ElementType.METHOD,
        ElementType.PARAMETER,
        ElementType.TYPE_PARAMETER,
        ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Ann1 {

    public String value();
}
@Target({ElementType.PACKAGE,
        ElementType.TYPE,
        ElementType.FIELD,
        ElementType.CONSTRUCTOR,
        ElementType.METHOD,
        ElementType.PARAMETER,
        ElementType.TYPE_PARAMETER,
        ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Ann2 {

    public int value();
}

注解标注的类:

@Ann1("用在类上")
@Ann2(0)
public class UseAnnotation1<@Ann1("用在变量类型") T0, @Ann2(12) T1> {

    @Ann1("用在字段上")
    @Ann2(2)
    private String name;

    private Map<@Ann1("用在泛型上,String") @Ann2(3) String, @Ann1("用在泛型上,Integer") @Ann2(4) Integer> map;

    @Ann1("用在构造器上")
    @Ann2(5)
    public UseAnnotation1() {

    }

    @Ann1("用在了返回值上")
    @Ann2(6)
    public String m1(@Ann1("用在了参数上") @Ann2(7) String name) {
        return null;
    }
}

测试方法:

@Test
    public void testAnnotation() throws NoSuchFieldException, NoSuchMethodException {
        Annotation[] annotations = UseAnnotation1.class.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }

        System.out.println("------------------");

        TypeVariable<Class<UseAnnotation1>>[] typeParameters = UseAnnotation1.class.getTypeParameters();
        for (TypeVariable<Class<UseAnnotation1>> typeParameter : typeParameters) {
            Annotation[] annotations1 = typeParameter.getAnnotations();
            for (Annotation annotation : annotations1) {
                System.out.println(annotation);
            }
        }

        System.out.println("------------------");

        Field name = UseAnnotation1.class.getDeclaredField("name");
        Annotation[] annotations1 = name.getAnnotations();
        for (Annotation annotation : annotations1) {
            System.out.println(annotation);
        }

        System.out.println("-----------------");

        Field field = UseAnnotation1.class.getDeclaredField("map");
        Type genericType = field.getGenericType();
        Type[] actualTypeArguments = ((ParameterizedType) genericType).getActualTypeArguments();
        AnnotatedType annotatedType = field.getAnnotatedType();
        AnnotatedType[] annotatedActualTypeArguments = ((AnnotatedParameterizedType) annotatedType).getAnnotatedActualTypeArguments();
        int i = 0;
        for (AnnotatedType actualTypeArgument : annotatedActualTypeArguments) {
            Type actualTypeArgument1 = actualTypeArguments[i++];
            String typeName = actualTypeArgument1.getTypeName();
            System.out.println(typeName);
            for (Annotation annotation : actualTypeArgument.getAnnotations()) {
                System.out.println(annotation);
            }
        }

        System.out.println("-----------------");

        Constructor<?> constructor = UseAnnotation1.class.getConstructors()[0];
        for (Annotation annotation : constructor.getAnnotations()) {
            System.out.println(annotation);
        }

        System.out.println("-----------------");

        Method method = UseAnnotation1.class.getMethod("m1", String.class);
        for (Annotation annotation : method.getAnnotations()) {
            System.out.println(annotation);
        }

        System.out.println("-----------------");

        Parameter[] parameters = method.getParameters();
        for (Parameter parameter : parameters) {
            Annotation[] annotations2 = parameter.getAnnotations();
            for (Annotation annotation : annotations2) {
                System.out.println(annotation);
            }
        }
    }

运行结果:

@com.spring.annotation.Ann1(value=用在类上)
@com.spring.annotation.Ann2(value=0)
------------------
@com.spring.annotation.Ann1(value=用在变量类型)
@com.spring.annotation.Ann2(value=12)
------------------
@com.spring.annotation.Ann1(value=用在字段上)
@com.spring.annotation.Ann2(value=2)
-----------------
java.lang.String
@com.spring.annotation.Ann1(value=用在泛型上,String)
@com.spring.annotation.Ann2(value=3)
java.lang.Integer
@com.spring.annotation.Ann1(value=用在泛型上,Integer)
@com.spring.annotation.Ann2(value=4)
-----------------
@com.spring.annotation.Ann1(value=用在构造器上)
@com.spring.annotation.Ann2(value=5)
-----------------
@com.spring.annotation.Ann1(value=用在了返回值上)
@com.spring.annotation.Ann2(value=6)
-----------------
@com.spring.annotation.Ann1(value=用在了参数上)
@com.spring.annotation.Ann2(value=7)

@Inherited : 实现类之间的注解继承

看一下这个注解的源码:

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

我们通过@Target元注解的属性值可以看出,这个@Inherited 是专门修饰注解的。
作用:让子类可以继承父类中被@Inherited修饰的注解,注意是继承父类中的,如果接口中的注解也使用@Inherited修饰了,那么接口的实现类是无法继承这个注解的

案例

public class InheritAnnotationTest {

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @interface A1{}

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @interface A2{}

    @A1
    interface I1{}

    @A2
    class C1{}

    class M extends C1 implements I1{}

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

运行结果:

@com.spring.annotation.InheritAnnotationTest$A2()

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

@Repeatable重复使用注解

如果我们想重复使用注解的时候,需要用到@Repeatable注解

使用步骤

  1. 先定义容器注解
@Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE, ElementType.FIELD})
    @Repeatable(A1s.class)
    @interface A1 {
        String name();
    }
  1. 为注解指定容器
//容器注解中必须有个value类型的参数,参数类型为子注解类型的数组。
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE, ElementType.FIELD})
    @interface A1s {
        A1[] value();
    }
  1. 使用注解
@A1(name = "我是类上的第一个A1注解")
    @A1(name = "我是类上的第二个A1注解")
    public class RepeatableTest {

        @A1s(
                {@A1(name = "我是变量上的第一个A1S注解"),
                        @A1(name = "我是变量上的第一个A1S注解")
                })
        private String V1;
    }

测试:

public static void main(String[] args) throws NoSuchFieldException {
        Annotation[] annotations = RepeatableTest.class.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }

        System.out.println("------------------");

        Field name = RepeatableTest.class.getDeclaredField("V1");
        Annotation[] annotations1 = name.getAnnotations();
        for (Annotation annotation : annotations1) {
            System.out.println(annotation);
        }
    }

运行结果:

@com.spring.annotation.RepeatableAnnTest$A1s(value=[@com.spring.annotation.RepeatableAnnTest$A1(name=我是类上的第一个A1注解), @com.spring.annotation.RepeatableAnnTest$A1(name=我是类上的第二个A1注解)])
------------------
@com.spring.annotation.RepeatableAnnTest$A1s(value=[@com.spring.annotation.RepeatableAnnTest$A1(name=我是变量上的第一个A1S注解), @com.spring.annotation.RepeatableAnnTest$A1(name=我是变量上的第一个A1S注解)])
Disconnected from the target VM, address: '127.0.0.1:59466', transport: 'socket'

我们先看一个案例

public class AliasForAnnTest {

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

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @A1
    @interface B1 {
        String value() default "b";
    }

    @B1("b1")
    public class Ann {
    }

    public static void main(String[] args) {
        A1 mergedAnnotation = AnnotatedElementUtils.getMergedAnnotation(Ann.class, A1.class);
        System.out.println(mergedAnnotation);
        
        System.out.println("------------");
        
        B1 mergedAnnotation1 = AnnotatedElementUtils.getMergedAnnotation(Ann.class, B1.class);
        System.out.println(mergedAnnotation1);
    }
}

运行结果:

@com.spring.annotation.AliasForAnnTest$A1(value=a)
------------
@com.spring.annotation.AliasForAnnTest$B1(value=b1)

代码很简单,没有什么问题。如果我想在Ann类上给注解A1设置值怎么办呢?@Aliasfor可以实现

@Aliasfor 案例

在上面的案例上直接修改B1:

@Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @A1
    @interface B1 {
        String value() default "b";
		//添加一个@AliasFor 注解,指定注解类和注解属性名
        @AliasFor(annotation = A1.class, value = "value")
        String a1Value();
    }

Ann类修改:

@B1(value = "b1" , a1Value = "a1")
    public class Ann {
    }

其他不变,运行结果:

@com.spring.annotation.AliasForAnnTest$A1(value=a1)
------------
@com.spring.annotation.AliasForAnnTest$B1(a1Value=a1, value=b1)

在来一个案例

public class AliasForAnn {

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE, ElementType.FIELD})
    @interface A1 {

        @AliasFor(value = "v2")
        String v1() default "";

        @AliasFor(value = "v1")
        String v2() default "";
    }

    @A1(v1 = "类 v1")
    public class Test {

        @A1(v2 = "属性 v2")
        private String name;
    }

    public static void main(String[] args) throws NoSuchFieldException {
        A1 mergedAnnotation = AnnotatedElementUtils.getMergedAnnotation(Test.class, A1.class);
        System.out.println(mergedAnnotation);

        System.out.println("--------");

        A1 name = AnnotatedElementUtils.getMergedAnnotation(Test.class.getDeclaredField("name"), A1.class);
        System.out.println(name);
    }
}

运行结果:

@com.spring.annotation.AliasForAnn$A1(v1=类 v1, v2=类 v1)
--------
@com.spring.annotation.AliasForAnn$A1(v1=属性 v2, v2=属性 v2)

我们看下@AliasFor的源码:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface AliasFor {

    @AliasFor("attribute")
    String value() default "";

    @AliasFor("value")
    String attribute() default "";

    Class<? extends Annotation> annotation() default Annotation.class;

}

AliasFor注解中value和attribute互为别名,随便设置一个,同时会给另外一个设置相同的值。

;