飞道的博客

朋友被阿里面试官灵魂拷问,跑来求救。。。

490人阅读  评论(0)

最近有个朋友去阿里面试,被面试官来了个灵魂拷问:

  1. 注解是干什么的?

  2. 一个注解可以使用多次么?如何使用?

  3. @Inherited是做什么的?

  4. @Target中的`TYPE_PARAMETER和TYPE_USER`用在什么地方?

  5. 泛型中如何使用注解?

  6. 注解定义可以实现继承么?

  7. spring中对注解有哪些增强?@Aliasfor注解是干什么的?

第1个他回答上来了,后面的几个直接懵了,然后就没有然后了。

之后跑来问我,然后我让他看本文,准备下次去吊打面试官。

本文内容

带你玩转java注解,解决上面的所有问题。

什么是注解?

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

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

注解如何使用?

3个步骤:

  1. 定义注解

  2. 使用注解

  3. 获取注解信息做各种牛逼的事情

定义注解

关于注解的定义,先来几个问题:

  1. 如何为注解定义参数?

  2. 注解可以用在哪里?

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

定义注解语法

jdk中注解相关的类和接口都定义在java.lang.annotation包中。

注解的定义和我们常见的类、接口类似,只是注解使用@interface来定义,如下定义一个名称为MyAnnotation的注解:


   
  1. public @ interface MyAnnotation {
  2. }

注解中定义参数

注解有没有参数都可以,定义参数如下:


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

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

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

  2. 该元素的类型只能是基本数据类型、String、Class、枚举类型、注解类型(体现了注解的嵌套效果)以及上述类型的一位数组

  3. 该元素的名称一般定义为名词,如果注解中只有一个元素,请把名字起为value(后面使用会带来便利操作)

  4. 参数名称后面的()不是定义方法参数的地方,也不能在括号中定义任何参数,仅仅只是一个特殊的语法

  5. default代表默认值,值必须和第2点定义的类型一致

  6. 如果没有默认值,代表后续使用注解时必须给该类型元素赋值

指定注解的使用范围:@Target

使用@Target注解定义注解的使用范围,如下:


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

上面指定了MyAnnotation注解可以用在类、接口、注解类型、枚举类型以及方法上面,自定义注解上也可以不使用@Target注解,如果不使用,表示自定义注解可以用在任何地方

看一下@Target源码:


   
  1. @Documented
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Target(ElementType.ANNOTATION_TYPE)
  4. public @ interface Target {
  5.     ElementType[] value();
  6. }

有一个参数value,是ElementType类型的一个数组,再来看一下ElementType,是个枚举,源码如下:


   
  1. package java.lang.annotation;
  2. /*注解的使用范围*/
  3. public enum ElementType {
  4.         /*类、接口、枚举、注解上面*/
  5.     TYPE,
  6.      /*字段上*/
  7.     FIELD,
  8.      /*方法上*/
  9.     METHOD,
  10.      /*方法的参数上*/
  11.     PARAMETER,
  12.      /*构造函数上*/
  13.     CONSTRUCTOR,
  14.      /*本地变量上*/
  15.     LOCAL_VARIABLE,
  16.      /*注解上*/
  17.     ANNOTATION_TYPE,
  18.      /*包上*/
  19.     PACKAGE,
  20.      /*类型参数上*/
  21.     TYPE_PARAMETER,
  22.      /*类型名称上*/
  23.     TYPE_USE
  24. }

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

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

  1. 源码阶段

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

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

那么自定义注解会保留在上面哪个阶段呢?可以通过@Retention注解来指定,如:


   
  1. @Retention(RetentionPolicy.SOURCE)
  2. public @ interface MyAnnotation {
  3. }

上面指定了MyAnnotation只存在于源码阶段,后面的2个阶段都会丢失。

来看一下@Retention


   
  1. @Documented
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Target(ElementType.ANNOTATION_TYPE)
  4. public @ interface Retention {
  5.     RetentionPolicy value();
  6. }

有一个value参数,类型为RetentionPolicy枚举,如下:


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

使用注解

语法

将注解加载使用的目标上面,如下:


   
  1. @注解名称(参数 1=值 1,参数 2=值 2,参数n=值n)
  2. 目标对象

直接来案例说明。

无参注解


   
  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @ interface Ann1 {  //@1
  4. }
  5. @Ann1  //@2
  6. public class UseAnnotation1 {
  7. }

@1:Ann1为无参注解

@2:类上使用@Ann1注解,没有参数

一个参数的注解


   
  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @ interface Ann2 {  //@1
  4.     String name();
  5. }
  6. @Ann2(name =  "我是路人甲java"//@2
  7. public class UseAnnotation2 {
  8. }

一个参数为value的注解,可以省略参数名称

只有一个参数,名称为value的时候,使用时参数名称可以省略


   
  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @ interface Ann3 {
  4.     String value(); //@1
  5. }
  6. @Ann3( "我是路人甲java"//@2
  7. public class UseAnnotation3 {
  8. }

@1:注解之后一个参数,名称为value

@2:使用注解,参数名称value省略了

数组类型参数


   
  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @ interface Ann4 {
  4.     String[] name(); //@1
  5. }
  6. @Ann4(name = { "我是路人甲java""欢迎和我一起学spring"})  //@2
  7. public class UseAnnotation4 {
  8.     @Ann4(name =  "如果只有一个值,{}可以省略"//@3
  9.     public class T1 {
  10.     }
  11. }

@1:name的类型是一个String类型的数组

@2:name有多个值的时候,需要使用{}包含起来

@3:如果name只有一个值,{}可以省略

为参数指定默认值

通过default为参数指定默认值,用的时候如果没有设置值,则取默认值,没有指定默认值的参数,使用的时候必须为参数设置值,如下:


   
  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @ interface Ann5 {
  4.     String[] name()  default { "路人甲java""spring系列"}; //@1
  5.      int[] score()  default  1//@2
  6.      int age()  default  30//@3
  7.     String address();  //@4
  8. }
  9. @Ann5(age =  32,address =  "上海"//@5
  10. public class UseAnnotation5 {
  11. }

@1:数组类型通过{}指定默认值

@2:数组类型参数,默认值只有一个省略了{}符号

@3:默认值为30

@4:未指定默认值

@5:age=32对默认值进行了覆盖,并且为address指定了值

综合案例


   
  1. @Target(value = {
  2.         ElementType.TYPE,
  3.         ElementType.METHOD,
  4.         ElementType.FIELD,
  5.         ElementType.PARAMETER,
  6.         ElementType.CONSTRUCTOR,
  7.         ElementType.LOCAL_VARIABLE
  8. })
  9. @Retention(RetentionPolicy.RUNTIME)
  10. @ interface Ann6 {
  11.     String value();
  12.     ElementType elementType();
  13. }
  14. @Ann6(value =  "我用在类上", elementType = ElementType.TYPE)
  15. public class UseAnnotation6 {
  16.     @Ann6(value =  "我用在字段上", elementType = ElementType.FIELD)
  17.     private String a;
  18.     @Ann6(value =  "我用在构造方法上", elementType = ElementType.CONSTRUCTOR)
  19.     public UseAnnotation6(@Ann6(value =  "我用在方法参数上", elementType = ElementType.PARAMETER) String a) {
  20.         this.a = a;
  21.     }
  22.     @Ann6(value =  "我用在了普通方法上面", elementType = ElementType.METHOD)
  23.     public void m1() {
  24.         @Ann6(value =  "我用在了本地变量上", elementType = ElementType.LOCAL_VARIABLE) String a;
  25.     }
  26. }

上面演示了自定义注解在在类、字段、构造器、方法参数、方法、本地变量上的使用,@Ann6注解有个elementType参数,我想通过这个参数的值来告诉大家对应@Target中的那个值来限制使用目标的,大家注意一下上面每个elementType的值。

@Target(ElementType.TYPE_PARAMETER)

这个是1.8加上的,用来标注类型参数,类型参数一般在类后面声明或者方法上声明,这块需要先了解一下泛型泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!不然理解起来比较吃力,来个案例感受一下:


   
  1. @Target(value = {
  2.         ElementType.TYPE_PARAMETER
  3. })
  4. @Retention(RetentionPolicy.RUNTIME)
  5. @ interface Ann7 {
  6.     String value();
  7. }
  8. public class UseAnnotation7<@Ann7( "T0是在类上声明的一个泛型类型变量") T0, @Ann7( "T1是在类上声明的一个泛型类型变量") T1> {
  9.     public <@Ann7( "T2是在方法上声明的泛型类型变量") T2> void m1() {
  10.     }
  11.     public static void main(String[] args) throws NoSuchMethodException {
  12.          for (TypeVariable typeVariable : UseAnnotation7.class.getTypeParameters()) {
  13.              print(typeVariable);
  14.         }
  15.          for (TypeVariable typeVariable : UseAnnotation7.class.getDeclaredMethod( "m1").getTypeParameters()) {
  16.              print(typeVariable);
  17.         }
  18.     }
  19.     private static void  print(TypeVariable typeVariable) {
  20.         System.out. println( "类型变量名称:" + typeVariable.getName());
  21.         Arrays.stream(typeVariable.getAnnotations()).forEach(System.out:: println);
  22.     }
  23. }

类和方法上面可以声明泛型类型的变量,上面有3个泛型类型变量,我们运行一下看看效果:


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

@Target(ElementType.TYPE_USE)

这个是1.8加上的,能用在任何类型名称上,来个案例感受一下:


   
  1. @Target({ElementType.TYPE_USE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @ interface Ann10 {
  4.     String value();
  5. }
  6. @Ann10( "用在了类上")
  7. public class UserAnnotation10<@Ann10( "用在了类变量类型V1上") V1, @Ann10( "用在了类变量类型V2上") V2> {
  8.     private Map<@Ann10( "用在了泛型类型上") String, Integer>  map;
  9.     public <@Ann10( "用在了参数上") T> String m1(String name) {
  10.          return null;
  11.     }
  12. }

类后面的V1、V2都是类型名称,Map后面的尖括号也是类型名称,m1方法前面也定义了一个类型变量,名称为T

注解信息的获取

为了运行时能准确获取到注解的相关信息,Java在java.lang.reflect 反射包下新增了AnnotatedElement接口,它主要用于表示目前正在虚拟机中运行的程序中已使用注解的元素,通过该接口提供的方法可以利用反射技术地读取注解的信息,看一下UML图:


  • Package:用来表示包的信息

  • Class:用来表示类的信息

  • Constructor:用来表示构造方法信息

  • Field:用来表示类中属性信息

  • Method:用来表示方法信息

  • Parameter:用来表示方法参数信息

  • TypeVariable:用来表示类型变量信息,如:类上定义的泛型类型变量,方法上面定义的泛型类型变量

AnnotatedElement常用方法

案例

要解析的列如下


   
  1. package com.javacode2018.lesson001.demo18;
  2. import java.lang.annotation.ElementType;
  3. import java.lang.annotation.Retention;
  4. import java.lang.annotation.RetentionPolicy;
  5. import java.lang.annotation.Target;
  6. import java.util.Map;
  7. @Target({ElementType.PACKAGE,
  8.         ElementType.TYPE,
  9.         ElementType.FIELD,
  10.         ElementType.CONSTRUCTOR,
  11.         ElementType.METHOD,
  12.         ElementType.PARAMETER,
  13.         ElementType.TYPE_PARAMETER,
  14.         ElementType.TYPE_USE})
  15. @Retention(RetentionPolicy.RUNTIME)
  16. @ interface Ann11 {
  17.     String value();
  18. }
  19. @Target({ElementType.PACKAGE,
  20.         ElementType.TYPE,
  21.         ElementType.FIELD,
  22.         ElementType.CONSTRUCTOR,
  23.         ElementType.METHOD,
  24.         ElementType.PARAMETER,
  25.         ElementType.TYPE_PARAMETER,
  26.         ElementType.TYPE_USE})
  27. @Retention(RetentionPolicy.RUNTIME)
  28. @ interface Ann11_0 {
  29.      int value();
  30. }
  31. @Ann11( "用在了类上")
  32. @Ann11_0( 0)
  33. public class UseAnnotation11<@Ann11( "用在了类变量类型V1上") @Ann11_0( 1) V1, @Ann11( "用在了类变量类型V2上") @Ann11_0( 2) V2> {
  34.     @Ann11( "用在了字段上")
  35.     @Ann11_0( 3)
  36.     private String name;
  37.     private Map<@Ann11( "用在了泛型类型上,String") @Ann11_0( 4) String, @Ann11( "用在了泛型类型上,Integer") @Ann11_0( 5) Integer>  map;
  38.     @Ann11( "用在了构造方法上")
  39.     @Ann11_0( 6)
  40.     public UseAnnotation11() {
  41.         this.name = name;
  42.     }
  43.     @Ann11( "用在了返回值上")
  44.     @Ann11_0( 7)
  45.     public String m1(@Ann11( "用在了参数上") @Ann11_0( 8) String name) {
  46.          return null;
  47.     }
  48. }

解析类上的注解

解析这部分
@Ann11("用在了类上")
代码

   
  1. @Test
  2. public void m1() {
  3.      for (Annotation annotation : UserAnnotation10.class.getAnnotations()) {
  4.         System.out. println(annotation);
  5.     }
  6. }
运行输出

   
  1. @com.javacode2018.lesson001.demo18.Ann11(value=用在了类上)
  2. @com.javacode2018.lesson001.demo18.Ann11_0(value= 0)

解析类上的类型变量

解析类名后面的尖括号的部分,即下面的部分:

UseAnnotation11<@Ann11("用在了类变量类型V1上") @Ann11_0(1) V1, @Ann11("用在了类变量类型V2上") @Ann11_0(2) V2>
用例代码

   
  1. @Test
  2. public void m2() {
  3.     TypeVariable<Class<UserAnnotation10>>[] typeParameters = UserAnnotation10.class.getTypeParameters();
  4.      for (TypeVariable<Class<UserAnnotation10>> typeParameter : typeParameters) {
  5.         System.out. println(typeParameter.getName() +  "变量类型注解信息:");
  6.         Annotation[] annotations = typeParameter.getAnnotations();
  7.          for (Annotation annotation : annotations) {
  8.             System.out. println(annotation);
  9.         }
  10.     }
  11. }
运行输出

   
  1. V1变量类型注解信息:
  2. @com.javacode2018.lesson001.demo18.Ann11(value=用在了类变量类型V1上)
  3. @com.javacode2018.lesson001.demo18.Ann11_0(value= 1)
  4. V2变量类型注解信息:
  5. @com.javacode2018.lesson001.demo18.Ann11(value=用在了类变量类型V2上)
  6. @com.javacode2018.lesson001.demo18.Ann11_0(value= 2)

解析字段name上的注解

用例代码

   
  1. @Test
  2. public void m3() throws NoSuchFieldException {
  3.     Field nameField = UserAnnotation10.class.getDeclaredField( "name");
  4.      for (Annotation annotation : nameField.getAnnotations()) {
  5.         System.out. println(annotation);
  6.     }
  7. }
运行输出

   
  1. @com.javacode2018.lesson001.demo18.Ann11(value=用在了字段上)
  2. @com.javacode2018.lesson001.demo18.Ann11_0(value= 3)

解析泛型字段map上的注解

用例代码

   
  1. @Test
  2. public void m4() throws NoSuchFieldException, ClassNotFoundException {
  3.     Field field = UseAnnotation11.class.getDeclaredField( "map");
  4.     Type genericType = field.getGenericType();
  5.     Type[] actualTypeArguments = ((ParameterizedType) genericType).getActualTypeArguments();
  6.     AnnotatedType annotatedType = field.getAnnotatedType();
  7.     AnnotatedType[] annotatedActualTypeArguments = ((AnnotatedParameterizedType) annotatedType).getAnnotatedActualTypeArguments();
  8.      int i =  0;
  9.      for (AnnotatedType actualTypeArgument : annotatedActualTypeArguments) {
  10.         Type actualTypeArgument1 = actualTypeArguments[i++];
  11.         System.out. println(actualTypeArgument1.getTypeName() +  "类型上的注解如下:");
  12.          for (Annotation annotation : actualTypeArgument.getAnnotations()) {
  13.             System.out. println(annotation);
  14.         }
  15.     }
  16. }
运行输出

   
  1. java.lang.String类型上的注解如下:
  2. @com.javacode2018.lesson001.demo18.Ann11(value=用在了泛型类型上,String)
  3. @com.javacode2018.lesson001.demo18.Ann11_0(value= 4)
  4. java.lang.Integer类型上的注解如下:
  5. @com.javacode2018.lesson001.demo18.Ann11(value=用在了泛型类型上,Integer)
  6. @com.javacode2018.lesson001.demo18.Ann11_0(value= 5)

解析构造函数上的注解

用例代码

   
  1. @Test
  2. public void m5() {
  3.     Constructor<?> constructor = UseAnnotation11.class.getConstructors()[ 0];
  4.      for (Annotation annotation : constructor.getAnnotations()) {
  5.         System.out. println(annotation);
  6.     }
  7. }
运行输出

   
  1. @com.javacode2018.lesson001.demo18.Ann11(value=用在了构造方法上)
  2. @com.javacode2018.lesson001.demo18.Ann11_0(value= 6)

解析m1方法上的注解

用例代码

   
  1. @Test
  2. public void m6() throws NoSuchMethodException {
  3.     Method method = UseAnnotation11.class.getMethod( "m1", String.class);
  4.      for (Annotation annotation : method.getAnnotations()) {
  5.         System.out. println(annotation);
  6.     }
  7. }
运行输出

   
  1. @com.javacode2018.lesson001.demo18.Ann11(value=用在了返回值上)
  2. @com.javacode2018.lesson001.demo18.Ann11_0(value= 7)

解析m1方法参数注解

用例代码

   
  1. @Test
  2. public void m7() throws NoSuchMethodException {
  3.     Method method = UseAnnotation11.class.getMethod( "m1", String.class);
  4.      for (Parameter parameter : method.getParameters()) {
  5.         System.out. println(String.format( "参数%s上的注解如下:", parameter.getName()));
  6.          for (Annotation annotation : parameter.getAnnotations()) {
  7.             System.out. println(annotation);
  8.         }
  9.     }
  10. }
运行输出

   
  1. 参数arg0上的注解如下:
  2. @com.javacode2018.lesson001.demo18.Ann11(value=用在了参数上)
  3. @com.javacode2018.lesson001.demo18.Ann11_0(value= 8)
上面参数名称为arg0,如果想让参数名称和源码中真实名称一致,操作如下:

   
  1. 如果你编译这个class的时候没有添加参数–parameters,运行的时候你会得到这个结果:
  2. Parameter: arg0
  3. 编译的时候添加了–parameters参数的话,运行结果会不一样:
  4. Parameter: args
  5. 对于有经验的Maven使用者,–parameters参数可以添加到maven-compiler-plugin的配置部分:
  6. <plugin>
  7.     <groupId>org.apache.maven.plugins</groupId>
  8.     <artifactId>maven-compiler-plugin</artifactId>
  9.     <version> 3.1</version>
  10.     <configuration>
  11.         <compilerArgument>-parameters</compilerArgument>
  12.         <source> 1.8</source>
  13.         <target> 1.8</target>
  14.     </configuration>
  15. </plugin>

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

用法

来看一下这个注解的源码


   
  1. @Documented
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Target(ElementType.ANNOTATION_TYPE)
  4. public @ interface Inherited {
  5. }

我们通过@Target元注解的属性值可以看出,这个@Inherited 是专门修饰注解的。

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

案例


   
  1. package com.javacode2018.lesson001.demo18;
  2. import java.lang.annotation.*;
  3. public class InheritAnnotationTest {
  4.     @Target(ElementType.TYPE)
  5.     @Retention(RetentionPolicy.RUNTIME)
  6.     @Inherited
  7.     @ interface A1{  //@1
  8.     }
  9.     @Target(ElementType.TYPE)
  10.     @Retention(RetentionPolicy.RUNTIME)
  11.     @Inherited
  12.     @ interface A2{  //@2
  13.     }
  14.     @A1  //@3
  15.      interface I1{}
  16.     @A2  //@4
  17.     static class C1{}
  18.     static class C2 extends C1 implements I1{}  //@5
  19.     public static void main(String[] args) {
  20.          for (Annotation annotation : C2.class.getAnnotations()) {  //@6
  21.             System.out. println(annotation);
  22.         }
  23.     }
  24. }

@1:定义了一个注解A1,上面使用了@Inherited,表示这个具有继承功能

@2:定义了一个注解A2,上面使用了@Inherited,表示这个具有继承功能

@3:定义接口I1,上面使用了@A1注解

@4:定义了一个C1类,使用了A2注解

@5:C2继承了C1并且实现了I1接口

@6:获取C2上以及从父类继承过来的所有注解,然后输出

运行输出:

@com.javacode2018.lesson001.demo18.InheritAnnotationTest$A2()

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

@Repeatable重复使用注解

来看一段代码:


   
  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Target(ElementType.TYPE)
  3. @ interface Ann12{}
  4. @Ann12
  5. @Ann12
  6. public class UseAnnotation12 {
  7. }

上面代码会报错,原因是:UseAnnotation12上面重复使用了@Ann12注解,默认情况下@Ann12注解是不允许重复使用的。

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

使用步骤

先定义容器注解


   
  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Target({ElementType.TYPE, ElementType.FIELD})
  3. @ interface Ann12s {
  4.     Ann12[] value();  //@1
  5. }

容器注解中必须有个value类型的参数,参数类型为子注解类型的数组。

为注解指定容器

要让一个注解可以重复使用,需要在注解上加上@Repeatable注解,@Repeatable中value的值为容器注解,如下代码中的@2


   
  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Target({ElementType.TYPE, ElementType.FIELD})
  3. @Repeatable(Ann12s.class) //@2
  4. @ interface Ann12 {
  5.     String name();
  6. }

使用注解

重复使用相同的注解有2种方式,如下面代码

  1. 重复使用注解,如下面的类上重复使用@Ann12注解

  2. 通过容器注解来使用更多个注解,如下面的字段v1上使用@Ann12s容器注解


   
  1. @Ann12(name =  "路人甲Java")
  2. @Ann12(name =  "Spring系列")
  3. public class UseAnnotation12 {
  4.     @Ann12s(
  5.             {@Ann12(name =  "Java高并发系列,见公众号"),
  6.                     @Ann12(name =  "mysql高手系列,见公众号")}
  7.     )
  8.     private String v1;
  9. }

获取注解信息


   
  1. com.javacode2018.lesson001.demo18.UseAnnotation12
  2. @Test
  3. public void test1() throws NoSuchFieldException {
  4.     Annotation[] annotations = UseAnnotation12.class.getAnnotations();
  5.      for (Annotation annotation : annotations) {
  6.         System.out. println(annotation);
  7.     }
  8.     System.out. println( "-------------");
  9.     Field v1 = UseAnnotation12.class.getDeclaredField( "v1");
  10.     Annotation[] declaredAnnotations = v1.getDeclaredAnnotations();
  11.      for (Annotation declaredAnnotation : declaredAnnotations) {
  12.         System.out. println(declaredAnnotation);
  13.     }
  14. }

运行输出:


   
  1. @com.javacode2018.lesson001.demo18.Ann12s(value=[@com.javacode2018.lesson001.demo18.Ann12(name=路人甲Java), @com.javacode2018.lesson001.demo18.Ann12(name=Spring系列)])
  2. -------------
  3. @com.javacode2018.lesson001.demo18.Ann12s(value=[@com.javacode2018.lesson001.demo18.Ann12(name=Java高并发系列,见公众号), @com.javacode2018.lesson001.demo18.Ann12(name=mysql高手系列,见公众号)])

上面就是java中注解的功能,下面我们来介绍spring对于注解方面的支持。

先来看一个问题

代码如下:


   
  1. package com.javacode2018.lesson001.demo18;
  2. import org.junit.Test;
  3. import org.springframework.core.annotation.AnnotatedElementUtils;
  4. import org.springframework.core.annotation.AnnotationUtils;
  5. import java.lang.annotation.ElementType;
  6. import java.lang.annotation.Retention;
  7. import java.lang.annotation.RetentionPolicy;
  8. import java.lang.annotation.Target;
  9. @Target(ElementType.TYPE)
  10. @Retention(RetentionPolicy.RUNTIME)
  11. @ interface A1 {
  12.     String value()  default  "a"; //@0
  13. }
  14. @Target(ElementType.TYPE)
  15. @Retention(RetentionPolicy.RUNTIME)
  16. @A1
  17. @ interface B1 {  //@1
  18.     String value()  default  "b"; //@2
  19. }
  20. @B1( "路人甲Java"//@3
  21. public class UseAnnotation13 {
  22.     @Test
  23.     public void test1() {
  24.          //AnnotatedElementUtils是spring提供的一个查找注解的工具类
  25.         System.out. println(AnnotatedElementUtils.getMergedAnnotation(UseAnnotation13.class, B1.class));
  26.         System.out. println(AnnotatedElementUtils.getMergedAnnotation(UseAnnotation13.class, A1.class));
  27.     }
  28. }

@0:A1注解value参数值默认为a

@1:B1注解上使用到了@A1注解

@2:B1注解value参数值默认为b

@2:UseAnnotation13上面使用了@B1注解,value参数的值为:路人甲java

test1方法中使用到了spring中的一个类AnnotatedElementUtils,通过这个工具类可以很方便的获取注解的各种信息,方法中的2行代码用于获取UseAnnotation13类上B1注解和A1注解的信息。

运行test1方法输出:


   
  1. @com.javacode2018.lesson001.demo18.B1(value=路人甲Java)
  2. @com.javacode2018.lesson001.demo18.A1(value=a)

上面用法很简单,没什么问题。

此时有个问题:此时如果想在UseAnnotation13上给B1上的A1注解设置值是没有办法的,注解定义无法继承导致的,如果注解定义上面能够继承,那用起来会爽很多,spring通过@Aliasfor方法解决了这个问题。

Spring  @AliasFor:对注解进行增强

直接上案例,然后解释代码。

案例1:通过@AliasFor解决刚才难题


   
  1. package com.javacode2018.lesson001.demo18;
  2. import org.junit.Test;
  3. import org.springframework.core.annotation.AliasFor;
  4. import org.springframework.core.annotation.AnnotatedElementUtils;
  5. import java.lang.annotation.ElementType;
  6. import java.lang.annotation.Retention;
  7. import java.lang.annotation.RetentionPolicy;
  8. import java.lang.annotation.Target;
  9. @Target(ElementType.TYPE)
  10. @Retention(RetentionPolicy.RUNTIME)
  11. @ interface A14 {
  12.     String value()  default  "a"; //@0
  13. }
  14. @Target(ElementType.TYPE)
  15. @Retention(RetentionPolicy.RUNTIME)
  16. @A14  //@6
  17. @ interface B14 {  //@1
  18.     String value()  default  "b"; //@2
  19.     @AliasFor(annotation = A14.class, value =  "value"//@5
  20.     String a14Value();
  21. }
  22. @B14(value =  "路人甲Java",a14Value =  "通过B14给A14的value参数赋值"//@3
  23. public class UseAnnotation14 {
  24.     @Test
  25.     public void test1() {
  26.          //AnnotatedElementUtils是spring提供的一个查找注解的工具类
  27.         System.out. println(AnnotatedElementUtils.getMergedAnnotation(UseAnnotation14.class, B14.class));
  28.         System.out. println(AnnotatedElementUtils.getMergedAnnotation(UseAnnotation14.class, A14.class));
  29.     }
  30. }

运行输出:


   
  1. @com.javacode2018.lesson001.demo18.B14(a14Value=通过B14给A14的value参数赋值, value=路人甲Java)
  2. @com.javacode2018.lesson001.demo18.A14(value=通过B14给A14的value参数赋值)

注意上面diam的@3只使用了B14注解,大家认真看一下,上面输出汇总可以看出A14的value值和B14的a14Value参数值一样,说明通过B14给A14设置值成功了。

重点在于代码@5,这个地方使用到了@AliasFor注解:

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

这个相当于给某个注解指定别名,即将B1注解中a14Value参数作为A14value参数的别名,当给B1的a14Value设置值的时候,就相当于给A14的value设置值,有个前提是@AliasFor注解的annotation参数指定的注解需要加载当前注解上面,如:@6

案例2:同一个注解中使用@AliasFor


   
  1. package com.javacode2018.lesson001.demo18;
  2. import org.junit.Test;
  3. import org.springframework.core.annotation.AliasFor;
  4. import org.springframework.core.annotation.AnnotatedElementUtils;
  5. import java.lang.annotation.ElementType;
  6. import java.lang.annotation.Retention;
  7. import java.lang.annotation.RetentionPolicy;
  8. import java.lang.annotation.Target;
  9. @Target({ElementType.TYPE, ElementType.FIELD})
  10. @Retention(RetentionPolicy.RUNTIME)
  11. @ interface A15 {
  12.     @AliasFor( "v2") //@1
  13.     String v1()  default  "";
  14.     @AliasFor( "v1") //@2
  15.     String v2()  default  "";
  16. }
  17. @A15(v1 =  "我是v1"//@3
  18. public class UseAnnotation15 {
  19.     @A15(v2 =  "我是v2"//@4
  20.     private String name;
  21.     @Test
  22.     public void test1() throws NoSuchFieldException {
  23.          //AnnotatedElementUtils是spring提供的一个查找注解的工具类
  24.         System.out. println(AnnotatedElementUtils.getMergedAnnotation(UseAnnotation15.class, A15.class));
  25.         System.out. println(AnnotatedElementUtils.getMergedAnnotation(UseAnnotation15.class.getDeclaredField( "name"), A15.class));
  26.     }
  27. }

注意上面代码,A15注解中(@1和@2)的2个参数都设置了@AliasFor,@AliasFor如果不指定annotation参数的值,那么annotation默认值就是当前注解,所以上面2个属性互为别名,当给v1设置值的时候也相当于给v2设置值,当给v2设置值的时候也相当于给v1设置值。

运行输出


   
  1. @com.javacode2018.lesson001.demo18.A15(v1=我是v1, v2=我是v1)
  2. @com.javacode2018.lesson001.demo18.A15(v1=我是v2, v2=我是v2)

从输出中可以看出v1和v2的值始终是相等的,上面如果同时给v1和v2设置值的时候运行代码会报错。

我们回头来看看@AliasFor的源码:


   
  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Target(ElementType.METHOD)
  3. @Documented
  4. public @ interface AliasFor {
  5.     @AliasFor( "attribute")
  6.     String value()  default  "";
  7.     @AliasFor( "value")
  8.     String attribute()  default  "";
  9.     Class<? extends Annotation> annotation()  default Annotation.class;
  10. }

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

案例2:@AliasFor中不指定value和attribute

当@AliasFor中不指定value或者attribute的时候,自动将@AliasFor修饰的参数作为value和attribute的值,如下@AliasFor注解的value参数值为name


   
  1. package com.javacode2018.lesson001.demo18;
  2. import org.junit.Test;
  3. import org.springframework.core.annotation.AliasFor;
  4. import org.springframework.core.annotation.AnnotatedElementUtils;
  5. import java.lang.annotation.ElementType;
  6. import java.lang.annotation.Retention;
  7. import java.lang.annotation.RetentionPolicy;
  8. import java.lang.annotation.Target;
  9. @Target(ElementType.TYPE)
  10. @Retention(RetentionPolicy.RUNTIME)
  11. @ interface A16 {
  12.     String name()  default  "a"; //@0
  13. }
  14. @Target(ElementType.TYPE)
  15. @Retention(RetentionPolicy.RUNTIME)
  16. @A16
  17. @ interface B16 {  //@1
  18.     @AliasFor(annotation = A16.class)  //@5
  19.     String name()  default  "b"; //@2
  20. }
  21. @B16(name= "我是v1"//@3
  22. public class UseAnnotation16 {
  23.     @Test
  24.     public void test1() throws NoSuchFieldException {
  25.          //AnnotatedElementUtils是spring提供的一个查找注解的工具类
  26.         System.out. println(AnnotatedElementUtils.getMergedAnnotation(UseAnnotation16.class, A16.class));
  27.         System.out. println(AnnotatedElementUtils.getMergedAnnotation(UseAnnotation16.class, B16.class));
  28.     }
  29. }

运行输出:


   
  1. @com.javacode2018.lesson001.demo18.A16(name=我是v1)
  2. @com.javacode2018.lesson001.demo18.B16(name=我是v1)

总结

到目前为止文章开头的问题,想必各位都可以回答上来了,文章内容比较多,大家最好去敲一遍,加深理解。

欢迎留言!

案例源码


   
  1. 链接:https: //pan.baidu.com/s/1p6rcfKOeWQIVkuhVybzZmQ 
  2. 提取码:zr99

Spring系列

  1. Spring系列第1篇:为何要学spring?

  2. Spring系列第2篇:控制反转(IoC)与依赖注入(DI)

  3. Spring系列第3篇:Spring容器基本使用及原理

  4. Spring系列第4篇:xml中bean定义详解(-)

  5. Spring系列第5篇:创建bean实例这些方式你们都知道?

  6. Spring系列第6篇:玩转bean scope,避免跳坑里!

  7. Spring系列第7篇:依赖注入之手动注入

  8. Spring系列第8篇:自动注入(autowire)详解,高手在于坚持

  9. Spring系列第9篇:depend-on到底是干什么的?

  10. Spring系列第10篇:primary可以解决什么问题?

  11. Spring系列第11篇:bean中的autowire-candidate又是干什么的?

  12. Spring系列第12篇:lazy-init:bean延迟初始化

  13. Spring系列第13篇:使用继承简化bean配置(abstract & parent)

  14. Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?

  15. Spring系列第15篇:代理详解(Java动态代理&cglib代理)?

更多好文章

  1. Java高并发系列(共34篇)

  2. MySql高手系列(共27篇)

  3. Maven高手系列(共10篇)

  4. Mybatis系列(共12篇)

  5. 聊聊db和缓存一致性常见的实现方式

  6. 接口幂等性这么重要,它是什么?怎么实现?

  7. 泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!

感谢大家的阅读,也欢迎您把这篇文章分享给更多的朋友一起阅读!谢谢!

路人甲java

▲长按图片识别二维码关注

路人甲Java:工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!


转载:https://blog.csdn.net/likun557/article/details/104788022
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场