在spring框架下做開發時,@Import是常見的注解,可以用來動態創建bean,今天我們先從源碼分析原理,再用實戰來驗證Import的作用;
文章概覽
本章由以下幾部分組成:
1. 從Enable前綴的注解談起,揭示常見的Enable注解與Import注解的關系;
2. 常見的四種Import注解用法列舉;
3. 分析spring源碼,揭示Import注解的工作原理;
4. 官方API文檔中的疑問解答;
5. 實戰通過Import注解動態創建bean實例;
從Enable前綴的注解談起
有很多注解都以Enable為前綴,例如配置異步調用的注解EnableAsync,其源碼如下 :
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(AsyncConfigurationSelector.class) public @interface EnableAsync { Class<? extends Annotation> annotation() default Annotation.class; AdviceMode mode() default AdviceMode.PROXY; int order() default Ordered.LOWEST_PRECEDENCE; }
從以上代碼可見,使異步調用生效的關鍵是@Import(AsyncConfigurationSelector.class),通過此注解spring容器會創建AsyncConfigurationSelector實例並調用其selectImports方法,完成異步調用相關的配置;再多看幾個Enable前綴的注解的源碼,例如EnableBatchProcessing、EnableCaching、EnableDiscoveryClient等,也都是通過Import來生效的,這種方式值得我們學習,在業務開發中也能用類似方式來對bean實例做控制;
常見的四種Import注解用法列舉
在@Import注解的參數中可以填寫類名,例如@Import(Abc.class),根據類Abc的不同類型,spring容器有以下四種處理方式:
1. 如果Abc類實現了ImportSelector接口,spring容器就會實例化Abc類,並且調用其selectImports方法;
2. DeferredImportSelector是ImportSelector的子類,如果Abc類實現了DeferredImportSelector接口,spring容器就會實例化Abc類,並且調用其selectImports方法,和ImportSelector的實例不同的是,DeferredImportSelector的實例的selectImports方法調用時機晚於ImportSelector的實例,要等到@Configuration注解中相關的業務全部都處理完了才會調用(具體邏輯在ConfigurationClassParser.processDeferredImportSelectors方法中)。
3. 如果Abc類實現了ImportBeanDefinitionRegistrar接口,spring容器就會實例化Abc類,並且調用其registerBeanDefinitions方法;
4. 如果Abc沒有實現ImportSelector、DeferredImportSelector、ImportBeanDefinitionRegistrar等其中的任何一個,spring容器就會實例化Abc類,官方說明在這里;
分析spring源碼,揭示Import注解的工作原理
接下來通過spring源碼來了解spring容器是如何處理Import注解的;
1. 先看spring容器的初始化代碼,定位AbstractApplicationContext類的refresh方法,里面會調用invokeBeanFactoryPostProcessors方法,如下圖紅框所示:
2. 展開invokeBeanFactoryPostProcessors方法,繼續追蹤到了PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors方法,具體操作如下圖紅框所示:
3. 對於上圖分析的調用invokeBeanDefinitionRegistryPostProcessors方法時作為入參傳入的bean,ConfigurationClassPostProcessor類的實例是符合過濾要求的:既實現了BeanDefinitionRegistryPostProcessor接口,又實現了PriorityOrdered接口,因此,在invokeBeanDefinitionRegistryPostProcessors方法中,ConfigurationClassPostProcessor類的postProcessBeanDefinitionRegistry方法被調用:
4. ConfigurationClassPostProcessor類的postProcessBeanDefinitionRegistry方法中,如下圖紅框所示,processConfigBeanDefinitions方法負責處理@Configuration注解相關的業務:
5. processConfigBeanDefinitions方法代碼如下,請注意中文注釋:
//被確認為配置類的bean定義都放在集合configCandidates中 Set<BeanDefinitionHolder> configCandidates = new LinkedHashSet<BeanDefinitionHolder>(); //取所有bean的名稱 String[] candidateNames = registry.getBeanDefinitionNames(); //逐個檢查每個bean for (String beanName : candidateNames) { //取得每個bean的定義對象 BeanDefinition beanDef = registry.getBeanDefinition(beanName); if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) || ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) { if (logger.isDebugEnabled()) { logger.debug("Bean definition has already been processed as a configuration class: " + beanDef); } } //注意:ConfigurationClassUtils.checkConfigurationClassCandidate方法非常值得一看,里面的通過當前類的注解來判斷是否為配置類 else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) { //例如有@Configuration注解的類,被判定為配置類,放入集合configCandidates中 configCandidates.add(new BeanDefinitionHolder(beanDef, beanName)); } } // 如果一個配置類都沒找到,就直接返回了 if (configCandidates.isEmpty()) { return; } // Detect any custom bean name generation strategy supplied through the enclosing application context SingletonBeanRegistry singletonRegistry = null; if (registry instanceof SingletonBeanRegistry) { singletonRegistry = (SingletonBeanRegistry) registry; if (!this.localBeanNameGeneratorSet && singletonRegistry.containsSingleton(CONFIGURATION_BEAN_NAME_GENERATOR)) { BeanNameGenerator generator = (BeanNameGenerator) singletonRegistry.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR); this.componentScanBeanNameGenerator = generator; this.importBeanNameGenerator = generator; } } //實例化ConfigurationClassParser對象,用來處理配置類 ConfigurationClassParser parser = new ConfigurationClassParser( this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry); Set<ConfigurationClass> alreadyParsed = new HashSet<ConfigurationClass>(configCandidates.size()); do { //parse方法是處理配置類邏輯的核心代碼 parser.parse(configCandidates); parser.validate();
6. 看ConfigurationClassParser類的parse方法:
public void parse(Set<BeanDefinitionHolder> configCandidates) { //稍后執行的parse方法中,所有DeferredImportSelector實現類都會被放入集合deferredImportSelectors中 this.deferredImportSelectors = new LinkedList<DeferredImportSelectorHolder>(); for (BeanDefinitionHolder holder : configCandidates) { BeanDefinition bd = holder.getBeanDefinition(); try { if (bd instanceof AnnotatedBeanDefinition) { //在這個parse方法中,所有DeferredImportSelector實現類都會被放入集合deferredImportSelectors中,它們的selectImports方法不會被執行,而其他ImportSelector實現類的selectImports都會被執行 parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName()); } else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) { parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName()); } else { parse(bd.getBeanClassName(), holder.getBeanName()); } } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Exception ex) { throw new BeanDefinitionStoreException( "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex); } } //此方法內,會將集合deferredImportSelectors中的所有對象取出來執行其selectImports方法 processDeferredImportSelectors(); }
7. 從上述代碼中可以看出,DeferredImportSelector實現類的selectImports方法會在最后被調用,其余的關鍵邏輯應該在parse(AnnotationMetadata metadata, String beanName)這個關鍵方法中,順着這個方法一直追蹤下去,直到doProcessConfigurationClass方法,如下圖紅框所示,所有Import注解的處理,都在processImports方法中:
8. processImports方法中包含了對ImportSelector實現類和ImportBeanDefinitionRegistrar實現類的處理,以及未實現這些接口的類的處理,我們逐個來分析吧,首先看ImportBeanDefinitionRegistrar實現類的處理,如下圖紅框位置,調用configClass.addImportBeanDefinitionRegistrar方法將ImportBeanDefinitionRegistrar實現類存入configClass的成員變量importBeanDefinitionRegistrars中,后面的ConfigurationClassPostProcessor類的processConfigBeanDefinitions方法中,this.reader.loadBeanDefinitions(configClasses);會調用這些ImportBeanDefinitionRegistrar實現類的registerBeanDefinitions方法:
8. processImports方法中包含了對ImportSelector實現類和ImportBeanDefinitionRegistrar實現類的處理,以及未實現這些接口的類的處理,我們逐個來分析吧,首先看ImportBeanDefinitionRegistrar實現類的處理,如下圖紅框位置,調用configClass.addImportBeanDefinitionRegistrar方法將ImportBeanDefinitionRegistrar實現類存入configClass的成員變量importBeanDefinitionRegistrars中,后面的ConfigurationClassPostProcessor類的processConfigBeanDefinitions方法中,this.reader.loadBeanDefinitions(configClasses);會調用這些ImportBeanDefinitionRegistrar實現類的registerBeanDefinitions方法:
9. 再來看processImports方法中對ImportSelector實現類的處理,這里略有些復雜,因為涉及到對processImports方法的迭代調用,請看下圖紅框旁邊的紅字說明:
如上圖所示,第二步就是在processImports方法中調用了processImports方法,再次進入processImports之后,會着ImportSelector實現類返回的bean名稱直接走到第三步的位置,第三步處理的就是沒有實現ImportSelector和ImportBeanDefinitionRegistrar這些接口的普通bean了;
10. processImports方法對沒有實現ImportSelector和ImportBeanDefinitionRegistrar這些接口的普通bean的處理是執行processConfigurationClass方法,將這些bean放入了成員變量configurationClasses中,如下圖紅框所示:
11. processImports方法分析完畢,Import注解導入的bean都被保存在ConfigurationClassParser實例中,我們回到ConfigurationClassPostProcessor類的processConfigBeanDefinitions方法,如下圖,this.reader.loadBeanDefinitions(configClasses);負責處理processImports方法找出的那些打算通過@Import注解來注冊到spring容器的bean:
12. 展開this.reader.loadBeanDefinitions(configClasses)方法,在ConfigurationClassBeanDefinitionReader類中,是對每個配置類逐個執行loadBeanDefinitionsForConfigurationClass方法:
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) { TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator(); for (ConfigurationClass configClass : configurationModel) { loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator); } }
13. 展開方法,真相大白,實現了ImportBeanDefinitionRegistrar接口的實例,會執行其registerBeanDefinitions方法,其余普通的類,通過loadBeanDefinitionsFromImportedResources方法將其bean定義注冊在spring環境:
private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) { if (trackedConditionEvaluator.shouldSkip(configClass)) { String beanName = configClass.getBeanName(); if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) { this.registry.removeBeanDefinition(beanName); } this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName()); return; } if (configClass.isImported()) { registerBeanDefinitionForImportedConfigurationClass(configClass); } for (BeanMethod beanMethod : configClass.getBeanMethods()) { loadBeanDefinitionsForBeanMethod(beanMethod); } //普通的類,通過loadBeanDefinitionsFromImportedResources方法將其bean定義注冊在spring環境 loadBeanDefinitionsFromImportedResources(configClass.getImportedResources()); //實現了ImportBeanDefinitionRegistrar接口的實例,會在loadBeanDefinitionsFromRegistrars方法中執行其registerBeanDefinitions方法 loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars()); }
14. 前面將普通類、ImportBeanDefinitionRegistrar實現類、ImportSelector實現類的分析已經完成,對於DeferredImportSelector實現類的處理在processDeferredImportSelectors方法中,其實和ImportSelector實現類的處理並無區別,只是處理時機比起ImportSelector實現類略晚,這里就不多說了;
至此,通過Import注解注冊bean的四種方式已經全部分析完畢,小結如下:
- 1. 普通類(即沒有實現ImportBeanDefinitionRegistrar、ImportSelector、DeferredImportSelector等接口的類)會通過ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsFromImportedResources方法將bean定義注冊到spring容器;
- 2. ImportSelector實現類,其selectImports方法返回的bean的名稱,通過ConfigurationClassParser類的asSourceClass方法轉成SourceClass對象,然后被當作普通類處理;
- 3. DeferredImportSelector實現類的處理和ImportSelector實現類的處理並無區別,只是處理時機比起ImportSelector實現類略晚;
- 4. ImportBeanDefinitionRegistrar實現類的registerBeanDefinitions方法會被調用,里面可以注冊業務所需的bean定義;
官方API文檔中的疑問解答
在官方API文檔中,對ImportSelector接口的描述如下圖所示,紅框中的一段意思是: ImportSelector接口的實現類,如果同時也實現了EnvironmentAware、BeanFactoryAware、BeanClassLoaderAware、ResourceLoaderAware這些接口中的一個或幾個,那么這些接口對應的方法優先執行,然后才會執行ImportSelector接口的selectImports:
上圖紅框中的描述會讓我們不禁疑惑:spring是如何做到的呢?一起來看源碼吧:
1. 再次打開ConfigurationClassParser類的processImports方法,如下圖兩個紅框所示,對於@Import注解值中的類,只要實現了ImportBeanDefinitionRegistrar、ImportSelector、DeferredImportSelector等接口中的任何一個,都會調用invokeAwareMethods方法(如果實現的是ImportSelector或DeferredImportSelector接口,此時還沒有執行selectImports方法):
2. 展開invokeAwareMethods方法,真相大白,這里面檢查是否實現了EnvironmentAware、BeanFactoryAware、BeanClassLoaderAware、ResourceLoaderAware等接口,如果實現了就調用對應的方法;
private void invokeAwareMethods(Object importStrategyBean) { if (importStrategyBean instanceof Aware) { if (importStrategyBean instanceof EnvironmentAware) { ((EnvironmentAware) importStrategyBean).setEnvironment(this.environment); } if (importStrategyBean instanceof ResourceLoaderAware) { ((ResourceLoaderAware) importStrategyBean).setResourceLoader(this.resourceLoader); } if (importStrategyBean instanceof BeanClassLoaderAware) { ClassLoader classLoader = (this.registry instanceof ConfigurableBeanFactory ? ((ConfigurableBeanFactory) this.registry).getBeanClassLoader() : this.resourceLoader.getClassLoader()); ((BeanClassLoaderAware) importStrategyBean).setBeanClassLoader(classLoader); } if (importStrategyBean instanceof BeanFactoryAware && this.registry instanceof BeanFactory) { ((BeanFactoryAware) importStrategyBean).setBeanFactory((BeanFactory) this.registry); } } }
至此,源碼分析工作已經結束,接下來實戰@Import注解的使用;
實戰通過Import注解動態創建bean實例
到了實戰驗證環節了,本次實戰的內容是創建一個springboot工程,通過@Import注解將bean注冊到spring容器。代碼詳見:https://gitee.com/gd1234/springboot-study。