淺談Spring注解

Spring目前的趨勢是使用注解結合Java代碼而不是配置來定義行為、屬性、功能、規則和擴展點,因此梳理注解也是梳理Spring功能點的很好的方式,全面的梳理可以補足我們知識點的漏洞。

查找所有注解

首先,我們來創建一個項目,使用SPRING INITIALIZR生成一個引入Spring各種組件的項目模板,然后引入如下工具包:

org.reflections

reflections

0.9.11

通過這個反射工具包,我們可以創建一個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侵入的功能增強。

有關如何實現自定義注解不贅述,這里我們簡單回顧一下幾個元注解(注解的注解):

- @Documented:將會在被此注解注解的元素的javadoc文檔中列出注解,一般都打上這個注解沒壞處

- @Target:注解能被應用的目標元素,比如類、方法、屬性、參數等等,需要仔細思考

- @Retention:僅在源碼保留,還是保留到編譯后的字節碼,還是到運行時也去加載,超過90%的應用會在運行時去解析注解進行額外的處理,所以大部分情況我們都會設置配置為RetentionPolicy.RUNTIME

-@Inherited:如果子類沒有定義注解的話,能自動從父類獲取定義了繼承屬性的注解,比如Spring的@Service是沒有繼承特性的,但是@Transactional是有繼承特性的,在OO繼承體系中使用Spring注解的時候請特別注意這點,理所當然認為注解是能被子類繼承的話可能會引起不必要的Bug,需要仔細斟酌是否開啟繼承

- @Repeatable:Java 8引入的特性,通過關聯注解容器定義可重復注解,小小語法糖提高了代碼可讀性,對于元素有多個重復注解其實是很常見的事情,比如某方法可以是A角色可以訪問也可以是B角色可以訪問,某方法需要定時任務執行,要在A條件執行也需要在B條件執行

- @Native:是否在.h頭文件中生成被標記的字段,除非原生程序需要和Java程序交互,否則很少會用到這個元注解

現在我們來從幾個方面逐一溫習一下Spring的那些常用的值得關注的注解。

Spring核心注解

- 首先來看一下各種stereotype:按分類定義了由Spring管理的各種組件,@Controller定義表現層組件,@Service定義業務邏輯層組件,@Repository定義數據訪問層資源庫組件,@Component定義其它組件(比如訪問外部服務的組件),之前也說過了隨著這些注解功能無區別,但是對組件進行合適的分類意義重大,不僅僅增加可讀性而且方便我們通過AOP對不同類型的組件進行更多自動增強

- 再來看看IOC相關的一些注解:@Autowired自動裝配不用多說了;@Required用于在setter方法標記屬性值需要由Spring進行裝配,對于目前版本的Spring這個注解已經廢棄,現在Spring更推薦使用構造方法注入;@Qualifier用于通過給Bean定義修飾語來注入相應的Bean,和@Autowired一起使用相當于@Resource的效果,當然還有一種常見用法是嵌入其它注解用于對Bean進行區分,然后配合@Autowired一起使用,參見后面提到的Spring Cloud的@LoadBalanced注解;@Value用于注入屬性配置或SpEL表達式(前者是我們常見用法,后者可以從其它對象獲取值,功能更強大一點);@Lookup可以實現方法注入,如果我們的類是單例的,但是又希望Spring注入的依賴的對象是Prototype生命周期(每次new一個出來)的,這個時候可以通過此注解進行方法注入

- 然后來看一下有關事務的幾個注解:@EnableTransactionManagement用于開啟事務管理,使用Spring Boot如果引入Spring Data的話不需要手動開啟(不過建議大家在使用事務的時候還是通過日志來驗證事務管理是否生效);@Transactional大家都知道用于開啟事務以及設置傳播性、隔離性、回滾條件等;@TransactionalEventListener用于配置事務的回調方法,可以在事務提交前、提交后、完成后以及回滾后幾個階段接受回調事件

-@Order注解可以設置Spring管理對象的加載順序,在之前介紹AOP的文章中我們看到有的時候我們必須通過設置合理的@Order來合理安排切面的切入順序避免一些問題,還有在一些業務場景中,我們往往會去定義一組類似于Filter的@Component,然后會從容器獲得一組Bean,這個時候業務組件的運行順序往往會比較重要,也可以通過這個方式進行排序

-@AliasFor注解可以設置一組注解屬性相互作為別名,對于有歧義的時候會使代碼更清晰,此外還有一個用途是創建復合注解,Spring MVC的@GetMapping注解就是基于@RequestMapping這個注解創建的復合注解,我們可以很方便得通過這種方式來實現注解的繼承

Spring上下文注解

- 首先來看一下配置相關的一些注解:@Configuration用于標注配置類,啟用Java配置方式的Bean配置;@Bean用于配置一個Bean;@ComponentScan(@ComponentScans用于配置一組@ComponentScan,Java8可以直接使用重復注解特性配置多個@ComponentScan)用于掃描包方式配置Bean;@PropertySource以及@PropertySources用于導入配置文件;@Conditional用于設置關聯的條件類,在合適的時候啟用Bean的配置(Spring Boot自動配置根基);@Import用于導入其它配置類;@ImportResource用于導入非Java配置方式的XML配置;@Profile用于指定在合適的Profile下啟用配置;@Lazy用于告知容器延遲到使用的時候實例化Bean(默認情況下容器啟動的時候實例化Bean來檢查所有的問題);@Description用于給Bean設置描述;@Scope用于設置Bean的生命周期;@Primary用于在定義了多個Bean的時候指定首選的Bean

- 其它一些注解包括:@EventListener用于設置回調方法監聽Spring制定的以及自定義的各種事件;@EnableAspectJAutoProxy用于開啟支持AspectJ的@Aspect切面配置支持,使用Spring Boot引入了AOP啟動器的話不需要顯式開啟

Spring Web注解

Spring MVC的各種注解對應了SpringMVC各方面的功能,下面我們來了解一下:

- 首先是三個定義了Bean特殊生命周期的復合注解:@RequestScope、@SessionScope@ApplicationScope。在Web應用中,我們可能需要Bean跟隨請求、會話和應用程序的聲明周期來進行創建,這個時候可以直接使用這三個快捷的復合注解

- 接下去可以看到各種@XXXMapping的注解,分別用于配置HandlerMethod匹配到不同的Http Method,當然不使用這些快捷的注解也是可以的,直接使用@RequestMapping然后手動設置method

-@ResponseStatus可以用到方法上也可以用到異常上,前者會直接使請求得到指定的響應代碼或原因(可以配合@ExceptionHandler使用),后者可以實現遇到指定異常的時候給出指定的響應代碼或原因,@ResponseBody我們實現Restful接口的時候(@RestController)最常用了,把返回內容(序列化后)輸出到請求體

- Spring MVC給了我們各種注解方便我們從HTTP請求各種地方獲取參數,@RequestBody從請求體(處理復雜數據,比如JSON),@RequestHeader從請求頭,@CookieValue從cookie中,@SessionAttribute從會話中,@RequestAttribute從請求的Attribute中(比如過濾器和攔截器手動設置的一些臨時數據),@RequestParam從請求參數(處理簡單數據,鍵值對),@PathVariable從路徑片段,@MatrixAttribute矩陣變量允許我們采用特殊的規則在URL路徑后加參數(分號區分不同參數,逗號為參數增加多個值)

-@ControllerAdvice是一個重要注解,允許我們在集中的地方配置控制器(有@RequestMapping的方法)相關的增強(@RestControllerAdvice也是差不多的,只是相當于為@ExceptionHandler加上了@ResponseBody)。那么可以應用哪些增強呢?首先是可以用@ExceptionHandler進行統一的全局異常處理;第二是@InitBinder用來設置WebDataBinder,WebDataBinder用來自動綁定前臺請求參數到Model中;第三是@ModelAttribute讓全局的@RequestMapping都能獲得在此處設置的鍵值對。當然,這里說的@InitBinder和@ExceptionHandler也可以不定義在@ControllerAdvice內部(作為全局開啟),定義在Controller內部應用到某個Controller也是可以的

- 其它還有一些注解比如:@CrossOrigin可以用到Controller或Method上(需要配合@RequestMapping)設置細粒度的跨域行為

在之前的文章中我們也提到,對于Spring MVC,定義自己的注解應用到參數、方法、控制器上,配合HandlerMethodArgumentResolver、XXAdvise、以及Interceptor實現具體的功能來使用太太常見了,幾乎所有的非業務橫切關注點,我們都不應該在方法實現中重復任何一行代碼。

Spring Boot注解

- 來看一下上下文相關的注解:@ConfigurationProperties很常用(配合@EnableConfigurationProperties注解來設置需要啟用的配置類),用來自定義配置類和配置文件進行關聯;@DeprecatedConfigurationProperty用于標記廢棄的配置以及設置替代配置和告知廢棄原因;@ConfigurationPropertiesBinding用于指定自定義的轉換器用于配置解析的時的類型轉換;@NestedConfigurationProperty用于關聯外部的類型作為嵌套配置類

- 再看看自動配置相關的注解,自動配置是Spring Boot最重要的特性,在之前的系列文章中我有提到一個觀點,IOC是好事情,但是把組件內部的一些默認配置以及組件和組件的組裝交給外部用戶來配置其實是不合理的,組件應當可以自動進行自我配置實現開箱急用,只有需要自定義組件的時候才要求外部來進行個性化配置:@EnableAutoConfiguration注解可以啟用自動配置,Spring Boot應用程序一般我們會直接使用復合注解@SpringBootApplication;@AutoConfigureOrder(值越小優先級越高)、@AutoConfigureAfter、@AutoConfigureBefore用于設置自動配置類加載順序,以及精確控制加載依賴關系,有的時候我們的自動配置需要相互依賴或者會相互干擾,需要手動調節

- 最后來看一下十幾種配置條件,用好這些注解是實現完善的自動配置的關鍵:@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+設置配置文件三部曲。

- 首先是 Netflix包下的一些注解,各種EnableXXX就不說了,參考前一篇文章,之前沒介紹過@RibbonClient,這個注解用來為負載均衡客戶端做一些自定義的配置,可以進一步配置或自定義從哪里獲取服務端列表、負載均衡策略、Ping也就是服務鑒活策略等等

- client包下的@SpringCloudApplication之前文章中我們也沒有使用到,這是一個復合注解就是@SpringBootApplication+@EnableDiscoveryClient+@EnableCircuitBreaker,Spring Cloud那堆東西很多,還是自己親手定義一個一個功能的注解來的踏實;@LoadBalanced注解用于和RestTemplate配合使用構成一個負載均衡的Http客戶端,實現原理上其實這個注解是一個@Qualifier注解,Spring會為所有@LoadBalanced的RestTemplate加入一個LoadBalancerInterceptor(實現ClientHttpRequestInterceptor)實現負載均衡

- sleuth包下面的注解和鏈路跟蹤相關,比較常用的是通過@SpanName手動設置span的名稱,其它注解對于業務開發并不常用

為了讓學習變得輕松、高效,今天給大家免費分享一套Java入門教學資源。幫助大家在成為Java架構師的道路上披荊斬棘。需要資料的歡迎加入學習交流群:9285,05736

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 【姓名:郭雅婷】 【組名:吃瓜】 這是我人生第一幅思維導圖。哈哈,配上顏色就覺得很漂亮啦。 這個中心圖代表我自己,...
    gyating閱讀 420評論 3 2
  • 作者:瑾岫 你離開之后,每個夜晚我都會伴著手機里的音樂入眠。此刻是凌晨兩點,我剛剛睡了一個小時又醒了,不知道為什么...
    瑾岫閱讀 377評論 0 0
  • 我看人生 每個人的人生都不一樣,有多少人就會有多少種人生。人生不由己,死卻由己,這就是眾多英烈視死如歸的由來。貪生...
    梁州酒徒閱讀 219評論 0 0
  • 為什么總有這么多抓狂的事情,為什么不會去改變自己,所做的這一切有什么意義。 堅持下去會有不同嗎? 何必如此傷心難過...
    Aries曉曉閱讀 107評論 0 0