Spring提供了几种方式来注册Bean,日常开发中使用最多的是ComponentScan。得益于ComponentScan,注册bean非常的简单,只需要在被注册的类上声明Component或者Service等注解即可。 除了ComponentScan,Spring还支持使用Configuration注解来注册Bean。在大型的项目中,模块化开发能极大地降低系统的复杂性,这时需要每个模块来定义本模块Bean注册情况,Configuration发挥着巨大的作用。ConfigurationConditionalOnProperty(prefixmodule。wxlogin,valueenable,havingValuetrue)ComponentScan(basePackagescom。lin。decorator。wxlogin)publicclassWxLoginConfiguration{} 每个模块定义了Configuration之后,需要将多个模块的Configuration组合。Spring提供了Import注解来实现多个Configuration组合。Import(WxLoginConfiguration。class)publicclassApplication{publicstaticvoidmain(String〔〕args){SpringApplication。run(Application。class,args);}} Spring官方文档中关于Import的描述如下: ProvidesfunctionalityequivalenttotheelementinSpringXML。AllowsforimportingConfigurationclasses,ImportSelectorandImportBeanDefinitionRegistrarimplementations,aswellasregularcomponentclasses(asof4。2;analogoustoAnnotationConfigApplicationContext。register(java。lang。C?。。。))。 BeandefinitionsdeclaredinimportedConfigurationclassesshouldbeaccessedbyusingAutowiredinjection。Eitherthebeanitselfcanbeautowired,ortheconfigurationclassinstancedeclaringthebeancanbeautowired。Thelatterapproachallowsforexplicit,IDEfriendlynavigationbetweenConfigurationclassmethods。 除了Configuration,Import还支持引入ImportSelector和ImportBeanDefinitionRegistrar。既然要全面了解Import机制,那另外两个也要一探究竟。ImportSelector Spring官方文档中,对ImportSelector的描述如下: InterfacetobeimplementedbytypesthatdeterminewhichConfigurationclass(es)shouldbeimportedbasedonagivenselectioncriteria,usuallyoneormoreannotationattributes。 从字面上理解,ImportSelector可以根据注解里面的一个或多个属性来决定引入哪些Configuration。举个例子: 小伙伴都用过Transactional注解,Transactional注解生效的前提是EnableTransactionManagement生效。看过EnableTransactionManagement源代码的小伙伴应该都知道,它通过Import引入了一个ImportSelector。Target(ElementType。TYPE)Retention(RetentionPolicy。RUNTIME)DocumentedImport(TransactionManagementConfigurationSelector。class)publicinterfaceEnableTransactionManagement{booleanproxyTargetClass()AdviceModemode()defaultAdviceMode。PROXY;intorder()defaultOrdered。LOWESTPRECEDENCE;} 而TransactionManagementConfigurationSelector会根据注解里面的AdviceMode不同,来确定引入不同的Configuration。protectedString〔〕selectImports(AdviceModeadviceMode){switch(adviceMode){casePROXY:returnnewString〔〕{AutoProxyRegistrar。class。getName(),ProxyTransactionManagementConfiguration。class。getName()};caseASPECTJ:returnnewString〔〕{determineTransactionAspectClass()};default:}}ImportBeanDefinitionRegistrar Spring官方文档中,对ImportBeanDefinitionRegistrar的描述如下: InterfacetobeimplementedbytypesthatregisteradditionalbeandefinitionswhenprocessingConfigurationclasses。Usefulwhenoperatingatthebeandefinitionlevel(asopposedtoBeanmethodinstancelevel)isdesiredornecessary。 字面意思是,通过继承这个接口可以额外定义Bean。举个例子: 在使用Mybatis的时候,会使用到MapperScan这个注解,这个注解通过Import引入了ImportBeanDefinitionRegistrar,这也解释了为什么我们只在Interface上申明了一个Mapper,mybatis就帮我们生成好了Bean。Retention(RetentionPolicy。RUNTIME)Target(ElementType。TYPE)DocumentedImport(MapperScannerRegistrar。class)Repeatable(MapperScans。class)publicinterfaceMapperScan{} 有小伙伴在编码工程中,并没有使用MapperScan,为什么也能正常使用呢?其实是Mybatisstarter的功劳。在MybatisAutoConfiguration里面定义了ImportBeanDefinitionRegistrar,当MapperScan没有激活时,它就会生效。org。springframework。context。annotation。ConfigurationImport(AutoConfiguredMapperScannerRegistrar。class)ConditionalOnMissingBean({MapperFactoryBean。class,MapperScannerConfigurer。class})publicstaticclassMapperScannerRegistrarNotFoundConfigurationimplementsInitializingBean{OverridepublicvoidafterPropertiesSet(){logger。debug(NotfoundconfigurationforregisteringmapperbeanusingMapperScan,MapperFactoryBeanandMapperScannerConfigurer。);}}Import执行流程 了解了Import支持的三种不同类型的资源之后,接下来通过debug来看一下import的执行过程。通过设置断点,发现在ConfigurationClassParser类中,通过深度遍历来处理Import。privatevoidcollectImports(SourceClasssourceClass,SetSourceClassimports,SetSourceClassvisited)throwsIOException{if(visited。add(sourceClass)){for(SourceClassannotation:sourceClass。getAnnotations()){StringannNameannotation。getMetadata()。getClassName();if(!annName。equals(Import。class。getName())){collectImports(annotation,imports,visited);}}imports。addAll(sourceClass。getAnnotationAttributes(Import。class。getName(),value));}} 而上面介绍的ImportSelector,需要调用selectImports方法进行解析。privatevoidprocessImports(ConfigurationClassconfigClass,SourceClasscurrentSourceClass,CollectionSourceClassimportCandidates,PredicateStringexclusionFilter,booleancheckForCircularImports){if(importCandidates。isEmpty()){}if(checkForCircularImportsisChainedImportOnStack(configClass)){this。problemReporter。error(newCircularImportProblem(configClass,this。importStack));}else{this。importStack。push(configClass);try{for(SourceClasscandidate:importCandidates){if(candidate。isAssignable(ImportSelector。class)){CandidateclassisanImportSelectordelegatetoittodetermineimportsC?candidateClasscandidate。loadClass();ImportSelectorselectorParserStrategyUtils。instantiateClass(candidateClass,ImportSelector。class,this。environment,this。resourceLoader,this。registry);PredicateStringselectorFilterselector。getExclusionFilter();if(selectorFilter!null){exclusionFilterexclusionFilter。or(selectorFilter);}if(selectorinstanceofDeferredImportSelector){this。deferredImportSelectorHandler。handle(configClass,(DeferredImportSelector)selector);}else{String〔〕importClassNamesselector。selectImports(currentSourceClass。getMetadata());CollectionSourceClassimportSourceClassesasSourceClasses(importClassNames,exclusionFilter);processImports(configClass,currentSourceClass,importSourceClasses,exclusionFilter,false);}}elseif(candidate。isAssignable(ImportBeanDefinitionRegistrar。class)){CandidateclassisanImportBeanDefinitionRegistrardelegatetoittoregisteradditionalbeandefinitionsC?candidateClasscandidate。loadClass();ImportBeanDefinitionRegistrarregistrarParserStrategyUtils。instantiateClass(candidateClass,ImportBeanDefinitionRegistrar。class,this。environment,this。resourceLoader,this。registry);configClass。addImportBeanDefinitionRegistrar(registrar,currentSourceClass。getMetadata());}else{CandidateclassnotanImportSelectororImportBeanDefinitionRegistrarprocessitasanConfigurationclassthis。importStack。registerImport(currentSourceClass。getMetadata(),candidate。getMetadata()。getClassName());processConfigurationClass(candidate。asConfigClass(configClass),exclusionFilter);}}}catch(BeanDefinitionStoreExceptionex){}catch(Throwableex){thrownewBeanDefinitionStoreException(Failedtoprocessimportcandidatesforconfigurationclass〔configClass。getMetadata()。getClassName()〕,ex);}finally{this。importStack。pop();}}} 通过递归的方式,实现了资源的加载。 本文基于Spring5。3。4,如有错误,欢迎指正。