朋友被阿里面試官靈魂拷問,跑來求救



最近有個朋友去阿里面試,被面試官來了個靈魂拷問:

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

<code>public @interface MyAnnotation {
}/<code>

註解中定義參數

註解有沒有參數都可以,定義參數如下:

<code>public @interface 註解名稱{
    [public] 參數類型 參數名稱1() [default 參數默認值];
    [public] 參數類型 參數名稱2() [default 參數默認值];
    [public] 參數類型 參數名稱n() [default 參數默認值];
}/<code>

註解中可以定義多個參數,參數的定義有以下特點:

  1. 訪問修飾符必須為public,不寫默認為public
  2. 該元素的類型只能是基本數據類型、String、Class、枚舉類型、註解類型(體現了註解的嵌套效果)以及上述類型的一位數組
  3. 該元素的名稱一般定義為名詞,如果註解中只有一個元素,請把名字起為value(後面使用會帶來便利操作)
  4. 參數名稱後面的()不是定義方法參數的地方,也不能在括號中定義任何參數,僅僅只是一個特殊的語法
  5. default代表默認值,值必須和第2點定義的類型一致
  6. 如果沒有默認值,代表後續使用註解時必須給該類型元素賦值

指定註解的使用範圍:@Target

使用@Target註解定義註解的使用範圍,如下:

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

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

看一下@Target源碼:

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

有一個參數value,是ElementType類型的一個數組,再來看一下ElementType,是個枚舉,源碼如下:

<code>package java.lang.annotation;
/*註解的使用範圍*/
public enum ElementType {
       /*類、接口、枚舉、註解上面*/
    TYPE,
    /*字段上*/

    FIELD,
    /*方法上*/
    METHOD,
    /*方法的參數上*/
    PARAMETER,
    /*構造函數上*/
    CONSTRUCTOR,
    /*本地變量上*/
    LOCAL_VARIABLE,
    /*註解上*/
    ANNOTATION_TYPE,
    /*包上*/
    PACKAGE,
    /*類型參數上*/
    TYPE_PARAMETER,
    /*類型名稱上*/
    TYPE_USE
}/<code>

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

我們先來看一下java程序的3個過程

  1. 源碼階段
  2. 源碼被編譯為字節碼之後變成class文件
  3. 字節碼被虛擬機加載然後運行

那麼自定義註解會保留在上面哪個階段呢?可以通過@Retention註解來指定,如:

<code>@Retention(RetentionPolicy.SOURCE)
public @interface MyAnnotation {
}/<code>

上面指定了MyAnnotation只存在於源碼階段,後面的2個階段都會丟失。

來看一下@Retention

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

有一個value參數,類型為RetentionPolicy枚舉,如下:

<code>public enum RetentionPolicy {
    /*註解只保留在源碼中,編譯為字節碼之後就丟失了,也就是class文件中就不存在了*/
    SOURCE,
    /*註解只保留在源碼和字節碼中,運行階段會丟失*/
    CLASS,
    /*源碼、字節碼、運行期間都存在*/
    RUNTIME
}/<code>

使用註解

語法

將註解加載使用的目標上面,如下:

<code>@註解名稱(參數1=值1,參數2=值2,參數n=值n)
目標對象/<code>

直接來案例說明。

無參註解

<code>@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Ann1 { //@1

}

@Ann1 //@2
public class UseAnnotation1 {
}/<code>

@1:Ann1為無參註解

@2:類上使用@Ann1註解,沒有參數

一個參數的註解

<code>@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Ann2 { //@1
    String name();
}

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

}/<code>

一個參數為value的註解,可以省略參數名稱

只有一個參數,名稱為value的時候,使用時參數名稱可以省略

<code>@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Ann3 {
    String value();//@1
}

@Ann3("我是路人甲java") //@2
public class UseAnnotation3 {

}/<code>

@1:註解之後一個參數,名稱為value

@2:使用註解,參數名稱value省略了

數組類型參數

<code>@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 {
    }
}/<code>

@1:name的類型是一個String類型的數組

@2:name有多個值的時候,需要使用{}包含起來

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

為參數指定默認值

通過default為參數指定默認值,用的時候如果沒有設置值,則取默認值,沒有指定默認值的參數,使用的時候必須為參數設置值,如下:

<code>@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 {

}/<code>

@1:數組類型通過{}指定默認值

@2:數組類型參數,默認值只有一個省略了{}符號

@3:默認值為30

@4:未指定默認值

@5:age=32對默認值進行了覆蓋,並且為address指定了值

綜合案例

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

上面演示了自定義註解在在類、字段、構造器、方法參數、方法、本地變量上的使用,@Ann6註解有個elementType參數,我想通過這個參數的值來告訴大家對應@Target中的那個值來限制使用目標的,大家注意一下上面每個elementType的值。

@Target(ElementType.TYPE_PARAMETER)

這個是1.8加上的,用來標註類型參數,類型參數一般在類後面聲明或者方法上聲明,這塊需要先了解一下泛型泛型,有點難度,會讓很多人懵逼,那是因為你沒有看這篇文章!不然理解起來比較吃力,來個案例感受一下:

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

public class UseAnnotation7 {

    public  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);
    }
}/<code>

類和方法上面可以聲明泛型類型的變量,上面有3個泛型類型變量,我們運行一下看看效果:

<code>類型變量名稱:T0
@com.javacode2018.lesson001.demo18.Ann7(value=T0是在類上聲明的一個泛型類型變量)
類型變量名稱:T1
@com.javacode2018.lesson001.demo18.Ann7(value=T1是在類上聲明的一個泛型類型變量)
類型變量名稱:T2
@com.javacode2018.lesson001.demo18.Ann7(value=T2是在方法上聲明的泛型類型變量)/<code>

@Target(ElementType.TYPE_USE)

這個是1.8加上的,能用在任何類型名稱上,來個案例感受一下:

<code>@Target({ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@interface Ann10 {
    String value();
}

@Ann10("用在了類上")
public class UserAnnotation10 {

    private Map map;

    public  String m1(String name) {

        return null;
    }

}/<code>

類後面的V1、V2都是類型名稱,Map後面的尖括號也是類型名稱,m1方法前面也定義了一個類型變量,名稱為T

註解信息的獲取

為了運行時能準確獲取到註解的相關信息,Java在java.lang.reflect 反射包下新增了AnnotatedElement接口,它主要用於表示目前正在虛擬機中運行的程序中已使用註解的元素,通過該接口提供的方法可以利用反射技術地讀取註解的信息,看一下UML圖:

朋友被阿里面試官靈魂拷問,跑來求救


  • Package:用來表示包的信息
  • Class:用來表示類的信息
  • Constructor:用來表示構造方法信息
  • Field:用來表示類中屬性信息
  • Method:用來表示方法信息
  • Parameter:用來表示方法參數信息
  • TypeVariable:用來表示類型變量信息,如:類上定義的泛型類型變量,方法上面定義的泛型類型變量

AnnotatedElement常用方法

朋友被阿里面試官靈魂拷問,跑來求救

案例

要解析的列如下

<code>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("用在了字段上")
    @Ann11_0(3)
    private String name;

    private Map 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;
    }

}/<code>

解析類上的註解

解析這部分
<code>@Ann11("用在了類上")/<code>
代碼
<code>@Test
public void m1() {
    for (Annotation annotation : UserAnnotation10.class.getAnnotations()) {
        System.out.println(annotation);
    }
}/<code>
運行輸出
<code>@com.javacode2018.lesson001.demo18.Ann11(value=用在了類上)
@com.javacode2018.lesson001.demo18.Ann11_0(value=0)/<code>

解析類上的類型變量

解析類名後面的尖括號的部分,即下面的部分:

<code>UseAnnotation11/<code>
用例代碼
<code>@Test
public void m2() {
    TypeVariable<class>>[] typeParameters = UserAnnotation10.class.getTypeParameters();
    for (TypeVariable<class>> typeParameter : typeParameters) {
        System.out.println(typeParameter.getName() + "變量類型註解信息:");
        Annotation[] annotations = typeParameter.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }
    }
}/<class>/<class>/<code>
運行輸出
<code>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)/<code>

解析字段name上的註解

用例代碼
<code>@Test
public void m3() throws NoSuchFieldException {
    Field nameField = UserAnnotation10.class.getDeclaredField("name");
    for (Annotation annotation : nameField.getAnnotations()) {
        System.out.println(annotation);
    }
}/<code>
運行輸出
<code>@com.javacode2018.lesson001.demo18.Ann11(value=用在了字段上)
@com.javacode2018.lesson001.demo18.Ann11_0(value=3)/<code>

解析泛型字段map上的註解

用例代碼
<code>@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);
        }
    }
}/<code>
運行輸出
<code>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)/<code>

解析構造函數上的註解

用例代碼
<code>@Test
public void m5() {
    Constructor> constructor = UseAnnotation11.class.getConstructors()[0];
    for (Annotation annotation : constructor.getAnnotations()) {
        System.out.println(annotation);
    }
}/<code>
運行輸出
<code>@com.javacode2018.lesson001.demo18.Ann11(value=用在了構造方法上) 

@com.javacode2018.lesson001.demo18.Ann11_0(value=6)/<code>

解析m1方法上的註解

用例代碼
<code>@Test
public void m6() throws NoSuchMethodException {
    Method method = UseAnnotation11.class.getMethod("m1", String.class);
    for (Annotation annotation : method.getAnnotations()) {
        System.out.println(annotation);
    }
}/<code>
運行輸出
<code>@com.javacode2018.lesson001.demo18.Ann11(value=用在了返回值上)
@com.javacode2018.lesson001.demo18.Ann11_0(value=7)/<code>

解析m1方法參數註解

用例代碼
<code>@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);
        }
    }
}/<code>
運行輸出
<code>參數arg0上的註解如下:
@com.javacode2018.lesson001.demo18.Ann11(value=用在了參數上)
@com.javacode2018.lesson001.demo18.Ann11_0(value=8)/<code>
上面參數名稱為arg0,如果想讓參數名稱和源碼中真實名稱一致,操作如下:
<code>如果你編譯這個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>/<code>

@Inherit:實現類之間的註解繼承

用法

來看一下這個註解的源碼

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

我們通過@Target元註解的屬性值可以看出,這個@Inherited 是專門修飾註解的。

作用:讓子類可以繼承父類中被@Inherited修飾的註解,注意是繼承父類中的,如果接口中的註解也使用@Inherited修飾了,那麼接口的實現類是無法繼承這個註解的

案例

<code>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);
        }
    }
}/<code>

@1:定義了一個註解A1,上面使用了@Inherited,表示這個具有繼承功能

@2:定義了一個註解A2,上面使用了@Inherited,表示這個具有繼承功能

@3:定義接口I1,上面使用了@A1註解

@4:定義了一個C1類,使用了A2註解

@5:C2繼承了C1並且實現了I1接口

@6:獲取C2上以及從父類繼承過來的所有註解,然後輸出

運行輸出:

<code>@com.javacode2018.lesson001.demo18.InheritAnnotationTest$A2()/<code>

從輸出中可以看出類可以繼承父類上被@Inherited修飾的註解,而不能繼承接口上被@Inherited修飾的註解,這個一定要注意

@Repeatable重複使用註解

來看一段代碼:

<code>@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Ann12{}

@Ann12
@Ann12
public class UseAnnotation12 {
}/<code>

上面代碼會報錯,原因是:UseAnnotation12上面重複使用了@Ann12註解,默認情況下@Ann12註解是不允許重複使用的。

像上面這樣,如果我們想重複使用註解的時候,需要用到@Repeatable註解

使用步驟

先定義容器註解

<code>@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD})

@interface Ann12s {
    Ann12[] value(); //@1
}/<code>

容器註解中必須有個value類型的參數,參數類型為子註解類型的數組。

為註解指定容器

要讓一個註解可以重複使用,需要在註解上加上@Repeatable註解,@Repeatable中value的值為容器註解,如下代碼中的@2

<code>@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD})
@Repeatable(Ann12s.class)//@2
@interface Ann12 {
    String name();
}/<code>

使用註解

重複使用相同的註解有2種方式,如下面代碼

  1. 重複使用註解,如下面的類上重複使用@Ann12註解
  2. 通過容器註解來使用更多個註解,如下面的字段v1上使用@Ann12s容器註解
<code>@Ann12(name = "路人甲Java")
@Ann12(name = "Spring系列")
public class UseAnnotation12 {
    @Ann12s(
            {@Ann12(name = "Java高併發系列,見公眾號"),
                    @Ann12(name = "mysql高手系列,見公眾號")}
    )

    private String v1;
}/<code>

獲取註解信息

<code>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);
    }
}/<code>

運行輸出:

<code>@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高手系列,見公眾號)])/<code>

上面就是java中註解的功能,下面我們來介紹spring對於註解方面的支持。

先來看一個問題

代碼如下:

<code>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));
    }
}/<code>

@0:A1註解value參數值默認為a

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

@2:B1註解value參數值默認為b

@2:UseAnnotation13上面使用了@B1註解,value參數的值為:路人甲java

test1方法中使用到了spring中的一個類AnnotatedElementUtils,通過這個工具類可以很方便的獲取註解的各種信息,方法中的2行代碼用於獲取UseAnnotation13類上B1註解和A1註解的信息。

運行test1方法輸出:

<code>@com.javacode2018.lesson001.demo18.B1(value=路人甲Java) 

@com.javacode2018.lesson001.demo18.A1(value=a)/<code>

上面用法很簡單,沒什麼問題。

此時有個問題:此時如果想在UseAnnotation13上給B1上的A1註解設置值是沒有辦法的,註解定義無法繼承導致的,如果註解定義上面能夠繼承,那用起來會爽很多,spring通過@Aliasfor方法解決了這個問題。

Spring @AliasFor:對註解進行增強

直接上案例,然後解釋代碼。

案例1:通過@AliasFor解決剛才難題

<code>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));
    }
}/<code>

運行輸出:

<code>@com.javacode2018.lesson001.demo18.B14(a14Value=通過B14給A14的value參數賦值, value=路人甲Java)
@com.javacode2018.lesson001.demo18.A14(value=通過B14給A14的value參數賦值)/<code>

注意上面diam的@3只使用了B14註解,大家認真看一下,上面輸出彙總可以看出A14的value值和B14的a14Value參數值一樣,說明通過B14給A14設置值成功了。

重點在於代碼@5,這個地方使用到了@AliasFor註解:

<code>@AliasFor(annotation = A14.class, value = "value")/<code>

這個相當於給某個註解指定別名,即將B1註解中a14Value參數作為A14中value參數的別名,當給B1的a14Value設置值的時候,就相當於給A14的value設置值,有個前提是@AliasFor註解的annotation參數指定的註解需要加載當前註解上面,如:@6

案例2:同一個註解中使用@AliasFor

<code>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));
    }
}/<code>

注意上面代碼,A15註解中(@1和@2)的2個參數都設置了@AliasFor,@AliasFor如果不指定annotation參數的值,那麼annotation默認值就是當前註解,所以上面2個屬性互為別名,當給v1設置值的時候也相當於給v2設置值,當給v2設置值的時候也相當於給v1設置值。

運行輸出

<code>@com.javacode2018.lesson001.demo18.A15(v1=我是v1, v2=我是v1)
@com.javacode2018.lesson001.demo18.A15(v1=我是v2, v2=我是v2)/<code>

從輸出中可以看出v1和v2的值始終是相等的,上面如果同時給v1和v2設置值的時候運行代碼會報錯。

我們回頭來看看@AliasFor的源碼:

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

}/<code>

AliasFor註解中value和attribute互為別名,隨便設置一個,同時會給另外一個設置相同的值。

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

當@AliasFor中不指定value或者attribute的時候,自動將@AliasFor修飾的參數作為value和attribute的值,如下@AliasFor註解的value參數值為name

<code>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));
    }
}/<code>

運行輸出:

<code>@com.javacode2018.lesson001.demo18.A16(name=我是v1)
@com.javacode2018.lesson001.demo18.B16(name=我是v1)/<code>

總結

到目前為止文章開頭的問題,想必各位都可以回答上來了,文章內容比較多,大家最好去敲一遍,加深理解。


分享到:


相關文章: