原文地址:https://blog.csdn.net/wolfcode_cn/article/details/80654730java
在Spring的衆多註解中,常常會發現不少註解的不一樣屬性起着相同的做用,好比@RequestMapping的value屬性和path屬性,這就須要作一些基本的限制,好比value和path的值不能衝突,好比任意設置value或者設置path屬性的值,都可以經過另外一個屬性來獲取值等等。爲了統一處理這些狀況,Spring建立了@AliasFor標籤。git
@AliasFor標籤有幾種使用方式。github
1,在同一個註解內顯示使用;緩存
好比在@RequestMapping中的使用示例:app
@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標籤有一些使用限制,可是這應該能想到的,好比要求互爲別名的屬性屬性值類型,默認值,都是相同的,互爲別名的註解必須成對出現,好比value屬性添加了@AliasFor(「path」),那麼path屬性就必須添加@AliasFor(「value」),另外還有一點,互爲別名的屬性必須定義默認值。ide
那麼若是違反了別名的定義,在使用過程當中就會報錯,咱們來作個簡單測試:工具
@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())); } }
執行測試,報錯;value和locations互爲別名,不能同時設置;測試
稍微調整一下代碼:this
@MyAnnotation @ContextConfiguration(value = "aa.xml", locations = "aa.xml") public class AnnotationUtilsTest {
運行測試,均打印出:
aa.xml aa.xml
2,顯示的覆蓋元註解中的屬性;
先來看一段代碼:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = AopConfig.class) public class AopUtilsTest {
這段代碼是一個很是熟悉的基於JavaConfig的Spring測試代碼;假如如今我有個癖好,我以爲每次寫@ContextConfiguration(classes = AopConfig.class)太麻煩了,我想寫得簡單一點,我就能夠定義一個這樣的標籤:
@Retention(RetentionPolicy.RUNTIME) @ContextConfiguration public @interface STC { @AliasFor(value = "classes", annotation = ContextConfiguration.class) Class<?>[] cs() default {}; }
1,由於@ContextConfiguration註解自己被定義爲@Inherited的,因此咱們的STC註解便可理解爲繼承了@ContextConfiguration註解;
2,我以爲classes屬性太長了,因此我建立了一個cs屬性,爲了讓這個屬性等同於@ContextConfiguration屬性中的classes屬性,我使用了@AliasFor標籤,分別設置了value(即做爲哪一個屬性的別名)和annotation(即做爲哪一個註解);
使用咱們的STC:
@RunWith(SpringJUnit4ClassRunner.class) @STC(cs = AopConfig.class) public class AopUtilsTest { @Autowired private IEmployeeService service;
正常運行;
這就是@AliasFor標籤的第二種用法,顯示的爲元註解中的屬性起別名;這時候也有一些限制,好比屬性類型,屬性默認值必須相同;固然,在這種使用狀況下,@AliasFor只能爲做爲當前註解的元註解起別名;
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也互爲別名,這個就是所謂的在統一註解中的隱式別名方式;
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標籤的使用方式,咱們簡單來看看@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屬性做爲別名;
那麼就把這個註解放在咱們須要的地方就能夠了麼?真就這麼簡單麼?咱們來作一個例子:
1,建立一個註解
@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值設置爲不同的,而後在測試代碼中分別獲取value()和alias()的值,結果打印:
aa
bb
WTF?和預期的不同?緣由很簡單,AliasFor是Spring定義的標籤,要使用他,只能讓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方法來獲取標籤,而後再次打印value()和alias()值:
如願報錯;因此,使用@AliasFor最須要注意一點的,就是隻能使用Spring的AnnotationUtils工具類來獲取;
而真正在起做用的,是AnnotationUtils工具類中的<A extends Annotation> A synthesizeAnnotation(A annotation, AnnotatedElement annotatedElement)方法;
這個方法傳入註解對象,和這個註解對象所在的類型,返回一個通過處理(這個處理就主要是用於處理@AliasFor標籤)以後的註解對象,簡單說,這個方法就是把A註解對象—-(通過處理)——>支持AliasFor的A註解對象,咱們來看看其中的關鍵代碼:
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);
能夠看到,本質原理就是使用了AOP來對A註解對象作了次動態代理,而用於處理代理的對象爲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(即攔截方法中,這個攔截方法就是在註解中獲取屬性值的方法,不要忘了,註解的屬性實際上定義爲接口的方法),其次判斷,若是當前執行的方法不是equals、hashCode、toString、或者屬性是另外的註解,或者不是屬性方法,以外的方法(這些方法就是要處理的目標屬性),都調用了getAttributeValue方法,因此咱們又跟蹤到getAttributeValue方法的重要代碼:
String attributeName = attributeMethod.getName(); Object value = this.valueCache.get(attributeName); if (value == null) { value = this.attributeExtractor.getAttributeValue(attributeMethod);
這裏咱們重點關注的是若是沒有緩存到值(這個先不用管),直接調用attributeExtractor.getAttributeValue方法獲取屬性值,那麼,很容易猜到,若是屬性有@AliasFor註解,就應該是這個方法在處理;那咱們來看看這個方法又在作什麼事情:
attributeExtractor是一個AnnotationAttributeExtractor類型,這個對象是在構造SynthesizedAnnotationInvocationHandler時傳入的,默認是一個DefaultAnnotationAttributeExtractor對象;而DefaultAnnotationAttributeExtractor是繼承AbstractAliasAwareAnnotationAttributeExtractor,看名字,真正的處理AliasFor標籤的動做,應該就在這裏面,因而繼續看代碼:
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,首先正常獲取當前屬性的值;
2,List<String> aliasNames = this.attributeAliasMap.get(attributeName);獲得全部的標記爲別名的屬性名稱;
3,Object aliasValue = getRawAttributeValue(aliasName);遍歷獲取全部別名屬性的值;
4,三個重要判斷,attributeValue、aliasValue、defaultValue相同,咱們前面介紹的@AliasFor標籤的傳遞性也是在這裏體現;若是不相同,直接拋出異常;不然正常返回屬性值;
至此,AliasFor的執行過程分析完畢;
相似@AliasFor這樣的註解,在Spring框架中比比皆是,而每個這樣的細節的點,都值得咱們去體會。咱們經常說Spring框架很是複雜,由於在每個點的實現,都要考慮不少健壯性和擴展性的問題,這些,都是咱們值得去研究的。