Import
注解用来导入Bean对象到Spring容器中,最常用就是导入被 @Configuration
标识的配置类。@Import
注解允许导入 @Configuration
类、ImportSelector
、ImportBeanDefinitionRegister
的实现类。
使用
导入普通类
定义 Car
类,然后通过 @Import
注解把 Car
导入Spring容器中,无须手动注入它。
public class Car {
}
定义配置类,使用 @Import
注解标识。注意:即时配置类没有被 @Configuration
标识,被 @Import
注解标识的类也会被Spring当成配置类处理。
@Import(Car.class)
public class AccountConfig3 {
}
测试
public class AnnotationConfigTest {
public static void main(String[] args) throws IOException {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AccountConfig3.class);
Car bean = context.getBean(Car.class);
System.out.println(bean);
}
}
ImportSelector的方式导入
public class TestImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.test.annotationConfig.entity.TestBean1"};
}
}
ImportBeanDefinitionRegistrar的方式导入
public class TestImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
ImportBeanDefinitionRegistrar.super.registerBeanDefinitions(importingClassMetadata, registry, importBeanNameGenerator);
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(TestBean2.class);
registry.registerBeanDefinition("testBean2", rootBeanDefinition);
}
}
源码分析
Spring是如何处理 @Import
注解的?这些Bean对象又是如何导入Spring容器中的呢?
ConfigurationClassParser
类是用来解析配置类,这里通过简化一些方法,目的理解Spring是如何处理配置类以及 @Import
的处理。该类的方法processConfigBeanDefinitions
就是处理配置类的入口。
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
// 获取已经注册BeanDef
String[] candidateNames = registry.getBeanDefinitionNames();
for (String beanName : candidateNames) {
// 如果BeanDef是带有@Configuration注解
//或者是@Configuration候选注解(Component、ComponentScan、Import、ImportResource)也看作是配置类
if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
//加入配置类集
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}
// 构建 ConfigurationClassParser对象类解析配置类,包括:@ComponentScan、
// @Bean、@Import、@PropertySource注解处理,包括递归处理配置类
ConfigurationClassParser parser = new ConfigurationClassParser(......);
do {
parser.parse(candidates);
// 获取已经解析完的配置类
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
// 构建 ConfigurationClassBeanDefinitionReader 对象用来加载配置类配置的Bean对象
this.reader.loadBeanDefinitions(configClasses);
} while (!candidates.isEmpty());
}
ConfigurationClassParser
ConfigurationClassParser
负责解析@ComponentScan、@Bean、@Import、@PropertySource注解处理,包括递归处理配置类。这里简化一些方法,只看@Import注解的处理。由方法 processConfigurationClass
来处理。
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
// ....
// 把配置类当做类来处理
SourceClass sourceClass = asSourceClass(configClass, filter);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
}
while (sourceClass != null);
// 处理完成的配置类放入 Map对象中
this.configurationClasses.put(configClass, configClass);
}
doProcessConfigurationClass
是真正负责解析@ComponentScan、@Bean、@Import、@PropertySource等注解。
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
throws IOException {
// ...
// 处理@Import注解
// getImports方法用来获取 @Import注解的值
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
}
这里开始处理 @Import
,主要分三种情况:
ImportSelector
子类:如果导入的类实现了ImportSelector
接口,获取需要导入的类名,然后递归处理。ImportBeanDefinitionRegistrar
子类:实例化该类,并把它放入配置类的Map中- 普通类:当作配置类来处理,递归处理配置类。
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
boolean checkForCircularImports) {
// 遍历从@Import获取值,即导入的Bean
for (SourceClass candidate : importCandidates) {
if (candidate.isAssignable(ImportSelector.class)) {
// 伪代码,实例化 candidate 类
ImportSelector selector = instantiateClass();
// 获取importClassName
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
// 然后递归处理
processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
}
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// 实例化candidate类
ImportBeanDefinitionRegistrar registrar = instantiateClass();
// 把实例化的candidate类放入到配置类的Map中。
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {
// 如果导入的Bean不属于前两个接口的实现类的话,把这个Bean当做配置类来处理.
// asConfigClass() 会创建新的配置,并注明新的配置类candidate是由configClass导入的
// importedBy --> configClass
processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
}
}
}
ConfigurationClassBeanDefinitionReader
当配置解析完成后,需要由 ConfigurationClassBeanDefinitionReader
类来配置类配置的一些BeanDefinition。
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
// 遍历配置类,从配置类中加载BeanDefinition
for (ConfigurationClass configClass : configurationModel) {
loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
}
}
private void loadBeanDefinitionsForConfigurationClass(
ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
// 省略其他代码....
// 这个配置类是由其他配置通过 @Import注解导入的
if (configClass.isImported()) {
// 这就是为什么 @Import注解可以导入普通类的原因。例如 @Import(Test.class)
registerBeanDefinitionForImportedConfigurationClass(configClass);
}
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
// 加载由@Bean 注解的BeanDefiniton
loadBeanDefinitionsForBeanMethod(beanMethod);
}
// 加载类从 xml或者其他文件
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
// 加载BeanDefintion 从 ImportBeanDefinitionRegistrar 接口
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
参考
- Spring @Import Annotation