<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
在專案開發的過程中,我們會遇到很多名字為 @Enablexxx 的註解,比如@EnableApolloConfig
、 @EnableFeignClients
、 @EnableAsync
等。他們的功能都是通過這樣的註解實現一個開關,決定了是否開啟某個功能模組的所有元件的自動化設定,這極大的降低了我們的使用成本。
那麼你是好奇過 @Enablexxx 是如何達到這種效果呢,其作用機制是怎麼樣的呢?
按照預設的習慣,我們會把某個功能模組的開啟註解定義為 @Enablexxx,功能的實現和名字格式其實無關,而是其內部實現,這裡用 @EnableAsync 來舉例子。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(AsyncConfigurationSelector.class) public @interface EnableAsync { …… }
可以看到除了3個通用註解,還有一個@Import(AsyncConfigurationSelector.class)
註解,顯然它真正在這裡發揮了關鍵作用,它可以往容器中注入一個設定類。
在 Spring 容器啟動的過程中,執行到呼叫invokeBeanFactoryPostProcessors(beanFactory)
方法的時候,會呼叫所有已經註冊的 BeanFactoryPostProcessor,然後會呼叫實現 BeanDefinitionRegistryPostProcessor
介面的後置處理器 ConfigurationClassPostProcessor
,呼叫其 postProcessBeanDefinitionRegistry()
方法, 在這裡會解析通過註解設定的類,然後呼叫 ConfigurationClassParser#doProcessConfigurationClass()
方法,最終會走到processImports()
方法,對 @Import 註解進行處理,具體流程如下。
如果這部分流程不是很理解,推薦詳細閱讀一下 Spring 生命週期相關的程式碼,不過不重要,不影響理解後面的內容。
@Import 註解的功能是在ConfigurationClassParser
類的 processImports()
方法中實現的,對於這個方法我已經做了詳細的註釋,請檢視。
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter, boolean checkForCircularImports) { // 如果使用@Import註解修飾的類集合為空,直接返回 if (importCandidates.isEmpty()) { return; } // 通過一個棧結構解決迴圈引入 if (checkForCircularImports && isChainedImportOnStack(configClass)) { this.problemReporter.error(new CircularImportProblem(configClass, this.importStack)); } else { // 新增到棧中,用於處理迴圈import的問題 this.importStack.push(configClass); try { // 遍歷每一個@Import註解的類 for (SourceClass candidate : importCandidates) { // 1. // 檢驗設定類Import引入的類是否是ImportSelector子類 if (candidate.isAssignable(ImportSelector.class)) { // Candidate class is an ImportSelector -> delegate to it to determine imports // 候選類是一個匯入選擇器->委託來確定是否進行匯入 Class<?> candidateClass = candidate.loadClass(); // 通過反射生成一個ImportSelect物件 ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class, this.environment, this.resourceLoader, this.registry); // 獲取選擇器的額外過濾器 Predicate<String> selectorFilter = selector.getExclusionFilter(); if (selectorFilter != null) { exclusionFilter = exclusionFilter.or(selectorFilter); } // 判斷參照選擇器是否是DeferredImportSelector介面的範例 // 如果是則應用選擇器將會在所有的設定類都載入完畢後載入 if (selector instanceof DeferredImportSelector) { // 將選擇器新增到deferredImportSelectorHandler範例中,預留到所有的設定類載入完成後統一處理自動化設定類 this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector); } else { // 獲取引入的類,然後使用遞迴方式將這些類中同樣新增了@Import註解除參照的類 // 執行 ImportSelector.selectImports String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata()); Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter); // 遞迴處理,被Import進來的類也有可能@Import註解 processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false); } } // 2. // 如果是實現了ImportBeanDefinitionRegistrar介面的bd else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) { // Candidate class is an ImportBeanDefinitionRegistrar -> // delegate to it to register additional bean definitions // 候選類是ImportBeanDefinitionRegistrar -> 委託給當前註冊器註冊其他bean Class<?> candidateClass = candidate.loadClass(); ImportBeanDefinitionRegistrar registrar = ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class, this.environment, this.resourceLoader, this.registry); /** * 放到當前configClass的importBeanDefinitionRegistrars中 * 在ConfigurationClassPostProcessor處理configClass時會隨之一起處理 */ configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata()); } else { // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar -> // process it as an @Configuration class // 候選類既不是ImportSelector也不是ImportBeanDefinitionRegistrar-->將其作為@Configuration設定類處理 this.importStack.registerImport( currentSourceClass.getMetadata(), candidate.getMetadata().getClassName()); /** * 如果Import的型別是普通類,則將其當作帶有@Configuration的類一樣處理 */ processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter); } } } catch (BeanDefinitionStoreException ex) { …… finally { this.importStack.pop(); } } }
上述程式碼的核心邏輯無非就是如下幾個步驟。
ImportSelector
介面,就呼叫 ImportSelector
的 selectImports()
方法,這個方法返回的是一批設定類的全限定名,然後遞迴呼叫processImports()
繼續解析這些設定類,比如可以 @Import 的類裡面有 @Import 註解,在這裡可以遞迴處理。ImportSelector
介面,而是實現了ImportBeanDefinitionRegistrar
介面,則把對應的範例放入importBeanDefinitionRegistrars
這個Map中,等到ConfigurationClassPostProcessor
處理 configClass 的時候,會與其他設定類一同被呼叫 ImportBeanDefinitionRegistrar
的 registerBeanDefinitions()
方法,以實現往 Spring 容器中注入一些 BeanDefinition。所以到這裡,你應該明白 @Import 的作用機制了吧。對上述邏輯我總結了一張圖,如下。
繼續之前提到的 @EnableAsync 作為例子,原始碼如下。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(AsyncConfigurationSelector.class) public @interface EnableAsync { Class<? extends Annotation> annotation() default Annotation.class; boolean proxyTargetClass() default false; AdviceMode mode() default AdviceMode.PROXY; int order() default Ordered.LOWEST_PRECEDENCE; } // @Override public final String[] selectImports(AnnotationMetadata importingClassMetadata) { …… // 獲取 Mode AdviceMode adviceMode = attributes.getEnum(getAdviceModeAttributeName()); // 模板方法,由子類去實現 String[] imports = selectImports(adviceMode); if (imports == null) { throw new IllegalArgumentException("Unknown AdviceMode: " + adviceMode); } return imports; } public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> { @Override @Nullable public String[] selectImports(AdviceMode adviceMode) { switch (adviceMode) { case PROXY: return new String[] {ProxyAsyncConfiguration.class.getName()}; case ASPECTJ: return new String[] {ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME}; default: return null; } } }
它通過 @Import 註解引入了AsyncConfigurationSelector
設定類,它繼承了 AdviceModeImportSelector
類,而後者實現了 ImportSelector
介面,裡面的實現了一個由註解指定 mode 屬性來決定返回的設定類的邏輯,而 mode 的預設值就是 AdviceMode.PROXY
。
對應 switch 邏輯,將返回 ProxyAsyncConfiguration
類的全限定名。這就對應了 @Import 處理邏輯的第一個 if 邏輯塊,它將會解析這個類,然後遞迴呼叫processImports()
,再次進入此方法,進入第三個else邏輯塊,將其當作一個普通設定類解析。可以看到 ProxyAsyncConfiguration
其實就是 @Configuration 類,它的作用是註冊一個 Bean 物件 AsyncAnnotationBeanPostProcessor。
@Configuration @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration { @Bean(name = TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public AsyncAnnotationBeanPostProcessor asyncAdvisor() { …… return bpp; } }
以上就是Spring註解@Import原理解析的詳細內容,更多關於Spring註解@Import原理的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45