Import
注解是 Spring
基于 Java
注解配置的重要组成部分,处理 Import
注解是处理 Configuration
注解的子过程之一,本文将介绍 Import
注解的 3
种使用方式,然后通过分析源码和处理过程示意图解释它是如何导入(注册) BeanDefinition
的。
使用方式 Import
注解有 3
种导入(注册) BeanDefinition
的方式:
使用 Import
将目标类的 Class
对象,解析为 BeanDefinition
并注册。
使用 Import
配合 ImportSelector
的实现类,将 selectImports
方法返回的所有全限定类名字符串,解析为 BeanDefinition
并注册。
使用 Import
配合 ImportBeanDefinitionRegistra
r 的实现类,在 registerBeanDefinitions
方法中,直接向 BeanDefinitionRegistry
中注册 BeanDefinition
。
测试用例 测试了使用 Import
注解的 3
种方式:
使用 Import
直接导入(注册) Red
。
配合 ImportBeanDefinitionRegistrar
间接注册 Color
。
配合 ImportSelector
间接导入(注册) Blue
。
用例中的特别地测试了以下两种情况:
使用 Import
直接导入和配合 ImportSelector
间接导入相同的类 Red
只会注册一个 BeanDefinition
。
尽管 MyImportSelector
书面顺序在 MyImportBeanDefinitionRegistrar
之后,但是 MyImportBeanDefinitionRegistrar
判断 registry
是否包含在 MyImportSelector
导入的类 Blue
时,不受顺序影响。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 @Configuration @Import({Red.class, MyImportBeanDefinitionRegistrar.class, MyImportSelector.class,}) public class ImportConfig {} public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions (AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { boolean hasRed = registry.containsBeanDefinition("com.moralok.bean.Red" ); boolean hasBlue = registry.containsBeanDefinition("com.moralok.bean.Blue" ); if (hasRed && hasBlue) { BeanDefinition beanDefinition = new RootBeanDefinition (Color.class); registry.registerBeanDefinition("color" , beanDefinition); } } } public class MyImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { return new String [] {"com.moralok.bean.Blue" , "com.moralok.bean.Red" }; } } public class Color {} public class Red {} public class Blue {} public class IocTest { @Test public void importTest () { ApplicationContext ac = new AnnotationConfigApplicationContext (ImportConfig.class); String[] beanDefinitionNames = ac.getBeanDefinitionNames(); for (String name : beanDefinitionNames) { System.out.println("beanDefinitionName.........." + name); } } }
测试结果
1 2 3 4 5 ...... beanDefinitionName..........importConfig beanDefinitionName..........com.moralok.bean.Red beanDefinitionName..........com.moralok.bean.Blue beanDefinitionName..........color
源码分析 关于 Import
注解的源码分析需要建立在对关于 Configuration
注解的源码的了解基础上,因为前者是 Spring
解析配置类处理过程的一部分,可以参考文章:
获取要导入的目标 在 doProcessConfigurationClass
方法中处理配置类构建配置模型时,会调用 processImports
方法处理 Import
注解。在进入方法前,会调用 getImports
方法从 sourceClass
获取要导入的目标。
注意:目标不仅仅来自直接标注在 sourceClass
上的 Import
注解,因为 sourceClass
上可能还有其他的注解,这些注解自身可能标注了 Import
注解,因此需要递归地遍历所有注解,找到所有的 Import
注解。
1 2 3 4 5 6 protected final SourceClass doProcessConfigurationClass (ConfigurationClass configClass, SourceClass sourceClass) throws IOException { processImports(configClass, sourceClass, getImports(sourceClass), true ); }
collectImports
方法是一种常见的递归写法(深度优先遍历)。imports
存放要导入的目标,visited
存放已经访问过的 sourceClass
。sourceClass
在入口处包装了一个普通的 Class
,在递归的过程中包装的都是一个注解 Class
。
注意:这里还没有检测循环导入的情况并抛出异常,但 visited
保证了只会遍历一次。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 private Set<SourceClass> getImports (SourceClass sourceClass) throws IOException { Set<SourceClass> imports = new LinkedHashSet <SourceClass>(); Set<SourceClass> visited = new LinkedHashSet <SourceClass>(); collectImports(sourceClass, imports, visited); return imports; } private void collectImports (SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited) throws IOException { if (visited.add(sourceClass)) { for (SourceClass annotation : sourceClass.getAnnotations()) { String annName = annotation.getMetadata().getClassName(); if (!annName.startsWith("java" ) && !annName.equals(Import.class.getName())) { collectImports(annotation, imports, visited); } } imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value" )); } }
这时候,并不区分要导入的目标的 Class
有什么特别之处,Import
注解的语义,此时宽泛地说就是:“将 value
中的类导入”。但是显而易见,这样的方式不够灵活,因此才有了另外两种更有灵活性的导入方式:ImportSelector
和 ImportBeanDefinitionRegistrar
,Spring
最终不会真的注册这两种类,而是注册它们“介绍”的类,相当于把确定导入什么类的工作委托给它们。
处理要导入的目标 processImports
方法是处理 Import
注解的核心方法,这里的处理逻辑就对应着 Import
注解的三种使用方式。主要步骤如下:
检测要导入的候选者不为空
判断是否要检测循环导入以及是否存在循环导入
处理要导入的候选者
如果是 ImportSelector
类型,调用 selectImports
方法获取新的要导入的目标,递归调用 processImports
处理
如果是 ImportBeanDefinitionRegistrar
类型,添加到配置模型 configClass
(出口 1
)
如果是其他剩余情况,作为配置类处理(出口 2
)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 private void processImports (ConfigurationClass configClass, SourceClass currentSourceClass, Collection<SourceClass> importCandidates, boolean checkForCircularImports) throws IOException { 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)) { 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)) { 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 { 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 如果要导入的目标是 ImportSelector
类型,那么 Spring
将确定真正导入什么目标的工作委托给它,不导入目标本身,实际上只导入目标“介绍”的类。具体步骤是:
先获取 Class
对象
再实例化得到一个 ImportSelector
实例
调用 selectImports
方法,该方法返回的是类的全限定名,这样就得到了真正要导入的目标
再次递归调用 processImports
ImportSelector
就像它名字的含义一样,本质上是一种导入选择器,是一种更加灵活的 getImports
方法。由于返回的目标可能属于三种情形中的任意一种,所以对这些目标的处理还是要回到 processImports
方法。可以说 ImportSelector
类型本身不是 processImports
方法的出口,它最终会转换为 ImportBeanDefinitionRegistrar
或其他剩余情况。
ImportSelector
灵活性的来源:
selectImports
的 AnnotationMetadata
参数,为它提供了根据注解信息返回要导入的目标的能力
ImportSelector
可以实现 Aware
接口,用以感知到一些容器级别的资源,如 BeanFactory
,这为它提供了根据这些资源中的信息返回要导入的目标的能力
类型二:ImportBeanDefinitionRegistrar 如果要导入的目标是 ImportBeanDefinitionRegistrar
,它会和 ImportSelector
有些相似却又有所不同。Spring
同样将确定真正导入什么目标的工作委托给它,不导入目标本身,实际上只导入目标“介绍”的类。
先获取 Class
对象
再实例化得到一个 ImportBeanDefinitionRegistrar
实例
添加到配置模型 configClass
的 importBeanDefinitionRegistrars
属性
ImportBeanDefinitionRegistrar
不像 ImportSelector
需要进一步处理,它本身就代表着一个返回出口,成为了配置模型的一部分。但是请注意,registerBeanDefinitions
方法此时并没有被调用。
ImportBeanDefinitionRegistrar
灵活性的来源:
registerBeanDefinitions
的 AnnotationMetadata
参数,为它提供了根据注解信息决定注册 BeanDefinition
的能力
registerBeanDefinitions
的 BeanDefinitionRegistry
参数,为它提供了根据 BeanDefinitionRegistry
中的信息决定注册 BeanDefinition
的能力
ImportBeanDefinitionRegistrar
可以实现 Aware
接口,用以感知到一些容器级别的资源,如 BeanFactory
,这为它提供了根据这些资源中的信息返回要导入的目标的能力
类型三:其他剩余情况 如果要导入的目标属于既不是 ImportSelector
也不是 ImportBeanDefinitionRegistrar
的其他剩余情况,那么 Spring
将其视为被 Configuration
注解标注的配置类进行处理。这里的处理逻辑是,Import
注解导入的类可能不是一个普通的类,而是一个配置类,因此需要回到 processConfigurationClass
进行处理。processConfigurationClass
方法正是本文开头的 doProcessConfigurationClass
方法的调用方,这里有两个地方值得注意:
Import
注解产生的 ConfigurationClass
根据不同的情况需要合并或者被抛弃,显式声明比 Import 导入的优先级更高。
其他剩余情况下,目标最终会转换为一个配置模型,添加到 parser
的 configurationClasses
属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 protected void processConfigurationClass (ConfigurationClass configClass) throws IOException { if (this .conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) { return ; } ConfigurationClass existingClass = this .configurationClasses.get(configClass); if (existingClass != null ) { if (configClass.isImported()) { if (existingClass.isImported()) { existingClass.mergeImportedBy(configClass); } return ; } else { this .configurationClasses.remove(configClass); for (Iterator<ConfigurationClass> it = this .knownSuperclasses.values().iterator(); it.hasNext();) { if (configClass.equals(it.next())) { it.remove(); } } } } SourceClass sourceClass = asSourceClass(configClass); do { sourceClass = doProcessConfigurationClass(configClass, sourceClass); } while (sourceClass != null ); this .configurationClasses.put(configClass, configClass); }
DeferredImportSelector 的调用时机 在解析完每一批(注释中说“全部”)的配置类后,会统一调用 DeferredImportSelector
。它作为一个标记接口推迟了 selectImports
的时机,打破了处理顺序的限制,在方法被调用时,可以得到更加完整的信息。注释中说“在选择导入的目标是 @Conditional
时,这个类型的选择器会很有用”,但是我不太理解,因为这个时候,处理配置类得到的信息尚未转换为 ImportSelector
可以感知到的信息,不像 ImportBeanDefinitionRegistrar
,它被调用的时机在最后,也因此可以感知到更多的信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 public void parse (Set<BeanDefinitionHolder> configCandidates) { this .deferredImportSelectors = new LinkedList <DeferredImportSelectorHolder>(); for (BeanDefinitionHolder holder : configCandidates) { BeanDefinition bd = holder.getBeanDefinition(); try { if (bd instanceof AnnotatedBeanDefinition) { 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 (Throwable ex) { throw new BeanDefinitionStoreException ( "Failed to parse configuration class [" + bd.getBeanClassName() + "]" , ex); } } processDeferredImportSelectors(); } private void processDeferredImportSelectors () { List<DeferredImportSelectorHolder> deferredImports = this .deferredImportSelectors; this .deferredImportSelectors = null ; Collections.sort(deferredImports, DEFERRED_IMPORT_COMPARATOR); for (DeferredImportSelectorHolder deferredImport : deferredImports) { ConfigurationClass configClass = deferredImport.getConfigurationClass(); try { String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata()); processImports(configClass, asSourceClass(configClass), asSourceClasses(imports), false ); } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException ( "Failed to process import candidates for configuration class [" + configClass.getMetadata().getClassName() + "]" , ex); } } }
ImportBeanDefinitionRegistrar 的调用时机 ConfigurationClassPostProcessor
在每次解析得到新的一批配置模型后,都会调用 ConfigurationClassBeanDefinitionReader
的 loadBeanDefinitions
方法加载 BeanDefinition
,在这过程的最后会从 ImportBeanDefinitionRegistrar
加载 BeanDefinition
。这代表在处理同一批配置类时,在 registerBeanDefinitions
方法中总是能感知到以其他方式注册到 BeanDefinitionRegistry
中的 BeanDefinition
,不论书面定义的顺序如何。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 public void loadBeanDefinitions (Set<ConfigurationClass> configurationModel) { TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator (); for (ConfigurationClass configClass : configurationModel) { loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator); } } 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(configClass.getImportedResources()); loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars()); } private void loadBeanDefinitionsFromRegistrars (Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) { for (Map.Entry<ImportBeanDefinitionRegistrar, AnnotationMetadata> entry : registrars.entrySet()) { entry.getKey().registerBeanDefinitions(entry.getValue(), this .registry); } }
循环导入的检测 在处理导入的目标前将配置类放入 importStack
,处理完毕移除。如果要导入的目标属于其他剩余情况时,注册被导入类->所有导入类集合的映射关系。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 private void processImports (ConfigurationClass configClass, SourceClass currentSourceClass, Collection<SourceClass> importCandidates, boolean checkForCircularImports) throws IOException { if (checkForCircularImports && isChainedImportOnStack(configClass)) { this .problemReporter.error(new CircularImportProblem (configClass, this .importStack)); } else { this .importStack.push(configClass); try { for (SourceClass candidate : importCandidates) { else { this .importStack.registerImport( currentSourceClass.getMetadata(), candidate.getMetadata().getClassName()); processConfigurationClass(candidate.asConfigClass(configClass)); } } } finally { this .importStack.pop(); } } }
检测是否发生循环导入。以当前类开始,循环向上查找最近一个导入自身的类,如果找到自身,说明发生循环导入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private boolean isChainedImportOnStack (ConfigurationClass configClass) { if (this .importStack.contains(configClass)) { String configClassName = configClass.getMetadata().getClassName(); AnnotationMetadata importingClass = this .importStack.getImportingClassFor(configClassName); while (importingClass != null ) { if (configClassName.equals(importingClass.getClassName())) { return true ; } importingClass = this .importStack.getImportingClassFor(importingClass.getClassName()); } } return false ; }
总结 对比
ImportSelector
ImportBeanDefinitionRegistrar
其他剩余情况
灵活性
中
高
低
处理结果
转换为配置模型的一部分
转换为一个配置模型
方法调用时机
立即(或解析配置类的最后)
加载 BeanDefinition
的最后
方法的结果
获取 Import
目标
直接注册 BeanDefinition
处理过程示意图