淺談Spring?中?@EnableXXX?注解的套路
前言
在 Spring 框架中有很多實(shí)用的功能,不需要寫大量的配置代碼,只需添加幾個(gè)注解即可開啟。 其中一個(gè)重要原因是那些 @EnableXXX 注解,它可以讓你通過在配置類加上簡單的注解來快速地開啟諸如事務(wù)管理(@EnableTransactionManagement)、Spring MVC(@EnableWebMvc)或定時(shí)任務(wù)(@EnableScheduling)等功能。這些看起來簡單的注解語句提供了很多功能,但它們的內(nèi)部機(jī)制從表面上看卻不太明顯。 一方面,對于使用者來說用這么少的代碼獲得這么多實(shí)用的功能是很好的,但另一方面,如果你不了解某個(gè)東西的內(nèi)部是如何工作的,就會使調(diào)試和解決問題更加困難。
設(shè)計(jì)目標(biāo)
Spring 框架中那些 @EnableXXX 注解的設(shè)計(jì)目標(biāo)是允許用戶用最少的代碼來開啟復(fù)雜使用的功能。 此外,用戶必須能夠使用簡單的默認(rèn)值,或者允許手動配置該代碼。最后,代碼的復(fù)雜性要向框架使用者隱藏掉。 簡而言之,讓使用者設(shè)置大量的 Bean,并選擇性地配置它們,而不必知道這些 Bean 的細(xì)節(jié)(或真正被設(shè)置的內(nèi)容)。下面來看看具體的幾個(gè)例子:
@EnableScheduling (導(dǎo)入一個(gè) @Configuration 類)
首先要知道的是,@EnableXXX 注解并不神奇。實(shí)際上在 BeanFactory 中并不知道這些注解的具體內(nèi)容,而且在 BeanFactory 類中,核心功能和特定注解(如 @EnableWebMvc)或它們所存放的 jar 包(如 spring-web)之間沒有任何依賴關(guān)系。 讓我們看一下 @EnableScheduling,下面看看它是如何工作的。 定義一個(gè) SchedulingConfig 配置類,如下所示:
@Configuration @EnableScheduling public class SchedulingConfig { // some bean in here }
上面的內(nèi)容沒有什么特別之處。只是一個(gè)用 @EnableScheduling 注釋的標(biāo)準(zhǔn) Java 配置。@EnableScheduling 讓你以設(shè)定的頻率執(zhí)行某些方法。例如,你可以每 10 分鐘運(yùn)行 BankService.transferMoneyToMghio()。 @EnableScheduling 注解源碼如下:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Import(SchedulingConfiguration.class) @Documented public @interface EnableScheduling { }
上面的 EnableScheduling 注解,我們可以看到它只是一個(gè)標(biāo)準(zhǔn)的類級注解(@Target/@Retention),應(yīng)該包含在 JavaDocs 中(@Documented),但是它有一個(gè) Spring 特有的注解(@Import)。 @Import 是將一切聯(lián)系起來的關(guān)鍵。 在這種情況下,由于我們的 SchedulingConfig 被注解為 @EnableScheduling,當(dāng) BeanFactory 解析文件時(shí)(內(nèi)部是ConfigurationClassPostProcessor 在解析它),它也會發(fā)現(xiàn) @Import(SchedulingConfiguration.class) 注解,它將導(dǎo)入該值中定義的類。 在這個(gè)注解中,就是 SchedulingConfiguration。
這里導(dǎo)入是什么意思呢?在這種情況下,它只是被當(dāng)作另一個(gè) Spring Bean。 SchedulingConfiguration 實(shí)際上被注解為@Configuration,所以 BeanFactory 會把它看作是另一個(gè)配置類,所有在該類中定義的 Bean 都會被拉入你的應(yīng)用上下文,就像你自己定義了另一個(gè) @Configuration 類一樣。 如果我們檢查 SchedulingConfiguration,我們可以看到它只定義了一個(gè)Bean(一個(gè)Post Processor),它負(fù)責(zé)我們上面描述的調(diào)度工作,源碼如下:
@Configuration @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public class SchedulingConfiguration { @Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() { return new ScheduledAnnotationBeanPostProcessor(); } }
也許你會問,如果想配置 SchedulingConfiguration 中定義的 bean 呢? 這里也只是在處理普通的Bean。 所以你對其它 Bean 所使用的機(jī)制也適用于此。 在這種情況下,ScheduledAnnotationBeanPostProcessor 使用一個(gè)標(biāo)準(zhǔn)的 Spring Bean 生命周期(postProcessAfterInitialization)來發(fā)現(xiàn)應(yīng)用程序上下文何時(shí)被刷新。 當(dāng)符合條件時(shí),它會檢查是否有任何 Bean 實(shí)現(xiàn)了 SchedulingConfigurer,如果有,就使用這些 Bean 來配置自己。 其實(shí)這一點(diǎn)并不明細(xì)(在 IDE 中也不太容易找到),但它與 BeanFactory 是完全分離的,而且是一個(gè)相當(dāng)常見的模式,一個(gè) Bean 被用來配置另一個(gè) Bean。 而現(xiàn)在我們可以把所有的點(diǎn)連接起來,它(在某種程度上)很容易找到(你可以 Google 一下文檔或閱讀一下 JavaDocs)。
@EnableTransactionManagement(導(dǎo)入一個(gè) ImportSelector)
在上一個(gè)示例中,我們討論了像 @EnableScheduling 這樣的注解如何使用 @Import 來導(dǎo)入另一個(gè) @Configuration 類并使其所有的 Bean 對你的應(yīng)用程序可用(和可配置)。但是如果你想根據(jù)某些配置加載不同的 Bean 集,會發(fā)生什么呢? @EnableTransactionManagement 就是一個(gè)很好的例子。TransactioConfig 定義如下:
@Configuration @EnableTransactionManagement(mode = AdviceMode.ASPECTJ) public class TransactioConfig { // some bean in here }
再一次,上面沒有什么特別之處。只是一個(gè)用@EnableTransactionManagement注釋的標(biāo)準(zhǔn)Java配置。唯一與之前的例子有些不同的是,用戶為注釋指定了一個(gè)參數(shù)(mode=AdviceMode.ASPECTJ)。 @EnableTransactionManagement注解本身看起來像這樣。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(TransactionManagementConfigurationSelector.class) public @interface EnableTransactionManagement { boolean proxyTargetClass() default false; AdviceMode mode() default AdviceMode.PROXY; int order() default Ordered.LOWEST_PRECEDENCE; }
和前面一樣,一個(gè)相當(dāng)標(biāo)準(zhǔn)的注解,盡管這次它有一些參數(shù)。 然而,正如前文提到,@Import 注解是將一切聯(lián)系在一起的關(guān)鍵,這一點(diǎn)再次得到證實(shí)。 但區(qū)別在于,這次我們導(dǎo)入的是 TransactionManagementConfigurationSelector 這個(gè)類,通過源碼可以發(fā)現(xiàn),其實(shí)它不是一個(gè)被 @Configuration 注解的類。 TransactionManagementConfigurationSelector 是一個(gè)實(shí)現(xiàn)ImportSelector 的類。 ImportSelector 的目的是讓你的代碼選擇在運(yùn)行時(shí)加載哪些配置類。 它有一個(gè)方法,接收關(guān)于注解的一些元數(shù)據(jù),并返回一個(gè)類名數(shù)組。 在這種情況下,TransactionManagementConfigurationSelector 會查看模式并根據(jù)模式返回一些類。其中的 selectImports 方法源碼如下:
@Override protected String[] selectImports(AdviceMode adviceMode) { switch (adviceMode) { case PROXY: return new String[] {AutoProxyRegistrar.class.getName(), ProxyTransactionManagementConfiguration.class.getName()}; case ASPECTJ: return new String[] {determineTransactionAspectClass()}; default: return null; } }
這些類中的大多數(shù)是 @Configuration(例如 ProxyTransactionManagementConfiguration),通過前文介紹我們知道它們會像前面一樣工作。 對于 @Configuration 類,它們被加載和配置的方式與我們之前看到的完全一樣。 所以簡而言之,我們可以使用 @Import 和 @Configuration 類來加載一套標(biāo)準(zhǔn)的 Bean,或者使用 @Import 和 ImportSelector 來加載一套在運(yùn)行時(shí)決定的 Bean。
@EnableAspectJAutoProxy (在 Bean 定義層導(dǎo)入)
@Import 支持的最后一種情況,即當(dāng)你想直接處理 BeanRegistry(工廠)時(shí)。如果你需要操作Bean Factory或者在Bean定義層處理Bean,那么這種情況就適合你,它與上面的情況非常相似。 你的 AspectJProxyConfig 可能看起來像。
@Configuration @EnableAspectJAutoProxy public class AspectJProxyConfig { // some bean in here }
再一次,上面定義沒有什么特別的東西。只是一個(gè)用 @EnableAspectJAutoProxy 注釋的標(biāo)準(zhǔn) Java 配置。 下面是@EnableAspectJAutoProxy 的源代碼。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(AspectJAutoProxyRegistrar.class) public @interface EnableAspectJAutoProxy { boolean proxyTargetClass() default false; boolean exposeProxy() default false; }
和前面一樣,@Import 是關(guān)鍵,但這次它指向 AspectJAutoProxyRegistrar,它既沒有 @Configuration 注解,也沒有實(shí)現(xiàn) ImportSelector 接口。 這次使用的是實(shí)現(xiàn)了 ImportBeanDefinitionRegistrar。 這個(gè)接口提供了對 Bean 注冊中心(Bean Registry)和注解元數(shù)據(jù)的訪問,因此我們可以在運(yùn)行時(shí)根據(jù)注解中的參數(shù)來操作 Bean 注冊表。 如果你仔細(xì)看過前面的示例,你可以看到我們忽略的類也是 ImportBeanDefinitionRegistrar。 在 @Configuration 類不夠用的時(shí)候,這些類會直接操作 BeanFactory。
所以現(xiàn)在我們已經(jīng)涵蓋了 @EnableXXX 注解使用 @Import 將各種 Bean 引入你的應(yīng)用上下文的所有不同方式。 它們要么直接引入一組 @Configuration 類,這些類中的所有 Bean 都被導(dǎo)入到你的應(yīng)用上下文中。 或者它們引入一個(gè) ImportSelector 接口實(shí)現(xiàn)類,在運(yùn)行時(shí)選擇一組 @Configuration 類并將這些 Bean 導(dǎo)入到你的應(yīng)用上下文中。 最后,他們引入一個(gè)ImportBeanDefinitionRegistrars,可以直接與 BeanFactory 在 BeanDefinition 級別上合作。
結(jié)論
總的來說,個(gè)人認(rèn)為這種將 Bean 導(dǎo)入應(yīng)用上下文的方法很好,因?yàn)樗箍蚣苁褂谜叩氖褂媚硞€(gè)功能非常容易。不幸的是,它模糊了如何找到可用的選項(xiàng)以及如何配置它們。 此外,它沒有直接利用 IDE 的優(yōu)勢,所以很難知道哪些 Bean 正在被創(chuàng)建(以及為什么)。 然而,現(xiàn)在我們知道了 @Import 注解,我們可以使用 IDE 來挖掘一下每個(gè)注解及其相關(guān)的配置類,并了解哪些 Bean 正在被創(chuàng)建,它們?nèi)绾伪惶砑拥侥愕膽?yīng)用上下文中,以及如何配置它們。 希望對你有幫助~
到此這篇關(guān)于淺談Spring 中 @EnableXXX 注解的套路的文章就介紹到這了,更多相關(guān)Spring @EnableXXX 注解內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot+LayIM+t-io 實(shí)現(xiàn)好友申請通知流程
這篇文章主要介紹了 SpringBoot+LayIM+t-io 實(shí)現(xiàn)好友申請通知流程,本文圖文并茂給大家介紹的非常詳細(xì),需要的朋友可以參考下2017-12-12Java錯(cuò)誤:進(jìn)行語法分析時(shí)已到達(dá)文件結(jié)尾的解決
這篇文章主要介紹了Java錯(cuò)誤:進(jìn)行語法分析時(shí)已到達(dá)文件結(jié)尾的解決方案,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08Java實(shí)現(xiàn)多個(gè)數(shù)組間的排列組合
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)多個(gè)數(shù)組間的排列組合,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-02-02IDEA @SpringBootApplication報(bào)錯(cuò)原因及解決
這篇文章主要介紹了IDEA @SpringBootApplication報(bào)錯(cuò)原因及解決方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01詳解用maven搭建springboot環(huán)境的方法
本篇文章主要介紹了詳解用maven搭建springboot環(huán)境的方法,這里整理了詳細(xì)的代碼,非常具有實(shí)用價(jià)值,有需要的小伙伴可以參考下2017-08-08