欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-11-21 15:09:55
本人免费整理了Java高级资料,涵盖了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高并发分布式等教程,一共30G,需要自己领取。传送门:https://mp.weixin.qq.com/s/osB-BOl6W-ZLTSttTkqMPQ 最 ......

本人免费整理了java高级资料,涵盖了java、redis、mongodb、mysql、zookeeper、spring cloud、dubbo高并发分布式等教程,一共30g,需要自己领取。
传送门:https://mp.weixin.qq.com/s/osb-bol6w-zltstttkqmpq

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

  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的注解:

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 {
    elementtype[] value();
}

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

package java.lang.annotation;
/*注解的使用范围*/
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(retentionpolicy.source)
public @interface myannotation {
}

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

来看一下@retention

@documented
@retention(retentionpolicy.runtime)
@target(elementtype.annotation_type)
public @interface retention {
    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 {
}

@1:ann1为无参注解

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

一个参数的注解

@target(elementtype.type)
@retention(retentionpolicy.runtime)
@interface ann2 { //@1
    string name();
}

@ann2(name = "我是路人甲java") //@2
public class useannotation2 {

}

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

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

@target(elementtype.type)
@retention(retentionpolicy.runtime)
@interface ann3 {
    string value();//@1
}

@ann3("我是路人甲java") //@2
public class useannotation3 {

}

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

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

数组类型参数

@target(elementtype.type)
@retention(retentionpolicy.runtime)
@interface ann4 {
    string[] name();//@1
}

@ann4(name = {"我是路人甲java", "欢迎和我一起学spring"}) //@2
public class useannotation4 {
    @ann4(name = "如果只有一个值,{}可以省略") //@3
    public class t1 {
    }
}

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

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

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

为参数指定默认值

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

@target(elementtype.type)
@retention(retentionpolicy.runtime)
@interface ann5 {
    string[] name() default {"路人甲java", "spring系列"};//@1
    int[] score() default 1; //@2
    int age() default 30; //@3
    string address(); //@4
}

@ann5(age = 32,address = "上海") //@5
public class useannotation5 {

}

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

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

@3:默认值为30

@4:未指定默认值

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

综合案例

@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;
    }
}

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

@target(elementtype.type_parameter)

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

@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);
    }
}

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

类型变量名称:t0
@com.javacode2018.lesson001.demo18.ann7(value=t0是在类上声明的一个泛型类型变量)
类型变量名称:t1
@com.javacode2018.lesson001.demo18.ann7(value=t1是在类上声明的一个泛型类型变量)
类型变量名称:t2
@com.javacode2018.lesson001.demo18.ann7(value=t2是在方法上声明的泛型类型变量)

@target(elementtype.type_use)

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

@target({elementtype.type_use})
@retention(retentionpolicy.runtime)
@interface ann10 {
    string value();
}

@ann10("用在了类上")
public class userannotation10<@ann10("用在了类变量类型v1上") v1, @ann10("用在了类变量类型v2上") v2> {

    private map<@ann10("用在了泛型类型上") string, integer> map;

    public <@ann10("用在了参数上") t> string m1(string name) {
        return null;
    }

}

类后面的v1、v2都是类型名称,map后面的尖括号也是类型名称,m1方法前面也定义了一个类型变量,名称为t

注解信息的获取

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

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

 

 

  • package:用来表示包的信息
  • class:用来表示类的信息
  • constructor:用来表示构造方法信息
  • field:用来表示类中属性信息
  • method:用来表示方法信息
  • parameter:用来表示方法参数信息
  • typevariable:用来表示类型变量信息,如:类上定义的泛型类型变量,方法上面定义的泛型类型变量

annotatedelement常用方法

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

 

案例

要解析的列如下

package com.javacode2018.lesson001.demo18;

import java.lang.annotation.elementtype;
import java.lang.annotation.retention;
import java.lang.annotation.retentionpolicy;
import java.lang.annotation.target;
import java.util.map;

@target({elementtype.package,
        elementtype.type,
        elementtype.field,
        elementtype.constructor,
        elementtype.method,
        elementtype.parameter,
        elementtype.type_parameter,
        elementtype.type_use})
@retention(retentionpolicy.runtime)
@interface ann11 {
    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 ann11_0 {
    int value();
}

@ann11("用在了类上")
@ann11_0(0)
public class useannotation11<@ann11("用在了类变量类型v1上") @ann11_0(1) v1, @ann11("用在了类变量类型v2上") @ann11_0(2) v2> {
    @ann11("用在了字段上")
    @ann11_0(3)
    private string name;

    private map<@ann11("用在了泛型类型上,string") @ann11_0(4) string, @ann11("用在了泛型类型上,integer") @ann11_0(5) integer> map;

    @ann11("用在了构造方法上")
    @ann11_0(6)
    public useannotation11() {
        this.name = name;
    }

    @ann11("用在了返回值上")
    @ann11_0(7)
    public string m1(@ann11("用在了参数上") @ann11_0(8) string name) {
        return null;
    }

}

解析类上的注解

解析这部分
@ann11("用在了类上")
代码
@test
public void m1() {
    for (annotation annotation : userannotation10.class.getannotations()) {
        system.out.println(annotation);
    }
}
运行输出
@com.javacode2018.lesson001.demo18.ann11(value=用在了类上)
@com.javacode2018.lesson001.demo18.ann11_0(value=0)

解析类上的类型变量

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

useannotation11<@ann11("用在了类变量类型v1上") @ann11_0(1) v1, @ann11("用在了类变量类型v2上") @ann11_0(2) v2>
用例代码
@test
public void m2() {
    typevariable<class<userannotation10>>[] typeparameters = userannotation10.class.gettypeparameters();
    for (typevariable<class<userannotation10>> typeparameter : typeparameters) {
        system.out.println(typeparameter.getname() + "变量类型注解信息:");
        annotation[] annotations = typeparameter.getannotations();
        for (annotation annotation : annotations) {
            system.out.println(annotation);
        }
    }
}
运行输出
v1变量类型注解信息:
@com.javacode2018.lesson001.demo18.ann11(value=用在了类变量类型v1上)
@com.javacode2018.lesson001.demo18.ann11_0(value=1)
v2变量类型注解信息:
@com.javacode2018.lesson001.demo18.ann11(value=用在了类变量类型v2上)
@com.javacode2018.lesson001.demo18.ann11_0(value=2)

解析字段name上的注解

用例代码
@test
public void m3() throws nosuchfieldexception {
    field namefield = userannotation10.class.getdeclaredfield("name");
    for (annotation annotation : namefield.getannotations()) {
        system.out.println(annotation);
    }
}
运行输出
@com.javacode2018.lesson001.demo18.ann11(value=用在了字段上)
@com.javacode2018.lesson001.demo18.ann11_0(value=3)

解析泛型字段map上的注解

用例代码
@test
public void m4() throws nosuchfieldexception, classnotfoundexception {
    field field = useannotation11.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);
        }
    }
}
运行输出
java.lang.string类型上的注解如下:
@com.javacode2018.lesson001.demo18.ann11(value=用在了泛型类型上,string)
@com.javacode2018.lesson001.demo18.ann11_0(value=4)
java.lang.integer类型上的注解如下:
@com.javacode2018.lesson001.demo18.ann11(value=用在了泛型类型上,integer)
@com.javacode2018.lesson001.demo18.ann11_0(value=5)

解析构造函数上的注解

用例代码
@test
public void m5() {
    constructor<?> constructor = useannotation11.class.getconstructors()[0];
    for (annotation annotation : constructor.getannotations()) {
        system.out.println(annotation);
    }
}
运行输出
@com.javacode2018.lesson001.demo18.ann11(value=用在了构造方法上)
@com.javacode2018.lesson001.demo18.ann11_0(value=6)

解析m1方法上的注解

用例代码
@test
public void m6() throws nosuchmethodexception {
    method method = useannotation11.class.getmethod("m1", string.class);
    for (annotation annotation : method.getannotations()) {
        system.out.println(annotation);
    }
}
运行输出
@com.javacode2018.lesson001.demo18.ann11(value=用在了返回值上)
@com.javacode2018.lesson001.demo18.ann11_0(value=7)

解析m1方法参数注解

用例代码
@test
public void m7() throws nosuchmethodexception {
    method method = useannotation11.class.getmethod("m1", string.class);
    for (parameter parameter : method.getparameters()) {
        system.out.println(string.format("参数%s上的注解如下:", parameter.getname()));
        for (annotation annotation : parameter.getannotations()) {
            system.out.println(annotation);
        }
    }
}
运行输出
参数arg0上的注解如下:
@com.javacode2018.lesson001.demo18.ann11(value=用在了参数上)
@com.javacode2018.lesson001.demo18.ann11_0(value=8)
上面参数名称为arg0,如果想让参数名称和源码中真实名称一致,操作如下:
如果你编译这个class的时候没有添加参数–parameters,运行的时候你会得到这个结果:

parameter: arg0

编译的时候添加了–parameters参数的话,运行结果会不一样:

parameter: args

对于有经验的maven使用者,–parameters参数可以添加到maven-compiler-plugin的配置部分:

<plugin>
    <groupid>org.apache.maven.plugins</groupid>
    <artifactid>maven-compiler-plugin</artifactid>
    <version>3.1</version>
    <configuration>
        <compilerargument>-parameters</compilerargument>
        <source>1.8</source>
        <target>1.8</target>
    </configuration>
</plugin>

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

用法

来看一下这个注解的源码

@documented
@retention(retentionpolicy.runtime)
@target(elementtype.annotation_type)
public @interface inherited {
}

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

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

案例

package com.javacode2018.lesson001.demo18;

import java.lang.annotation.*;

public class inheritannotationtest {
    @target(elementtype.type)
    @retention(retentionpolicy.runtime)
    @inherited
    @interface a1{ //@1
    }
    @target(elementtype.type)
    @retention(retentionpolicy.runtime)
    @inherited
    @interface a2{ //@2
    }

    @a1 //@3
    interface i1{}
    @a2 //@4
    static class c1{}

    static class c2 extends c1 implements i1{} //@5

    public static void main(string[] args) {
        for (annotation annotation : c2.class.getannotations()) { //@6
            system.out.println(annotation);
        }
    }
}

@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重复使用注解

来看一段代码:

@retention(retentionpolicy.runtime)
@target(elementtype.type)
@interface ann12{}

@ann12
@ann12
public class useannotation12 {
}

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

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

使用步骤

先定义容器注解

@retention(retentionpolicy.runtime)
@target({elementtype.type, elementtype.field})
@interface ann12s {
    ann12[] value(); //@1
}

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

为注解指定容器

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

@retention(retentionpolicy.runtime)
@target({elementtype.type, elementtype.field})
@repeatable(ann12s.class)//@2
@interface ann12 {
    string name();
}

使用注解

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

  1. 重复使用注解,如下面的类上重复使用@ann12注解
  2. 通过容器注解来使用更多个注解,如下面的字段v1上使用@ann12s容器注解
@ann12(name = "路人甲java")
@ann12(name = "spring系列")
public class useannotation12 {
    @ann12s(
            {@ann12(name = "java高并发系列,见公众号"),
                    @ann12(name = "mysql高手系列,见公众号")}
    )
    private string v1;
}

获取注解信息

com.javacode2018.lesson001.demo18.useannotation12

@test
public void test1() throws nosuchfieldexception {
    annotation[] annotations = useannotation12.class.getannotations();
    for (annotation annotation : annotations) {
        system.out.println(annotation);
    }
    system.out.println("-------------");
    field v1 = useannotation12.class.getdeclaredfield("v1");
    annotation[] declaredannotations = v1.getdeclaredannotations();
    for (annotation declaredannotation : declaredannotations) {
        system.out.println(declaredannotation);
    }
}

运行输出:

@com.javacode2018.lesson001.demo18.ann12s(value=[@com.javacode2018.lesson001.demo18.ann12(name=路人甲java), @com.javacode2018.lesson001.demo18.ann12(name=spring系列)])
-------------
@com.javacode2018.lesson001.demo18.ann12s(value=[@com.javacode2018.lesson001.demo18.ann12(name=java高并发系列,见公众号), @com.javacode2018.lesson001.demo18.ann12(name=mysql高手系列,见公众号)])

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

先来看一个问题

代码如下:

package com.javacode2018.lesson001.demo18;

import org.junit.test;
import org.springframework.core.annotation.annotatedelementutils;
import org.springframework.core.annotation.annotationutils;

import java.lang.annotation.elementtype;
import java.lang.annotation.retention;
import java.lang.annotation.retentionpolicy;
import java.lang.annotation.target;

@target(elementtype.type)
@retention(retentionpolicy.runtime)
@interface a1 {
    string value() default "a";//@0
}

@target(elementtype.type)
@retention(retentionpolicy.runtime)
@a1
@interface b1 { //@1
    string value() default "b";//@2
}

@b1("路人甲java") //@3
public class useannotation13 {
    @test
    public void test1() {
        //annotatedelementutils是spring提供的一个查找注解的工具类
        system.out.println(annotatedelementutils.getmergedannotation(useannotation13.class, b1.class));
        system.out.println(annotatedelementutils.getmergedannotation(useannotation13.class, a1.class));
    }
}

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

@1:b1注解上使用到了@a1注解

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

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

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

运行test1方法输出:

@com.javacode2018.lesson001.demo18.b1(value=路人甲java)
@com.javacode2018.lesson001.demo18.a1(value=a)

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

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

spring @aliasfor:对注解进行增强

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

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

package com.javacode2018.lesson001.demo18;

import org.junit.test;
import org.springframework.core.annotation.aliasfor;
import org.springframework.core.annotation.annotatedelementutils;

import java.lang.annotation.elementtype;
import java.lang.annotation.retention;
import java.lang.annotation.retentionpolicy;
import java.lang.annotation.target;

@target(elementtype.type)
@retention(retentionpolicy.runtime)
@interface a14 {
    string value() default "a";//@0
}

@target(elementtype.type)
@retention(retentionpolicy.runtime)
@a14 //@6
@interface b14 { //@1

    string value() default "b";//@2

    @aliasfor(annotation = a14.class, value = "value") //@5
    string a14value();
}

@b14(value = "路人甲java",a14value = "通过b14给a14的value参数赋值") //@3
public class useannotation14 {
    @test
    public void test1() {
        //annotatedelementutils是spring提供的一个查找注解的工具类
        system.out.println(annotatedelementutils.getmergedannotation(useannotation14.class, b14.class));
        system.out.println(annotatedelementutils.getmergedannotation(useannotation14.class, a14.class));
    }
}

运行输出:

@com.javacode2018.lesson001.demo18.b14(a14value=通过b14给a14的value参数赋值, value=路人甲java)
@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参数作为a14中value参数的别名,当给b1的a14value设置值的时候,就相当于给a14的value设置值,有个前提是@aliasfor注解的annotation参数指定的注解需要加载当前注解上面,如:@6

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

package com.javacode2018.lesson001.demo18;

import org.junit.test;
import org.springframework.core.annotation.aliasfor;
import org.springframework.core.annotation.annotatedelementutils;

import java.lang.annotation.elementtype;
import java.lang.annotation.retention;
import java.lang.annotation.retentionpolicy;
import java.lang.annotation.target;

@target({elementtype.type, elementtype.field})
@retention(retentionpolicy.runtime)
@interface a15 {
    @aliasfor("v2")//@1
    string v1() default "";

    @aliasfor("v1")//@2
    string v2() default "";
}

@a15(v1 = "我是v1") //@3
public class useannotation15 {

    @a15(v2 = "我是v2") //@4
    private string name;

    @test
    public void test1() throws nosuchfieldexception {
        //annotatedelementutils是spring提供的一个查找注解的工具类
        system.out.println(annotatedelementutils.getmergedannotation(useannotation15.class, a15.class));
        system.out.println(annotatedelementutils.getmergedannotation(useannotation15.class.getdeclaredfield("name"), a15.class));
    }
}

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

运行输出

@com.javacode2018.lesson001.demo18.a15(v1=我是v1, v2=我是v1)
@com.javacode2018.lesson001.demo18.a15(v1=我是v2, v2=我是v2)

从输出中可以看出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互为别名,随便设置一个,同时会给另外一个设置相同的值。

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

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

package com.javacode2018.lesson001.demo18;

import org.junit.test;
import org.springframework.core.annotation.aliasfor;
import org.springframework.core.annotation.annotatedelementutils;

import java.lang.annotation.elementtype;
import java.lang.annotation.retention;
import java.lang.annotation.retentionpolicy;
import java.lang.annotation.target;

@target(elementtype.type)
@retention(retentionpolicy.runtime)
@interface a16 {
    string name() default "a";//@0
}

@target(elementtype.type)
@retention(retentionpolicy.runtime)
@a16
@interface b16 { //@1

    @aliasfor(annotation = a16.class) //@5
    string name() default "b";//@2
}

@b16(name="我是v1") //@3
public class useannotation16 {


    @test
    public void test1() throws nosuchfieldexception {
        //annotatedelementutils是spring提供的一个查找注解的工具类
        system.out.println(annotatedelementutils.getmergedannotation(useannotation16.class, a16.class));
        system.out.println(annotatedelementutils.getmergedannotation(useannotation16.class, b16.class));
    }
}

运行输出:

@com.javacode2018.lesson001.demo18.a16(name=我是v1)
@com.javacode2018.lesson001.demo18.b16(name=我是v1)

总结

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

欢迎留言!