Spring对注解的增强以及Java注解的定义和使用

一、什么是注解?

注解是对代码的一种增强,可以在代码编译或代码运行时,获取注解的信息,然后根据这些信息做一些针对性的事情

二、定义注解

2.1 语法

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

public @interface 注解名称{
   [public] 参数类型 参数名称1() [default 参数默认值];
   String value() default "";
   ...
}

2.2 注解中定义参数

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

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

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

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

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

  1. 看一下@Target源码:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value();
}
  1. 参数value,是ElementType类型的一个数组,再来看一下ElementType
package java.lang.annotation;
/*注解的使用范围*/
public enum ElementType {
    /*类、接口、枚举、注解上面*/
    TYPE,
    /*字段上*/
    FIELD,
    /*方法上*/
    METHOD,
    /*方法的参数上*/
    PARAMETER,
    /*构造函数上*/
    CONSTRUCTOR,
    /*本地变量上*/
    LOCAL_VARIABLE,
    /*注解上*/
    ANNOTATION_TYPE,
    /*包上*/
    PACKAGE,
    /*类型参数上*/
    TYPE_PARAMETER,
    /*类型名称上*/
    TYPE_USE
}

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

java程序的三个过程:

  • 源码阶段
  • 源码被编译为字节码之后变成class文件
  • 字节码被虚拟机加载然后运行
@Retention(RetentionPolicy.SOURCE)
public @interface MyAnnotation {
}

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

  1. 来看一下@Retention源码
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    RetentionPolicy value();
}
  1. value参数,类型为RetentionPolicy枚举
public enum RetentionPolicy {
    /*注解只保留在源码中,编译为字节码之后就丢失了,也就是class文件中就不存在了*/
    SOURCE,
    /*注解只保留在源码和字节码中,运行阶段会丢失*/
    CLASS,
    /*源码、字节码、运行期间都存在*/
    RUNTIME
}

三、使用注解

3.1 无参注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation1 { //@1
}

@MyAnnotation1 //@2
public class UseAnnotation1 {}

3.2 一个参数的注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation2 { //@1
    String name();
}

@MyAnnotation2(name = "zkc") //@2
public class UseAnnotation2 {}

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

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation3 {
    String value();//@1
}

@MyAnnotation3("zkc") //@2
public class UseAnnotation3 {}

3.4 数组类型参数

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation4 {
    String[] name();//@1
}

@MyAnnotation4(name = {"我是zkc", "学spring"}) //@2
public class UseAnnotation4 {
    @MyAnnotation4(name = "如果只有一个值,{}可以省略") //@3
    public class T1 {}
}

3.5 @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);
    }
}
类型变量名称:T0
@com.javacode2018.lesson001.demo18.Ann7(value=T0是在类上声明的一个泛型类型变量)
类型变量名称:T1
@com.javacode2018.lesson001.demo18.Ann7(value=T1是在类上声明的一个泛型类型变量)
类型变量名称:T2
@com.javacode2018.lesson001.demo18.Ann7(value=T2是在方法上声明的泛型类型变量)

3.6 @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

3.7 注解信息的获取

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

3.7.1 解析类上的注解

@Ann11("用在了类上")
@Ann11_0(0)
public class UseAnnotation11....
@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)

3.7.2 解析类上的类型变量

解析类名后面的尖括号的部分

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)

3.7.3 解析字段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)

3.7.4 解析泛型字段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)

3.7.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)

3.7.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)

3.7.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>

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

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

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

  1. 作用

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

  1. 案例
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);
        }
    }
}
@com.javacode2018.lesson001.demo18.InheritAnnotationTest$A2()

四、@Repeatable重复使用注解

使用步骤:

  1. 先定义容器注解

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

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD})
@interface Ann12s {
    Ann12[] value(); //@1
}
  1. 为注解指定容器

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

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD})
@Repeatable(Ann12s.class)//@2
@interface Ann12 {
    String name();
}
  1. 使用注解

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

  • 重复使用注解,如下面的类上重复使用@Ann12注解
  • 通过容器注解来使用更多个注解,如下面的字段v1上使用@Ann12s容器注解
@Ann12(name = "路人甲Java")
@Ann12(name = "Spring系列")
public class UseAnnotation12 {
    @Ann12s(
      {@Ann12(name = "Java高并发系列,见公众号"),
      @Ann12(name = "mysql高手系列,见公众号")}
    )
    private String v1;
}
  1. 获取注解信息
@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);
    }
}

五、 @AliasFor:对注解进行增强

  1. 源码
@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;

}
  1. 通过@AliasFor解决UseAnnotation13上给B14上的A14注解设置值
@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参数赋值)

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

  1. 同一个注解中使用@AliasFor
@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));
    }
}
@com.javacode2018.lesson001.demo18.A15(v1=我是v1, v2=我是v1)
@com.javacode2018.lesson001.demo18.A15(v1=我是v2, v2=我是v2)

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

互为别名的属性属性值类型,默认值,都是相同的;
互为别名的注解必须成对出现,比如 value 属性添加了@AliasFor(“path”),那么 path 属性就必须添加@AliasFor(“value”);
互为别名的属性必须定义默认值。

  1. @AliasFor中不指定value和attribute

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

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

七、自定义注解支持定义bean名称

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component //@1
public @interface MyBean {
    @AliasFor(annotation = Component.class) //@2
    String value() default ""; //@3
}

重点在于@1和@2这2个地方的代码,通过上面的参数可以间接给@Component注解中的value设置值。

@MyBean("service1Bean")
public class Service1 {}
service1Bean->com.javacode2018.lesson001.demo22.test3.Service1@222545dc

八、常见面试题

  1. 注解是干什么的?

注解是对代码的一种增强,可以在代码编译或代码运行时,获取注解的信息,然后根据这些信息做一些针对性的事情

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

①定义一个容器注解,容器注解里定义value类型为子注解的数组。
②在子注解上打上@Repeatable 注解,value 为容器注解的class。该子注解就可以使用多次。

  1. @Inherited是做什么的?

定义了 @Inherited 注解的注解被使用在类上后,子类可以继承该注解。
被用在接口上,不能继承

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

两个注解都是 JDK8 加上的。
TYPE_PARAMETER 表示注解用在类型参数上,主要是泛型。
TYPE_USE 表示注解能用在任何地方

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

跟其他地方一样用。注解的 target 得有 ElementType.TYPE_PARAMETER 范围,或 TYPE_USE。

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

可以,注解如果用 @Inherited 修饰了,定义在父类上是可以被子类继承的

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

@Aliasfor 允许给注解设置别名,通过修改该别名字段的值,起到修改修饰注解值的作用。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

一位不知名民工

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

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

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

打赏作者

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

抵扣说明:

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

余额充值