Spring中的@AliasFor標(biāo)簽的用法及說明
在Spring的眾多注解中,經(jīng)常會發(fā)現(xiàn)很多注解的不同屬性起著相同的作用,比如@RequestMapping的value屬性和path屬性,這就需要做一些基本的限制,比如value和path的值不能沖突,比如任意設(shè)置value或者設(shè)置path屬性的值,都能夠通過另一個屬性來獲取值等等。為了統(tǒng)一處理這些情況,Spring創(chuàng)建了@AliasFor標(biāo)簽。
使用
@AliasFor標(biāo)簽有幾種使用方式。
1,在同一個注解內(nèi)顯示使用;比如在@RequestMapping中的使用示例:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
@AliasFor("path")
String[] value() default {};
@AliasFor("value")
String[] path() default {};
//...
}又比如@ContextConfiguration注解中的value和locations屬性:
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ContextConfiguration {
@AliasFor("locations")
String[] value() default {};
@AliasFor("value")
String[] locations() default {};
//...
}在同一個注解中成對使用即可,比如示例代碼中,value和path就是互為別名。但是要注意一點,@AliasFor標(biāo)簽有一些使用限制,但是這應(yīng)該能想到的,比如要求互為別名的屬性屬性值類型,默認(rèn)值,都是相同的,互為別名的注解必須成對出現(xiàn),比如value屬性添加了@AliasFor(“path”),那么path屬性就必須添加@AliasFor(“value”),另外還有一點,互為別名的屬性必須定義默認(rèn)值。
那么如果違反了別名的定義,在使用過程中就會報錯,我們來做個簡單測試:
@ContextConfiguration(value = "aa.xml", locations = "bb.xml")
public class AnnotationUtilsTest {
@Test
public void testAliasfor() {
ContextConfiguration cc = AnnotationUtils.findAnnotation(getClass(),
ContextConfiguration.class);
System.out.println(
StringUtils.arrayToCommaDelimitedString(cc.locations()));
System.out.println(StringUtils.arrayToCommaDelimitedString(cc.value()));
}
}
執(zhí)行測試,報錯;value和locations互為別名,不能同時設(shè)置;
稍微調(diào)整一下代碼:
@MyAnnotation
@ContextConfiguration(value = "aa.xml", locations = "aa.xml")
public class AnnotationUtilsTest {或者
@MyAnnotation
@ContextConfiguration(value = "aa.xml")
public class AnnotationUtilsTest {運(yùn)行測試,均打印出:
aa.xml
aa.xml
2,顯示的覆蓋元注解中的屬性;
先來看一段代碼:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AopConfig.class)
public class AopUtilsTest {這段代碼是一個非常熟悉的基于JavaConfig的Spring測試代碼;假如現(xiàn)在我有個癖好,我覺得每次寫@ContextConfiguration(classes = AopConfig.class)太麻煩了,我想寫得簡單一點,我就可以定義一個這樣的標(biāo)簽:
@Retention(RetentionPolicy.RUNTIME)
@ContextConfiguration
public @interface STC {
@AliasFor(value = "classes", annotation = ContextConfiguration.class)
Class<?>[] cs() default {};
}1,因為@ContextConfiguration注解本身被定義為@Inherited的,所以我們的STC注解即可理解為繼承了@ContextConfiguration注解;
2,我覺得classes屬性太長了,所以我創(chuàng)建了一個cs屬性,為了讓這個屬性等同于@ContextConfiguration屬性中的classes屬性,我使用了@AliasFor標(biāo)簽,分別設(shè)置了value(即作為哪個屬性的別名)和annotation(即作為哪個注解);
使用我們的STC:
@RunWith(SpringJUnit4ClassRunner.class)
@STC(cs = AopConfig.class)
public class AopUtilsTest {
@Autowired
private IEmployeeService service;正常運(yùn)行;
這就是@AliasFor標(biāo)簽的第二種用法,顯示的為元注解中的屬性起別名;這時候也有一些限制,比如屬性類型,屬性默認(rèn)值必須相同;當(dāng)然,在這種使用情況下,@AliasFor只能為作為當(dāng)前注解的元注解起別名;
3,在一個注解中隱式聲明別名;
這種使用方式和第二種使用方式比較相似,我們直接使用Spring官方文檔的例子:
@ContextConfiguration
public @interface MyTestConfig {
@AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
String[] value() default {};
@AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
String[] groovyScripts() default {};
@AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
String[] xmlFiles() default {};
}可以看到,在MyTestConfig注解中,為value,groovyScripts,xmlFiles都定義了別名@AliasFor(annotation = ContextConfiguration.class, attribute = “locations”),所以,其實在這個注解中,value、groovyScripts和xmlFiles也互為別名,這個就是所謂的在統(tǒng)一注解中的隱式別名方式;
4,別名的傳遞;
@AliasFor注解是允許別名之間的傳遞的,簡單理解,如果A是B的別名,并且B是C的別名,那么A是C的別名;
我們看一個例子:
@MyTestConfig
public @interface GroovyOrXmlTestConfig {
@AliasFor(annotation = MyTestConfig.class, attribute = "groovyScripts")
String[] groovy() default {};
@AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
String[] xml() default {};
}1,GroovyOrXmlTestConfig把 @MyTestConfig(參考上一個案例)作為元注解;
2,定義了groovy屬性,并作為MyTestConfig中的groovyScripts屬性的別名;
3,定義了xml屬性,并作為ContextConfiguration中的locations屬性的別名;
4,因為MyTestConfig中的groovyScripts屬性本身就是ContextConfiguration中的locations屬性的別名;所以xml屬性和groovy屬性也互為別名;
這個就是別名的傳遞性;
原理
明白@AliasFor標(biāo)簽的使用方式,我們簡單來看看@AliasFor標(biāo)簽的使用原理;
首先來看看該標(biāo)簽的定義:
@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標(biāo)簽自己就使用了自己,為value屬性添加了attribute屬性作為別名;
那么就把這個注解放在我們需要的地方就可以了么?真就這么簡單么?我們來做一個例子:
1,創(chuàng)建一個注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyAnnotation {
@AliasFor("alias")
String value() default "";
@AliasFor("value")
String alias() default "";
}在該注解中,我們讓alias和value屬性互為別名;
2,完成測試類:
@MyAnnotation(value = "aa", alias = "bb")
public class AnnotationUtilsTest {
@Test
public void testAliasfor2() {
MyAnnotation ann = getClass().getAnnotation(MyAnnotation.class);
System.out.println(ann.value());
System.out.println(ann.alias());
}
}我們將MyAnnotation放在AnnotationUtilsTest上,可以看到,我們故意將value和alias值設(shè)置為不一樣的,然后在測試代碼中分別獲取value()和alias()的值,結(jié)果打?。?/p>
aa
bb
WTF?和預(yù)期的不一樣?原因很簡單,AliasFor是Spring定義的標(biāo)簽,要使用他,只能讓Spring來處理,修改測試代碼:
@MyAnnotation(value = "aa", alias = "bb")
public class AnnotationUtilsTest {
@Test
public void testAliasfor3() {
MyAnnotation ann = AnnotationUtils.findAnnotation(getClass(),
MyAnnotation.class);
System.out.println(ann.value());
System.out.println(ann.alias());
}
}這次我們使用Spring的AnnotationUtils工具類的findAnnotation方法來獲取標(biāo)簽,然后再次打印value()和alias()值:

如愿報錯;所以,使用@AliasFor最需要注意一點的,就是只能使用Spring的AnnotationUtils工具類來獲??;
而真正在起作用的,是AnnotationUtils工具類中的<A extends Annotation> A synthesizeAnnotation(A annotation, AnnotatedElement annotatedElement)方法;
這個方法傳入注解對象,和這個注解對象所在的類型,返回一個經(jīng)過處理(這個處理就主要是用于處理@AliasFor標(biāo)簽)之后的注解對象,簡單說,這個方法就是把A注解對象—-(經(jīng)過處理)——>支持AliasFor的A注解對象,我們來看看其中的關(guān)鍵代碼:
DefaultAnnotationAttributeExtractor attributeExtractor =
new DefaultAnnotationAttributeExtractor(annotation, annotatedElement);
InvocationHandler handler =
new SynthesizedAnnotationInvocationHandler(attributeExtractor);
return (A) Proxy.newProxyInstance(annotation.getClass().getClassLoader(),
new Class<?>[] {(Class<A>) annotationType, SynthesizedAnnotation.class}, handler);可以看到,本質(zhì)原理就是使用了AOP來對A注解對象做了次動態(tài)代理,而用于處理代理的對象為SynthesizedAnnotationInvocationHandler;我們來看看SynthesizedAnnotationInvocationHandler中的重要處理代碼:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (isEqualsMethod(method)) {
return annotationEquals(args[0]);
}
if (isHashCodeMethod(method)) {
return annotationHashCode();
}
if (isToStringMethod(method)) {
return annotationToString();
}
if (isAnnotationTypeMethod(method)) {
return annotationType();
}
if (!isAttributeMethod(method)) {
String msg = String.format("Method [%s] is unsupported for synthesized annotation type [%s]", method,
annotationType());
throw new AnnotationConfigurationException(msg);
}
return getAttributeValue(method);
}在invoke(即攔截方法中,這個攔截方法就是在注解中獲取屬性值的方法,不要忘了,注解的屬性實際上定義為接口的方法),其次判斷,如果當(dāng)前執(zhí)行的方法不是equals、hashCode、toString、或者屬性是另外的注解,或者不是屬性方法,之外的方法(這些方法就是要處理的目標(biāo)屬性),都調(diào)用了getAttributeValue方法,所以我們又跟蹤到getAttributeValue方法的重要代碼:
String attributeName = attributeMethod.getName();
Object value = this.valueCache.get(attributeName);
if (value == null) {
value = this.attributeExtractor.getAttributeValue(attributeMethod);這里我們重點關(guān)注的是如果沒有緩存到值(這個先不用管),直接調(diào)用attributeExtractor.getAttributeValue方法獲取屬性值,那么,很容易猜到,如果屬性有@AliasFor注解,就應(yīng)該是這個方法在處理;那我們來看看這個方法又在做什么事情:
attributeExtractor是一個AnnotationAttributeExtractor類型,這個對象是在構(gòu)造SynthesizedAnnotationInvocationHandler時傳入的,默認(rèn)是一個DefaultAnnotationAttributeExtractor對象;而DefaultAnnotationAttributeExtractor是繼承AbstractAliasAwareAnnotationAttributeExtractor,看名字,真正的處理AliasFor標(biāo)簽的動作,應(yīng)該就在這里面,于是繼續(xù)看代碼:
public final Object getAttributeValue(Method attributeMethod) {
String attributeName = attributeMethod.getName();
Object attributeValue = getRawAttributeValue(attributeMethod);
List<String> aliasNames = this.attributeAliasMap.get(attributeName);
if (aliasNames != null) {
Object defaultValue = AnnotationUtils.getDefaultValue(getAnnotationType(), attributeName);
for (String aliasName : aliasNames) {
Object aliasValue = getRawAttributeValue(aliasName);
if (!ObjectUtils.nullSafeEquals(attributeValue, aliasValue) &&
!ObjectUtils.nullSafeEquals(attributeValue, defaultValue) &&
!ObjectUtils.nullSafeEquals(aliasValue, defaultValue)) {
throw new AnnotationConfigurationException(...)
}
if (ObjectUtils.nullSafeEquals(attributeValue, defaultValue)) {
attributeValue = aliasValue;
}
}
}
return attributeValue;
}對原代碼做了些改造,但是我們能清晰的看到重點:
1,首先正常獲取當(dāng)前屬性的值;
2,List<String> aliasNames = this.attributeAliasMap.get(attributeName);得到所有的標(biāo)記為別名的屬性名稱;
3,Object aliasValue = getRawAttributeValue(aliasName);遍歷獲取所有別名屬性的值;
4,三個重要判斷,attributeValue、aliasValue、defaultValue相同,我們前面介紹的@AliasFor標(biāo)簽的傳遞性也是在這里體現(xiàn);如果不相同,直接拋出異常;否則正常返回屬性值;
至此,AliasFor的執(zhí)行過程分析完畢;
總結(jié)
類似@AliasFor這樣的注解,在Spring框架中比比皆是,而每一個這樣的細(xì)節(jié)的點,都值得我們?nèi)ンw會。
我們常常說Spring框架非常復(fù)雜,因為在每一個點的實現(xiàn),都要考慮很多健壯性和擴(kuò)展性的問題,這些,都是我們值得去研究的。

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
JAVA 泛型與通配符詳解從原理到實戰(zhàn)應(yīng)用
本章全面介紹了泛型的核心概念、設(shè)計初衷及三大使用方式(泛型類、泛型接口、泛型方法),重點講解了泛型的擦除機(jī)制和通配符的靈活運(yùn)用,強(qiáng)調(diào)了泛型在編譯期類型安全和運(yùn)行時類型擦除的優(yōu)勢,感興趣的朋友跟隨小編一起看看吧2026-03-03
java實現(xiàn)簡單的學(xué)生信息管理系統(tǒng)代碼實例
這篇文章主要介紹了java實現(xiàn)簡單的學(xué)生信息管理系統(tǒng),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04
SpringBoot2.6.x升級后循環(huán)依賴及Swagger無法使用問題
這篇文章主要為大家介紹了SpringBoot2.6.x升級后循環(huán)依賴及Swagger無法使用問題,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06
Java?將list集合數(shù)據(jù)按照時間字段排序的方法
這篇文章主要介紹了Java?將list集合數(shù)據(jù)按照時間字段排序,本文通過示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-03-03
使用BufferedReader讀取TXT文件中數(shù)值,并輸出最大值
這篇文章主要介紹了使用BufferedReader讀取TXT文件中數(shù)值,并輸出最大值,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12
Spring Task 動態(tài)修改任務(wù)執(zhí)行計劃cron方式
這篇文章主要介紹了Spring Task 動態(tài)修改任務(wù)執(zhí)行計劃cron方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11
SpringBoot集成RedisTemplate的實現(xiàn)示例
本文主要介紹了SpringBoot集成RedisTemplate的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-09-09

