談談Spring家族中的那幾百個註解
本文我們來梳理一下Spring的那些註解,如下圖所示,大概從幾方面列出了Spring的一些註解:
如果此圖看不清楚也沒事,請執行下面的程式碼輸出所有的結果。
Spring目前的趨勢是使用註解結合Java程式碼而不是配置來定義行為、屬性、功能、規則和擴充套件點,因此梳理註解也是梳理Spring功能點的很好的方式,全面的梳理可以補足我們知識點的漏洞。
查詢所有註解
首先,我們來建立一個專案,使用SPRING INITIALIZR生成一個引入Spring各種元件的專案模板,然後引入如下工具包:
<dependency> <groupId>org.reflections</groupId> <artifactId>reflections</artifactId> <version>0.9.11</version> </dependency>
通過這個反射工具包,我們可以建立一個Spring Boot應用程式,以一行程式碼打印出所有Spring框架的註解:
import org.reflections.Reflections; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; import java.lang.annotation.Annotation; @Component public class ScanAnnotationRunner implements CommandLineRunner { @Override public void run(String... args) throws Exception { new Reflections("org.springframework") .getSubTypesOf(Annotation.class) .stream() .map(clazz->clazz.getName()) .sorted() .forEach(System.out::println); } }
輸出結果這裡就不給出了,下面我們逐一進行梳理其中的一些重要註解。
有關注解
Java的Annotation註解(類似於C#的Attribute特性),說白了就是給程式碼打上標籤的能力。我們可以配置這個標籤的保留階段,僅原始碼,原始碼+位元組碼,原始碼+位元組碼+執行時。通過引入註解,我們可以簡單快速賦予程式碼生命力,大大提高程式碼可讀性和擴充套件性。註解本身不具有任何能力,只是一個標籤,但是我們可以定義各種標籤然後實現各種標籤處理器來對類、方法、屬性甚至引數等進行功能擴充套件、功能開啟、屬性定義、行為定義、規則定義、關聯處理、元資料定義等等。在實現各種框架的時候,我們經常會自定義標籤方便框架使用者僅僅通過在合適的地方引入合適的註解來啟用(或自定義)框架的一些能力並應用到我們的程式中。
不僅僅是框架的作者會大量使用註解,在之前的系列文章中我們也多次自定義註解,我們有通過定義@Metrics註解配合Spring AOP來為程式啟動打點、日誌、異常等功能,我們有通過定義@Sign註解配合Spring MVC的ResponseBodyAdvice進行資料簽名功能,我們還經常會定義各種自定義註解配合Spring MVC的HandlerMethodArgumentResolver進行許可權的校驗等等功能。採用這種模式,我們的核心業務邏輯可以保持清晰乾淨,通過註解配合AOP賦予程式碼額外的能力。
你可能會說,註解還是有侵入性,我們需要耦合框架定義的那些註解,這個問題其實是無解的,100%無侵入性也代表了可讀性的降低,程式碼的功能和能力應當聚合在一起,這也就是為什麼Spring現在也不建議採用XML來做配置。Java核心類庫並沒有什麼註解,好在Spring已經有了大量註解,而Spring也變為了Java開發的標準,所以其實我們很多時候如果希望自己的框架(RPC啥的)完全沒有侵入性的話可以借用Spring的那些註解@Autowired、@Controller、@Service等註解,配合各種包的規範其實我們可以對目標元素的功能識別個八九不離十,完全有可能實現0侵入的功能增強。
有關如何實現自定義註解不贅述,這裡我們簡單回顧一下幾個元註解(註解的註解):
A. @Documented:將會在被此註解註解的元素的javadoc文件中列出註解,一般都打上這個註解沒壞處
B. @Target:註解能被應用的目標元素,比如類、方法、屬性、引數等等,需要仔細思考
C. @Retention:僅在原始碼保留,還是保留到編譯後的位元組碼,還是到執行時也去載入,超過90%的應用會在執行時去解析註解進行額外的處理,所以大部分情況我們都會設定配置為RetentionPolicy.RUNTIME
D. @Inherited:如果子類沒有定義註解的話,能自動從父類獲取定義了繼承屬性的註解,比如Spring的@Service是沒有繼承特性的,但是@Transactional是有繼承特性的,在OO繼承體系中使用Spring註解的時候請特別注意這點,理所當然認為註解是能被子類繼承的話可能會引起不必要的Bug,需要仔細斟酌是否開啟繼承
E. @Repeatable:Java 8 引入的特性,通過關聯註解容器定義可重複註解,小小語法糖提高了程式碼可讀性,對於元素有多個重複註解其實是很常見的事情,比如某方法可以是A角色可以訪問也可以是B角色可以訪問,某方法需要定時任務執行,要在A條件執行也需要在B條件執行
F. @Native:是否在.h標頭檔案中生成被標記的欄位,除非原生程式需要和Java程式互動,否則很少會用到這個元註解
現在我們來從幾個方面逐一溫習一下Spring的那些常用的值得關注的註解。
Spring核心註解
A. 首先來看一下各種stereotype:按分類定義了由Spring管理的各種元件,@Controller定義表現層元件,@Service定義業務邏輯層元件,@Repository定義資料訪問層資源庫元件,@Component定義其它元件(比如訪問外部服務的元件),之前也說過了隨著這些註解功能無區別,但是對元件進行合適的分類意義重大,不僅僅增加可讀性而且方便我們通過AOP對不同型別的元件進行更多自動增強
B.再來看看IOC相關的一些註解:@Autowired自動裝配不用多說了;@Required用於在setter方法標記屬性值需要由Spring進行裝配,對於目前版本的Spring這個註解已經廢棄,現在Spring更推薦使用構造方法注入;@Qualifier用於通過給Bean定義修飾語來注入相應的Bean,和@Autowired一起使用相當於@Resource的效果,當然還有一種常見用法是嵌入其它註解用於對Bean進行區分,然後配合@Autowired一起使用,參見後面提到的Spring Cloud的@LoadBalanced註解;@Value用於注入屬性配置或SpEL表示式(前者是我們常見用法,後者可以從其它物件獲取值,功能更強大一點);@Lookup可以實現方法注入,如果我們的類是單例的,但是又希望Spring注入的依賴的物件是Prototype生命週期(每次new一個出來)的,這個時候可以通過此註解進行方法注入
C. 然後來看一下有關事務的幾個註解:@EnableTransactionManagement用於開啟事務管理,使用Spring Boot如果引入Spring Data的話不需要手動開啟(不過建議大家在使用事務的時候還是通過日誌來驗證事務管理是否生效);@Transactional大家都知道用於開啟事務以及設定傳播性、隔離性、回滾條件等;@TransactionalEventListener用於配置事務的回撥方法,可以在事務提交前、提交後、完成後以及回滾後幾個階段接受回撥事件。
D. @Order註解可以設定Spring管理物件的載入順序,在之前介紹AOP的文章中我們看到有的時候我們必須通過設定合理的@Order來合理安排切面的切入順序避免一些問題,還有在一些業務場景中,我們往往會去定義一組類似於Filter的@Component,然後會從容器獲得一組Bean,這個時候業務元件的執行順序往往會比較重要,也可以通過這個方式進行排序
E. @AliasFor註解可以設定一組註解屬性相互作為別名,對於有歧義的時候會使程式碼更清晰,此外還有一個用途是建立複合註解,Spring MVC的@GetMapping註解就是基於@RequestMapping這個註解建立的複合註解,我們可以很方便得通過這種方式來實現註解的繼承
Spring上下文註解
A. 首先來看一下配置相關的一些註解:@Configuration用於標註配置類,啟用Java配置方式的Bean配置;@Bean用於配置一個Bean;@ComponentScan(@ComponentScans用於配置一組@ComponentScan,Java 8可以直接使用重複註解特性配置多個@ComponentScan)用於掃描包方式配置Bean;@PropertySource以及 @PropertySources用於匯入配置檔案;@Conditional用於設定關聯的條件類,在合適的時候啟用Bean的配置(Spring Boot自動配置根基);@Import用於匯入其它配置類; @ImportResource用於匯入非Java配置方式的XML配置;@Profile用於指定在合適的Profile下啟用配置;@Lazy用於告知容器延遲到使用的時候例項化Bean(預設情況下容器啟動的時候例項化Bean來檢查所有的問題);@Description用於給Bean設定描述;@Scope用於設定Bean的生命週期;@Primary用於在定義了多個Bean的時候指定首選的Bean
B. 其它一些註解包括:@EventListener用於設定回撥方法監聽Spring制定的以及自定義的各種事件;@EnableAspectJAutoProxy用於開啟支援AspectJ的 @Aspect切面配置支援,使用Spring Boot引入了AOP啟動器的話不需要顯式開啟
Spring Web註解
Spring MVC的各種註解對應了Spring MVC各方面的功能,下面我們來了解一下:
A. 首先是三個定義了Bean特殊生命週期的複合註解:@RequestScope、@SessionScope和 @ApplicationScope。在Web應用中,我們可能需要Bean跟隨請求、會話和應用程式的宣告週期來進行建立,這個時候可以直接使用這三個快捷的複合註解
B. 接下去可以看到各種 @XXXMapping的註解,分別用於配置HandlerMethod匹配到不同的Http Method,當然不使用這些快捷的註解也是可以的,直接使用@RequestMapping然後手動設定method
C. @ResponseStatus可以用到方法上也可以用到異常上,前者會直接使請求得到指定的響應程式碼或原因(可以配合@ExceptionHandler使用),後者可以實現遇到指定異常的時候給出指定的響應程式碼或原因,@ResponseBody我們實現Restful介面的時候(@RestController)最常用了,把返回內容(序列化後)輸出到請求體
D. Spring MVC給了我們各種註解方便我們從HTTP請求各種地方獲取引數,@RequestBody從請求體(處理複雜資料,比如JSON),@RequestHeader從請求頭,@CookieValue從cookie中,@SessionAttribute從會話中,@RequestAttribute從請求的Attribute中(比如過濾器和攔截器手動設定的一些臨時資料),@RequestParam從請求引數(處理簡單資料,鍵值對),@PathVariable從路徑片段,@MatrixAttribute矩陣變數允許我們採用特殊的規則在URL路徑後加引數(分號區分不同引數,逗號為引數增加多個值)
E. @ControllerAdvice是一個重要註解,允許我們在集中的地方配置控制器(有@RequestMapping的方法)相關的增強(@RestControllerAdvice也是差不多的,只是相當於為@ExceptionHandler加上了@ResponseBody)。那麼可以應用哪些增強呢?首先是可以用 @ExceptionHandler進行統一的全域性異常處理;第二是 @InitBinder用來設定WebDataBinder,WebDataBinder用來自動繫結前臺請求引數到Model中;第三是 @ModelAttribute讓全域性的@RequestMapping都能獲得在此處設定的鍵值對。當然,這裡說的@InitBinder和@ExceptionHandler也可以不定義在@ControllerAdvice內部(作為全域性開啟),定義在Controller內部應用到某個Controller也是可以的
F. 其它還有一些註解比如:@CrossOrigin可以用到Controller或Method上(需要配合@RequestMapping)設定細粒度的跨域行為
在之前的文章中我們也提到,對於Spring MVC,定義自己的註解應用到引數、方法、控制器上,配合HandlerMethodArgumentResolver、XXAdvise、以及Interceptor實現具體的功能來使用太太常見了,幾乎所有的非業務橫切關注點,我們都不應該在方法實現中重複任何一行程式碼。
加我架構圈子群:692-845-439 領取資料,群內每天更新資料,免費領取。
Java高階架構資料、原始碼、筆記、視訊。Dubbo、Redis、設計模式、Netty、zookeeper、Spring cloud、分散式、高併發等架構技術
Spring Boot註解
A. 來看一下上下文相關的註解:@ConfigurationProperties很常用(配合 @EnableConfigurationProperties註解來設定需要啟用的配置類),用來自定義配置類和配置檔案進行關聯;@DeprecatedConfigurationProperty用於標記廢棄的配置以及設定替代配置和告知廢棄原因;@ConfigurationPropertiesBinding用於指定自定義的轉換器用於配置解析的時的型別轉換; @NestedConfigurationProperty用於關聯外部的型別作為巢狀配置類
B. 再看看自動配置相關的註解,自動配置是Spring Boot最重要的特性,在之前的系列文章中我有提到一個觀點,IOC是好事情,但是把元件內部的一些預設配置以及元件和元件的組裝交給外部使用者來配置其實是不合理的,元件應當可以自動進行自我配置實現開箱急用,只有需要自定義元件的時候才要求外部來進行個性化配置:@EnableAutoConfiguration註解可以啟用自動配置,Spring Boot應用程式一般我們會直接使用複合註解@SpringBootApplication;@AutoConfigureOrder(值越小優先順序越高)、@AutoConfigureAfter、@AutoConfigureBefore用於設定自動配置類載入順序,以及精確控制載入依賴關係,有的時候我們的自動配置需要相互依賴或者會相互干擾,需要手動調節
C. 最後來看一下十幾種配置條件,用好這些註解是實現完善的自動配置的關鍵:@ConditionalOnBean用於僅當容器中已經包含指定的Bean型別或名稱時才匹配條件;@ConditionalOnClass僅當classpath上存在指定類時條件匹配;@ConditionalOnCloudPlatform僅當指定的雲平臺處於活動狀態時條件匹配;@ConditionalOnExpression依賴於SpEL表示式的值的條件元素的配置註解;@ConditionalOnJava基於應用執行的JVM版本的條件匹配;@ConditionalOnJndi基於JNDI可用和可以查詢指定位置的條件匹配;@ConditionalOnMissingBean僅當容器中不包含指定的Bean型別或名稱時條件匹配;@ConditionalOnMissingClass僅當classpath上不存在指定類時條件匹配;@ConditionalOnNotWebApplication 僅當不是WebApplicationContext(非Web專案)時條件匹配,對應 @ConditionalOnWebApplication;@ConditionalOnProperty是檢查指定的屬性是否具有指定的值;@ConditionalOnResource表示僅當 classpath 上存在指定資源時條件匹配;@ConditionalOnSingleCandidate僅當容器中包含指定的Bean類並且可以判斷只有單個候選者時條件匹配。其實所有這些實現原理都是擴充套件SpringBootCondition抽象類(實現之前提到的Condition介面),我們完全可以實現自己的條件註解(配合 @Conditional註解關聯到自己實現的SpringBootCondition)
Spring Cloud註解
在介紹本系列文章的第一篇中我們就提到了,Spring Cloud整齊劃一通過各種EnableXXX註解開啟某個功能,這裡就不對這些註解進行說明了,使用Spring Boot元件的功能非常簡單,基本就是引POM+EnableXXX+設定配置檔案三部曲。
A. 首先是 Netflix包下的一些註解,各種EnableXXX就不說了,參考前一篇文章,之前沒介紹過 @RibbonClient,這個註解用來為負載均衡客戶端做一些自定義的配置,可以進一步配置或自定義從哪裡獲取服務端列表、負載均衡策略、Ping也就是服務鑑活策略等等
B. client包下的 @SpringCloudApplication之前文章中我們也沒有使用到,這是一個複合註解就是 @SpringBootApplication+ @EnableDiscoveryClient+ @EnableCircuitBreaker,Spring Cloud那堆東西很多,還是自己親手定義一個一個功能的註解來的踏實; @LoadBalanced註解用於和RestTemplate配合使用構成一個負載均衡的Http客戶端,實現原理上其實這個註解是一個@Qualifier註解,Spring會為所有@LoadBalanced的RestTemplate加入一個LoadBalancerInterceptor(實現ClientHttpRequestInterceptor)實現負載均衡
C. sleuth包下面的註解和鏈路跟蹤相關,比較常用的是通過 @SpanName手動設定span的名稱,其它註解對於業務開發並不常用
總結
好了,寫了本文我發現我看到@已經Markdown的**就眼花,請點贊支援。本文我們通過程式碼打印出了大部分Spring相關的註解,你也可以通過這個方式熟悉其它框架的註解(畢竟註解是框架賦予我們各種便捷功能的一個重要入口,對註解瞭解個八九成也往往可以對框架賦予我們的豐富功能瞭解六七成)。然後我們梳理了一下Spring相關的各種註解,其中主要需要關注的是幾方面:
元註解,也就是註解的註解
Spring容器相關的一些註解,包括@Qualifier、@AliasFor、@Order等看似不重要但其實很重要的註解
Spring Java配置相關的一些註解,包括條件註解
Spring Boot自動配置相關的一些註解
很多註解可以同時應用到型別、方法、引數上,有的時候應用到不同的地方作用會略微不一樣,這個需要重點關注
我們知道註解其實只是一個標識,註解如何起作用背後的實現原理還是比較多樣的,你可以進一步結合本文介紹的Spring的各種註解探尋一下背後實現的原理。