在 Spring 中,使用 <import>
标签或者 @Import
注解能够向容器中导入配置类(使用 @Configuration
注解的类),利用这个功能可以很方便地实现模块化配置的效果。从 Spring 官方文档给出的说明了解到,在 Spring 4.2 版本以前,@Import
注解只支持导入配置类,在 Spring 4.2 及以后的版本,该注解支持导入普通的 Java 类。
1 2 3 4 5 6 @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Import { Class<?>[] value(); }
可以看出,@Import
注解可以放在类、接口、注解和枚举上,我们简单写点代码来试验一下。
1 2 3 4 5 6 7 @Configuration public class ConfigA { @Bean public A getA () { return new A (); } }
1 2 3 4 5 6 public class ConfigB { @Bean public B getB () { return new B (); } }
1 2 3 @Import({ConfigA.class, ConfigB.class}) public class ConfigC {}
1 2 3 4 5 6 7 public class Main { public static void main (String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext (ConfigC.class); A a = context.getBean(A.class); B b = context.getBean(B.class); } }
除了导入配置类,@Import
注解还有一些更高级的用法,那就是导入实现了 ImportBeanDefinitionRegistrar 接口或者 ImportSelector 接口的类。在这之前,我们需要知道 Spring 容器最终是通过 ConfigurationClassParser 工具类来解析该注解的。
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 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)) { Class<?> candidateClass = candidate.loadClass(); ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class); ParserStrategyUtils.invokeAwareMethods( selector, this .environment, this .resourceLoader, this .registry); if (selector instanceof DeferredImportSelector) { this .deferredImportSelectorHandler.handle(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,那么在这里会调用它的 selectImports 方法获取更多需要导入到容器的类,典型的比如 Spring Boot 的 @EnableAutoConfiguration
自动装配注解。
如果导入的是一个 ImportBeanDefinitionRegistrar,那么会在这里将它添加到配置类的 importBeanDefinitionRegistrars 属性中。Spring 容器在初始化时,会通过 ConfigurationClassBeanDefinitionReader 的 loadBeanDefinitions 方法获取所有的 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 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()); }
很多第三方框架在集成 Spring 的时候,都会通过实现 ImportBeanDefinitionRegistrar 接口将自定义的 BeanDefinition 注册到 Spring 容器中,比如 MyBatis 的 @MapperScan
注解。