最近有個朋友去阿里面試,被面試官來了個靈魂拷問:
- 註解是幹什麼的?
- 一個註解可以使用多次麼?如何使用?
- @Inherited是做什麼的?
- @Target中的`TYPE_PARAMETER和TYPE_USER`用在什麼地方?
- 泛型中如何使用註解?
- 註解定義可以實現繼承麼?
- spring中對註解有哪些增強?@Aliasfor註解是幹什麼的?
第1個他回答上來了,後面的幾個直接懵了,然後就沒有然後了。
之後跑來問我,然後我讓他看本文,準備下次去吊打面試官。
本文內容
帶你玩轉java註解,解決上面的所有問題。
什麼是註解?
代碼中註釋大家都熟悉吧,註釋是給開發者看的,可以提升代碼的可讀性和可維護性,但是對於java編譯器和虛擬機來說是沒有意義的,編譯之後的字節碼文件中是沒有註釋信息的;而註解和註釋有點類似,唯一的區別就是註釋是給人看的,而註釋是給編譯器和虛擬機看的,編譯器和虛擬機在運行的過程中可以獲取註解信息,然後可以根據這些註解的信息做各種想做的事情。比如:大家對@Override應該比較熟悉,就是一個註解,加在方法上,標註當前方法重寫了父類的方法,當編譯器編譯代碼的時候,會對@Override標註的方法進行驗證,驗證其父類中是否也有同樣簽名的方法,否則報錯,通過這個註解是不是增強了代碼的安全性。
總的來說:註解是對代碼的一種增強,可以在代碼編譯或者程序運行期間獲取註解的信息,然後根據這些信息做各種牛逼的事情。
註解如何使用?
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>
註解中可以定義多個參數,參數的定義有以下特點:
- 訪問修飾符必須為public,不寫默認為public
- 該元素的類型只能是基本數據類型、String、Class、枚舉類型、註解類型(體現了註解的嵌套效果)以及上述類型的一位數組
- 該元素的名稱一般定義為名詞,如果註解中只有一個元素,請把名字起為value(後面使用會帶來便利操作)
- 參數名稱後面的()不是定義方法參數的地方,也不能在括號中定義任何參數,僅僅只是一個特殊的語法
- default代表默認值,值必須和第2點定義的類型一致
- 如果沒有默認值,代表後續使用註解時必須給該類型元素賦值
指定註解的使用範圍:@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個過程
- 源碼階段
- 源碼被編譯為字節碼之後變成class文件
- 字節碼被虛擬機加載然後運行
那麼自定義註解會保留在上面哪個階段呢?可以通過@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種方式,如下面代碼
- 重複使用註解,如下面的類上重複使用@Ann12註解
- 通過容器註解來使用更多個註解,如下面的字段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>
總結
到目前為止文章開頭的問題,想必各位都可以回答上來了,文章內容比較多,大家最好去敲一遍,加深理解。
閱讀更多 Java桔煙 的文章