前兩篇文章咱聊了深入了解了 Spring 注解編程一些原理,這篇文章我們關注注解屬性方法,聊聊 Spring 為注解的帶來的功能,屬性別名與覆蓋。
注解屬性方法
在進入了解 Spring 注解屬性功能之前,我們先看一個正常 Java 注解。

在注解中,屬性方法與其他類/接口方法寫法類似,但是存在一些區別。
注解屬性方法的返回類型僅限為八種基本類型(包裝類不支持),字符串,class,enum,Annotation以及前面類型的數組。
復習一下,java 八種基本類型分別為,byte(字節型)、short(短整型)、int(整型)、long(長整型)、float(單精度浮點型)、double(雙精度浮點型)、boolean(布爾型)、char(字符型)。
其次,注解屬性方法可以使用 default設置默認值。如果沒有設置默認值,聲明注解時必須顯式設置屬性,否則編譯將會出錯。
另外 Java 注解無法繼承類,也無法實現接口。
Spring 屬性方法特性
在 Spring 中,有一些注解,使用不同屬性方法,卻能到達相同結果。典型的如 RequestMapping。
在 WEB 項目中,設置 url 路徑,我們可以在方法是這樣聲明:
@RequestMapping("hello")
public String helloAnnotation() {
。。。。
}
上面方法本質使用注解
value屬性。當注解聲明時只需要設置一個方法時,如果屬性方法為 value,不需要使用 key=value 的語法,只需要直接設置屬性值即可。
另外也可以使用 path 屬性方法設置。
@RequestMapping(path = "hello")
public String helloAnnotation() {
。。。。
}
兩種方式,最后運行效果一致。
查看 RequestMapping 注解源碼,可以發現在 value 與 path 屬性方法上使用 @AliasFor,並且兩個互相指向對方。

Spring 4.2 加入 @AliasFor 注解,並使用 @AliasFor 重新更新 RequestMapping等注解,為它們內部帶來了別名的功能。
@AliasFor 使用方式
在 Spring 中,@AliasFor 可以在同一注解中使用,使用方法如 RequestMapping 注解。
這種方式,帶來含義明確屬性方法。如 RequestMapping,path 屬性方法,這個屬性方法含義就比較明確,不同的人理解不會有偏差。而 value 屬性含義就不是很明確,不能一下子就將它真正含義產生聯系。
日常開發中,我們也要避免 i,a,b 這些無意義的命名,盡量使用含義明確的命名。這樣利用維護代碼的人理解。
第二點,同一注解屬性方法相互別名,這樣就兼容之前版本用法。
RequestMapping 注解如果僅新增 path 屬性,然后根據其解析 url 路徑,這樣就會導致升級 Spring 版本過程,運行錯誤的。
一個好軟件版本需要時向前兼容,如 JDK 8 兼容 JDK 6一樣。
另外 @AliasFor 注解還可以作用與不同注解之前,典型的如 SpringBootApplication注解。

SpringBootApplication#scanBasePackages 別名與 ComponentScan#basePackages。設置前者間接為后者賦值。
Spring Boot 就是使用 @Aliasfor 與組合注解功能,使用 SpringBootApplication一個注解代替 Configuration,EnableAutoConfiguration,ComponentScan。
Spring 注解屬性覆蓋與別名
使用 @AliasFor 注解,可以做到別名的功能。
在 Spring 中別名可以分為以下幾類:
- 顯式別名(xplicit Aliases)
- 隱式別名(Implicit Aliases)
- 傳遞隱式別名(Transitive Implicit Aliases)
以上三類都需要滿足以下條件:
- 屬性類型相同
- 屬性方法必須存在默認值
- 屬性默認值必須相同
否則運行過程中將會出錯。
顯式別名
如果一個注解中的兩個成員通過 @AliasFor聲明后互為別名,那么它們是顯式別名
。
顯示別名的關系如圖所示。

隱式別名
如果一個注解中的兩個或者更多成員通過@AliasFor聲明去覆蓋同一個元注解的成員值,它們就是隱式別名
。
隱式別名如圖所示。

上圖中,@One 的 name 屬性與 nameAlias 別名與 @Two nameAlias 屬性。由於 @One 注解中並未直接使用 @AliasFor,所以與 @One 注解隱式別名。
隱式別名類似於數學的等式。可以將其看做以下推導過程。
@One.name=@Two.nameAlias
@One.nameAlias=@Two.nameAlias
可以推導出
@One.name=@One.nameAlias
傳遞式隱式別名
如果一個注解中的兩個或者更多成員通過@AliasFor聲明去覆蓋元注解中的不同成員,但是實際上因為覆蓋的傳遞性導致最終覆蓋的是元注解中的同一個成員,那么它們就是傳遞隱式別名。
傳遞式隱式別名如圖所示。

這種類型涉及了多個注解,@One#name別名了 @Two#nameAlias屬性,然后在 @One#nameAlias 屬性又別名了 @Three#nameAliasThree 屬性。然后由於 @Two#nameAlias又別名了 @Three#nameAliasThree 屬性,這就導致 @One#name 與 @One#nameAlias 間接才生了關系。這種依靠傳遞性才生別名關系,稱為 傳遞式隱式別名。
隱式別名類似於數學的等式。大家也可以將其用上面等式推導。
屬性覆蓋
屬性覆蓋指的是注解的一個成員覆蓋另一個成員,最后兩者成員屬性值一致。
屬性覆蓋可以分為三類:
- 隱式覆蓋(Implicit Overrides)
- 顯示覆蓋(Explicit Overrides)
- 傳遞式顯式覆蓋(Transitive Explicit Overrides)
隱式覆蓋
當一個注解 @One 被元注解 @Two 標注,兩個注解存在同樣的屬性方法 name。@Two#name 將會被 @One#name 屬性覆蓋。

兩個看似不來自不同注解的成員 name 指向了同一個成員 name。
顯示覆蓋
顯示覆蓋就比較簡單了,使用 @AliasFor 注解之后,就成為顯示覆蓋。

傳遞式顯式覆蓋
如果注解 @One#name 顯示覆蓋了 @Two#nameAlias,而 @Two#nameAlias 顯示覆蓋了 @Three#nameAlias,最后因為傳遞性,@One#name 實際覆蓋了@Three#nameAlias。

總結
Spring 4.2 新增 @AliasFor注解,帶來一些特性。但是要注意的是僅僅存在 @AliasFor 不會執行任何語義別名。
底層原理可以參考 AnnotationUtils與 AnnotatedElementUtils。
幫助文檔

另外歡迎加入 Java 極客技術知識星球,獲取最新 Java 技術。

