BeanFactory後置處理器 - ConfigurationClassPostProcessor - Import
開始閱讀 Import 原始碼之前, 也是需要些一些測試demo, 來幫助理解和除錯
demo
建幾個內容一樣的類, 如: IndexDao1, IndexDao2, IndexDao3
其具體內容如下:
public class IndexDao1 { public IndexDao1() { System.out.println("IndexDao1 -- constructor"); } private String name = "IndexDao1"; public String getName() { returnname; } public void setName(String name) { this.name = name; } }
配置類:
@EnableIndexRegistrar @EnableIndex @Import(IndexDao3.class) public class StartConfig {}
註解類:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(IndexSelector.class) public @interfaceEnableIndex { boolean open() default true; }
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(IndexRegistrar.class) public @interface EnableIndexRegistrar { boolean open() default true; }
selector:
public class IndexRegistrar implements ImportBeanDefinitionRegistrar{ @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { AnnotationAttributes annAttr = AnnotationAttributes.fromMap( importingClassMetadata.getAnnotationAttributes(EnableIndexRegistrar.class.getName()) ); boolean open = (boolean) annAttr.get("open"); if(!open){ return; } BeanDefinition bd = new RootBeanDefinition(IndexDao2.class); registry.registerBeanDefinition("indexDao2", bd); } }
public class IndexSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { AnnotationAttributes annAttr = AnnotationAttributes.fromMap( importingClassMetadata.getAnnotationAttributes(EnableIndex.class.getName()) ); boolean open = (boolean) annAttr.get("open"); if(!open){ return new String[0]; } return new String[]{IndexDao1.class.getName()}; } }
測試方法:
public static void main(String[] args) { AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext(StartConfig.class); IndexDao1 bean1 = acac.getBean(IndexDao1.class); IndexDao2 bean2 = acac.getBean(IndexDao2.class); IndexDao3 bean3 = acac.getBean(IndexDao3.class); }
結果:
這個demo中, 我使用了 selector, registrar, 普通類的方式, 分別讓把 IndexDao1, IndexDao2, IndexDao3 交給spring了.
通過 import 這種方式, 可以控制載入一些元件, 如 mybatis, EnableCaching
原始碼
接著之前的org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass
這個方法中呼叫了 processImports , 這個是用來處理 Import 的.
/** * 處理@Import imports 3種情況 * ImportSelector 將類的字串陣列返回給spring, 這些類的建立過程, 完全由 spring 去解析建立, 經典示例: @EnableCaching * 普通類 普通類會生成 db, 放到 Map<ConfigurationClass, ConfigurationClass> configurationClasses 中,
* 等待 parse 方法執行完後, 註冊到 spring 容器中, 由 spring 去建立 * ImportBeanDefinitionRegistrar 使用者建立(或掃描建立) bd, 然後將這些bd註冊到容器中, 由spring去建立, 經典示例: mybatis */ 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); //判斷是否是 延遲Import, 這個不用管 if (selector instanceof DeferredImportSelector) { this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector); } else { //拿到 import 返回的類的字串陣列 String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata()); //將字串解析為 SourceClass, 裡面通過 ClassUtils.forName 的方式, 載入類, 再封裝為 SourceClass Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames); //檢測 返回的類中, 是否還有 @Import, 這裡需要遞迴, 因為不能確定出來的類中, 是否還有@Import 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(); } } }
先看一下, ImportSelector 和ImportBeanDefinitionRegistrar 裡面分別是什麼
ImportSelector
public interface ImportSelector { String[] selectImports(AnnotationMetadata importingClassMetadata); }
ImportSelector 返回的, 其實是 類的名稱的字串陣列.
ImportBeanDefinitionRegistrar
public interface ImportBeanDefinitionRegistrar { void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry); }
這個介面沒有返回值, 但是提供了一個註冊器. 我們可以在註冊器中, 註冊 bd
上面這個processImports方法中, 呼叫了 ImportSelector 的 selectImports 方法, 但是卻沒有呼叫ImportBeanDefinitionRegistrar 的方法.
說明她的方法不是在這裡呼叫的. 那麼他是在哪裡呼叫的呢?
org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions 中的
this.reader.loadBeanDefinitions(configClasses);
其呼叫棧:
org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass
org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsFromRegistrars
org.springframework.context.annotation.ImportBeanDefinitionRegistrar#registerBeanDefinitions
普通類:
普通類的時候, 不會呼叫什麼介面, 但是會對這個普通類進行解析, 生成 bd