Spring进阶(十一)之注解你真的全知道?

目录

注解如何使用?

定义注解

定义注解语法

注解中定义参数

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

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

使用注解

语法

无参注解

一个参数的注解

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

数组类型参数

为参数指定默认值

注解信息的获取

AnnotatedElement常用方法

案例:

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

@Repeatable重复使用注解

@AliasFor:对注解进行增强

我们先来看一个案例:

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

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

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


注解如何使用?

3个步骤:

  • 定义注解
  • 使用注解
  • 获取注解信息做各种牛逼的事情

定义注解

谈到注解的定义,先来想想三个问题:

  • 如何为注解定义参数
  • 注解可以用在哪里
  • 注解会被保留到什么时候

定义注解语法

jdk中注解相关的类和接口都定义在 java.lang.annotation 包中。 注解的定义和我们常见的类、接口类似,只是注解使用 @interface 来定义,如下定义一个名称为 MyAnnotation 的注解:

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注解也定义注解的使用范围,如下:

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

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

我们来看一下@Target的源码:

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

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

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

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

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

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

我们可以通过@Retention注解来指定注解保留到那个阶段,如:

@Retention(RetentionPolicy.SOURCE)//表示MyAnnotation只保留到源码阶段,之后的两个阶段会丢失
@Target(value = {ElementType.TYPE,ElementType.METHOD})
public @interface MyAnnotation {

}

我们来看一下@Retention源码

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

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

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

使用注解

语法

将注解使用在目标对象的上面

@注解名称(参数1=值1,参数2=值2,参数n=值n)

目标对象

无参注解

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

一个参数的注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Ann2 { //@1
String name();
}
@Ann2(name = "张三") //@2
public class UseAnnotation2 {
}

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

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

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Ann3 {
String value();//@1
}
@Ann3("张三") //@2
public class UseAnnotation3 {
}

数组类型参数

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Ann4 {
String[] name();//@1
}
@Ann4(name = {"张三", "李四"}) //@2
public class UseAnnotation4 {
@Ann4(name = "如果只有一个值,{}可以省略") //@3
public class T1 {
}
}

为参数指定默认值

通过default为参数指定默认值,使用的时候如果没有为该参数设置值则使用默认值,如果为该参数设置了值就使用该值。没有设置默认值的参数使用的时候必须设置值。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Ann5 {
String[] name() default {"张三", "李四"};//@1
int[] score() default 1; //@2
int age() default 30; //@3
String address(); //@4
}
@Ann5(age = 32,address = "上海") //@5
public class UseAnnotation5 {
}

注解信息的获取

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

Package:用来表示包的信息

Class:用来表示类的信息

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

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

Method:用来表示方法信息

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

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

AnnotatedElement常用方法

案例:

现有一个类如下:
 

@Ann1("用在了类上")
@Ann2(0)
public class MyAnnotation<@Ann1("用在类变量类型T1上") @Ann2(1) T1,@Ann1("用在类变量类型T2上") @Ann2(2) T2> {

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

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

    @Ann1("用在了构造方法上")
    @Ann2(6)
    public MyAnnotation(@Ann1("用在了参数上") @Ann2(7) String name) {
        this.name = name;
    }

    @Ann1("用在了返回值上")
    @Ann2(8)
    public String test(){
        return null;
    }
}

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

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

解析类上的注解:

    @Test
    //解析类上的注解
    public void test1() {
        for (Annotation annotations : MyAnnotation.class.getAnnotations()) {
            System.out.println(annotations);
        }
    }

解析类上的类型变量:

    @Test
    //解析类上的类型变量
    public void test2() {
        TypeVariable<Class<MyAnnotation>>[] typeParameters = MyAnnotation.class.getTypeParameters();
        for (TypeVariable<Class<MyAnnotation>> typeVariable : typeParameters) {
            System.out.println(typeVariable.getName());
            Annotation[] annotations = typeVariable.getAnnotations();
            for (Annotation annotation : annotations) {
                System.out.println(annotation);
            }
        }
    }

解析字段name上的注解:

    @Test
    //解析字段name上的注解
    public void test3() throws Exception {
        Field name = MyAnnotation.class.getDeclaredField("name");
        for (Annotation annotation : name.getAnnotations()) {
            System.out.println(annotation);
        }
    }

解析泛型字段map上的注解:

    @Test
    //解析泛型字段map上的注解
    public void test4() throws NoSuchFieldException {
        Field field = MyAnnotation.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++];
            System.out.println(actualTypeArgument1.getTypeName() + "类型上的注解如下:");
            for (Annotation annotation : actualTypeArgument.getAnnotations()) {
                System.out.println(annotation);
            }
        }
    }

 解析构造函数上的注解:

    @Test
    //解析构造函数上的注解
    public void test5(){
        Constructor<?> constructor = MyAnnotation.class.getDeclaredConstructors()[0];
        for (Annotation annotation:constructor.getAnnotations()){
            System.out.println(annotation);
        }
    }

解析指定方法的参数位置的注解:

    @Test
    //解析指定方法的参数位置的注解
    public void test6(){
        Constructor<?> constructor = MyAnnotation.class.getDeclaredConstructors()[0];
        Parameter parameter = constructor.getParameters()[0];
        for (Annotation annotation:parameter.getAnnotations()){
            System.out.println(annotation);
        }
    }

 解析test方法上的注解:

    @Test
    //解析test方法上的注解
    public void test7(){
        Method method = MyAnnotation.class.getDeclaredMethods()[0];
        for (Annotation annotation:method.getAnnotations()){
            System.out.println(annotation);
        }
    }

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

@Inherited注解,我们先来看看其源码:

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

由@Target(ElementType.ANNOTATION_TYPE)我们能得知Inhrited注解是用在注解上面的

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

案例:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface T1 {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface T2 {
}
@T1
public class B {
}
@T2
public interface C {
}
public class D extends B implements C{
}
public class InheritTest {

    @Test
    public void test(){
        Annotation[] annotations = D.class.getAnnotations();
        for (Annotation annotation:annotations){
            System.out.println(annotation);
        }
    }
}

运行后输出:

对于上面的代码:

我们首先是创建了两个注解T1 T2,且这两个注解都有@Inherited注解的修饰。

其次我们又创建了一个B类,B类使用了@T1注解,以及一个C接口,C接口使用了@T2注解。

最后我们是创建了一个D类来继承B类实现C接口,然后测试该D类含有的注解。

运行后发现D类只有@T1一个注解,验证了我们上面所说:一个类可以继承其父类中用@Inherited注解来修饰的注解,而不能继承其父接口中用@Inherited注解来修饰的注解

@Repeatable重复使用注解

先来看一段代码:

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


@Ann1
@Ann1
public class test {
}

上面的代码肯定是会报错的,因为test这个类上面用了两次@Ann1这个注解。默认情况下,同一个注解在同一个位置不能重复使用。

这时候,就需要用到我们的@Repeatable注解了

使用步骤:

  • 先定义一个“容器”注解
@Target({ElementType.TYPE,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Ann1 {
    Ann2[] value();
}

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

  • 为注解指定容器
@Target({ElementType.TYPE,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Ann1.class)
public @interface Ann2 {
    String name();
}

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

  • 使用注解

这种可以重复使用的注解的使用方法有两种:

重复使用注解,如下面的类上重复使用@Ann1注解

通过容器注解来使用更多个注解,如下面的字段name上使用@Ann2容器注解

@Ann2(name = "张三")
@Ann2(name = "李四")
public class B {

    @Ann1({@Ann2(name = "王五"), @Ann2(name = "陈六")})
    private String name;
}

 测试:

public class RepeatableTest {

    @Test
    public void test() throws NoSuchFieldException {
        Annotation[] annotations = B.class.getAnnotations();
        for (Annotation annotation:annotations){
            System.out.println(annotation);
        }
        System.out.println("---------------");
        Field name = B.class.getDeclaredField("name");
        for (Annotation annotation:name.getAnnotations()){
            System.out.println(annotation);
        }
    }
}

@AliasFor:对注解进行增强

我们先来看一个案例:

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

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

    String name() default "b";
}
@B1(name = "张三")
public class QuestionTest {

    @Test
    public void test(){

        System.out.println(AnnotatedElementUtils.getMergedAnnotation(QuestionTest.class, A1.class));
        System.out.println(AnnotatedElementUtils.getMergedAnnotation(QuestionTest.class, B1.class));
    }
}

运行输出:

上述代码中:

我们创建了一个A1注解,value参数默认值为a

又创建了一个B1注解,且B1注解用到了A1注解,B1注解的name参数默认值为b

接着我们又写了个测试方法test,里面使用到了spring中的一个类 AnnotatedElementUtils ,通过这个工具类可以很方便的获取注解的各种信息,方法中的2行代码用于获取QuestionTest类上B1注解和A1注解的信息。

--------------------------------------------------------------------------------------------------------------------------

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

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

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

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

    String name() default "b";

    @AliasFor(annotation = A1.class,value = "value")
    String test();
}
@B1(name = "张三",test = "李四")
public class QuestionTest {

    @Test
    public void test(){

        System.out.println(AnnotatedElementUtils.getMergedAnnotation(QuestionTest.class, A1.class));
        System.out.println(AnnotatedElementUtils.getMergedAnnotation(QuestionTest.class, B1.class));
    }
}

运行后输出:

上面输出汇总可以看出A1的value值和B1的test值一样,说明通过B1给A1设置值成功了。 重点在于@AliasFor 注解:

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

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

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

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

    @AliasFor("v1")
    String v2() default "";
}
@T1(v1 = "张三")
public class QuestionTest {

    @T1(v2 = "李四")
    public String name;



    @Test
    public void test() throws NoSuchFieldException {
        System.out.println(AnnotatedElementUtils.getMergedAnnotation(QuestionTest.class, T1.class));
        System.out.println(AnnotatedElementUtils.getMergedAnnotation(QuestionTest.class.getDeclaredField("name"), T1.class));
    }
}

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

从从下面输出中也可以看出v1和v2的值始终是相等的,所以如果同时给v1和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 互为别名,随便设置一个,同时会给另外一个设置相同的值。

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

当使用@AliasFor时如果不指定value或者attribute,会自动将@AliasFor修饰的参数作为value和attribute的值,还需注意一点,此种情况要想满足需要满足这两个注解中的这俩参数名称需一致

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface T1 {

    String test() default "";//@1
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@T1
public @interface T2 {

    @AliasFor(annotation = T1.class)
    String test() default "";//@2
}
@T2(test = "测试数据")
public class QuestionTest {

    @Test
    public void test(){

        System.out.println(AnnotatedElementUtils.getMergedAnnotation(QuestionTest.class, T1.class));
        System.out.println(AnnotatedElementUtils.getMergedAnnotation(QuestionTest.class, T2.class));
    }
}

运行后输出:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰魄雕狼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值