Spring中的@AliasFor標籤
在Spring的眾多註解中,經常會發現很多註解的不同屬性起著相同的作用,比如@RequestMapping的value屬性和path屬性,這就需要做一些基本的限制,比如value和path的值不能衝突,比如任意設定value或者設定path屬性的值,都能夠通過另一個屬性來獲取值等等。為了統一處理這些情況,Spring建立了@AliasFor標籤。
使用 @AliasFor標籤有幾種使用方式。
1,在同一個註解內顯示使用;比如在@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標籤有一些使用限制,但是這應該能想到的,比如要求互為別名的屬性屬性值型別,預設值,都是相同的,互為別名的註解必須成對出現,比如value屬性添加了@AliasFor(“path”),那麼path屬性就必須新增@AliasFor(“value”),另外還有一點,互為別名的屬性必須定義預設值。
那麼如果違反了別名的定義,在使用過程中就會報錯,我們來做個簡單測試:
@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互為別名,不能同時設定;
稍微調整一下程式碼:
@MyAnnotation @ContextConfiguration(value = "aa.xml", locations = "aa.xml") public class AnnotationUtilsTest { 或者
@MyAnnotation @ContextConfiguration(value = "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框架非常複雜,因為在每一個點的實現,都要考慮很多健壯性和擴充套件性的問題,這些,都是我們值得去研究的。