Springboot @Import 詳解
SpringBoot 的 @Import 用于將指定的類實(shí)例注入之Spring IOC Container中。
今天抽空在仔細(xì)看了下Springboot 關(guān)于 @Import 的處理過程, 記下來以后看。
1. @Import
先看Spring對(duì)它的注釋 (文檔貼過來的), 總結(jié)下來作用就是和xml配置的 <import />標(biāo)簽作用一樣,允許通過它引入 @Configuration 注解的類 (java config), 引入ImportSelector接口(這個(gè)比較重要, 因?yàn)橐ㄟ^它去判定要引入哪些@Configuration) 和 ImportBeanDefinitionRegistrar 接口的實(shí)現(xiàn), 也包括 @Component注解的普通類。
但是如果要引入另一個(gè)xml 文件形式配置的 bean, 則需要通過 @ImportResource 注解。
/** * Indicates one or more {@link Configuration @Configuration} classes to import. * * <p>Provides functionality equivalent to the {@code <import/>} element in Spring XML. * Allows for importing {@code @Configuration} classes, {@link ImportSelector} and * {@link ImportBeanDefinitionRegistrar} implementations, as well as regular component * classes (as of 4.2; analogous to {@link AnnotationConfigApplicationContext#register}). * * <p>{@code @Bean} definitions declared in imported {@code @Configuration} classes should be * accessed by using {@link org.springframework.beans.factory.annotation.Autowired @Autowired} * injection. Either the bean itself can be autowired, or the configuration class instance * declaring the bean can be autowired. The latter approach allows for explicit, IDE-friendly * navigation between {@code @Configuration} class methods. * * <p>May be declared at the class level or as a meta-annotation. * * <p>If XML or other non-{@code @Configuration} bean definition resources need to be * imported, use the {@link ImportResource @ImportResource} annotation instead. * * @author Chris Beams * @author Juergen Hoeller * @since 3.0 * @see Configuration * @see ImportSelector * @see ImportResource */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Import { /** * {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar} * or regular component classes to import. */ Class<?>[] value(); }
2. ImportSelector
因?yàn)?@Import 的實(shí)現(xiàn)有很多時(shí)候需要借助 ImportSelector 接口, 所以我們?cè)倏聪逻@個(gè)接口的描述, 總結(jié)下來就是需要通過這個(gè)接口的實(shí)現(xiàn)去決定要引入哪些 @Configuration。 它如果實(shí)現(xiàn)了以下四個(gè)Aware 接口, 那么spring保證會(huì)在調(diào)用它之前先調(diào)用Aware接口的方法。
至于為什么要保證調(diào)用Aware, 我個(gè)人覺得應(yīng)該是你可以通過這些Aware去感知系統(tǒng)里邊所有的環(huán)境變量, 從而決定你具體的選擇邏輯。
/** * Interface to be implemented by types that determine which @{@link Configuration} * class(es) should be imported based on a given selection criteria, usually one or more * annotation attributes. * * <p>An {@link ImportSelector} may implement any of the following * {@link org.springframework.beans.factory.Aware Aware} interfaces, and their respective * methods will be called prior to {@link #selectImports}: * <ul> * <li>{@link org.springframework.context.EnvironmentAware EnvironmentAware}</li> * <li>{@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware}</li> * <li>{@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware}</li> * <li>{@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}</li> * </ul> * * <p>ImportSelectors are usually processed in the same way as regular {@code @Import} * annotations, however, it is also possible to defer selection of imports until all * {@code @Configuration} classes have been processed (see {@link DeferredImportSelector} * for details). * * @author Chris Beams * @since 3.1 * @see DeferredImportSelector * @see Import * @see ImportBeanDefinitionRegistrar * @see Configuration */ public interface ImportSelector { /** * Select and return the names of which class(es) should be imported based on * the {@link AnnotationMetadata} of the importing @{@link Configuration} class. */ String[] selectImports(AnnotationMetadata importingClassMetadata); }
3. Springboot 對(duì)@Import注解的處理過程
Springboot對(duì)注解的處理都發(fā)生在AbstractApplicationContext -> refresh() -> invokeBeanFactoryPostProcessors(beanFactory) -> ConfigurationClassPostProcessor -> postProcessBeanDefinitionRegistry()方法中。
(稍微說下也免得我自己忘了, springboot初始化的普通context(非web) 是AnnotationConfigApplicationContext, 在初始化的時(shí)候會(huì)初始化兩個(gè)工具類, AnnotatedBeanDefinitionReader 和 ClassPathBeanDefinitionScanner 分別用來從 annotation driven 的配置和xml的配置中讀取beanDefinition并向context注冊(cè), 那么在初始化 AnnotatedBeanDefinitionReader 的時(shí)候, 會(huì)向BeanFactory注冊(cè)一個(gè)ConfigurationClassPostProcessor 用來處理所有的基于annotation的bean, 這個(gè)ConfigurationClassPostProcessor 是 BeanFactoryPostProcessor 的一個(gè)實(shí)現(xiàn),springboot會(huì)保證在 invokeBeanFactoryPostProcessors(beanFactory) 方法中調(diào)用注冊(cè)到它上邊的所有的BeanFactoryPostProcessor)
如下代碼顯示是通過 ConfigurationClassParser 類來轉(zhuǎn)換的
// Parse each @Configuration class ConfigurationClassParser parser = new ConfigurationClassParser( this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry);
那么在 ConfigurationClassParser -> processConfigurationClass() -> doProcessConfigurationClass() 方法中我們找到了 (這里邊的流程還是很清楚的, 分別按次序處理了@PropertySource, @ComponentScan, @Import, @ImportResource, 在處理這些注解的時(shí)候是通過遞歸處理來保證所有的都被處理了)
// Process any @Import annotations processImports(configClass, sourceClass, getImports(sourceClass), true);
那接下來就看它到底是怎么做的 . 流程依然清晰 :
首先, 判斷如果被import的是 ImportSelector.class 接口的實(shí)現(xiàn), 那么初始化這個(gè)被Import的類, 然后調(diào)用它的selectImports方法去獲得所需要的引入的configuration, 然后遞歸處理
其次, 判斷如果被import的是 ImportBeanDefinitionRegistrar 接口的實(shí)現(xiàn), 那么初始化后將對(duì)當(dāng)前對(duì)象的處理委托給這個(gè)ImportBeanDefinitionRegistrar (不是特別明白, 只是我的猜測(cè))
最后, 將import引入的類作為一個(gè)正常的類來處理 ( 調(diào)用最外層的doProcessConfigurationClass())
所以, 從這里我們知道, 如果你引入的是一個(gè)正常的component, 那么會(huì)作為@Compoent或者@Configuration來處理, 這樣在BeanFactory里邊可以通過getBean拿到, 但如果你是 ImportSelector 或者 ImportBeanDefinitionRegistrar 接口的實(shí)現(xiàn), 那么spring并不會(huì)將他們注冊(cè)到beanFactory中,而只是調(diào)用他們的方法。
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection<SourceClass> importCandidates, boolean checkForCircularImports) { if (importCandidates.isEmpty()) { return; } if (checkForCircularImports && isChainedImportOnStack(configClass)) { this.problemReporter.error(new CircularImportProblem(configClass, this.importStack)); } else { this.importStack.push(configClass); try { for (SourceClass candidate : importCandidates) { if (candidate.isAssignable(ImportSelector.class)) { // Candidate class is an ImportSelector -> delegate to it to determine imports Class<?> candidateClass = candidate.loadClass(); ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class); ParserStrategyUtils.invokeAwareMethods( selector, this.environment, this.resourceLoader, this.registry); if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) { this.deferredImportSelectors.add( new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector)); } else { String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata()); Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames); processImports(configClass, currentSourceClass, importSourceClasses, false); } } else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) { // Candidate class is an ImportBeanDefinitionRegistrar -> // delegate to it to register additional bean definitions Class<?> candidateClass = candidate.loadClass(); ImportBeanDefinitionRegistrar registrar = BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class); ParserStrategyUtils.invokeAwareMethods( registrar, this.environment, this.resourceLoader, this.registry); configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata()); } else { // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar -> // process it as an @Configuration class this.importStack.registerImport( currentSourceClass.getMetadata(), candidate.getMetadata().getClassName()); processConfigurationClass(candidate.asConfigClass(configClass)); } } } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to process import candidates for configuration class [" + configClass.getMetadata().getClassName() + "]", ex); } finally { this.importStack.pop(); } } }
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- SpringBoot @Import與@Conditional注解使用詳解
- SpringBoot超詳細(xì)講解@Enable*注解和@Import
- SpringBoot中@Import注解如何正確使用
- SpringBoot2底層注解@Import用法詳解
- 淺析SpringBoot2底層注解@Conditional@ImportResource
- SpringBoot中@Import注解的使用方式
- SpringBoot如何讀取xml配置bean(@ImportResource)
- 詳解SpringBoot開發(fā)使用@ImportResource注解影響攔截器
- SpringBoot自動(dòng)裝配之@Import深入講解
相關(guān)文章
Spring?MVC啟動(dòng)之HandlerMapping作用及實(shí)現(xiàn)詳解
這篇文章主要為大家介紹了Spring?MVC啟動(dòng)之HandlerMapping作用及實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03Java使用JDBC連接數(shù)據(jù)庫的詳細(xì)步驟
本文詳細(xì)講解了Java使用JDBC連接數(shù)據(jù)庫的詳細(xì)步驟,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-01-01jpa實(shí)現(xiàn)多對(duì)多的屬性時(shí)查詢的兩種方法
這篇文章主要介紹了jpa實(shí)現(xiàn)多對(duì)多的屬性時(shí)查詢的兩種方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11Java異步調(diào)用轉(zhuǎn)同步方法實(shí)例詳解
這篇文章主要介紹了Java異步調(diào)用轉(zhuǎn)同步方法實(shí)例詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06SpringBoot異步使用@Async的原理以及線程池配置詳解
在項(xiàng)目中當(dāng)訪問其他人的接口較慢時(shí),不想程序一直卡在耗時(shí)任務(wù)上,想程序能夠并行執(zhí)行,我們可以使用多線程來并行的處理任務(wù),也可以使用spring提供的異步處理方式@Async,這篇文章主要給大家介紹了關(guān)于SpringBoot異步使用@Async的原理以及線程池配置的相關(guān)資料2021-09-09基于IDEA,Eclipse搭建Spring Boot項(xiàng)目過程圖解
這篇文章主要介紹了基于IDEA,Eclipse搭建Spring Boot項(xiàng)目過程圖解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04springboot + devtools(熱部署)實(shí)例教程
devtools是boot的一個(gè)熱部署工具,當(dāng)我們修改了classpath下的文件(包括類文件、屬性文件、頁面等)時(shí),會(huì)重新啟動(dòng)應(yīng)用。本文通過實(shí)例給大家介紹springboot+devtools熱部署,感興趣的朋友一起看看吧2017-04-04