@Import 注解使用及原理解析
1:@Import的使用场景:通过 @Import 注解引入普通的 Java 类数组@Import({TestA.class}):这样就会把 TestA 注入进 IOC 容器,生成一个名字为 “com.demo.testA” 的 bean,同时也可以看到可以传入多个类,这样就可以在IOC容器里生成多个 bean。通过 @Import 注解引入实现了 ImportSelector 接口的类实现了Im
1:@Import的使用场景:
- 通过 @Import 注解引入普通的 Java 类数组
@Import({TestA.class}):这样就会把 TestA 注入进 IOC 容器,生成一个名字为 “com.demo.testA” 的 bean,同时也可以看到可以传入多个类,这样就可以在IOC容器里生成多个 bean。
- 通过 @Import 注解引入实现了 ImportSelector 接口的类
实现了ImportSelector接口,就必须重写 selectImports 方法,如下所示,就会把 TestB 注入IOC 容器,让 Spring 来管理名称为“com.demo.TestB” 的 bean。同理,该方法返回的是一个数组,如果返回多个,就会在 IOC 容器里生成多个 bean。
public class MySelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[]{"com.demo.TestB"};
}
}
- 通过 @Import 引入实现了 ImportBeanDefinitionRegistrar 接口的类
跟实现 ImportSelector 不同,实现 ImportBeanDefinitionRegistrar 不是必须重写ImportBeanDefinitionRegistrar 的方法,但是我们为了注入 bean定义, 还是需要重写 registerBeanDefinitions 方法,如下所示,此时就会注入一个名字为 testC 的 bean。同理也可以在该方法里使用多个 RootBeanDefinition 结合 registry 来注入多个 bean定义。
public class MyRegistrar implements ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//构造 BeanDefinition
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(TestC.class);
//注册 bean, 并给其取个名字
registry.registerBeanDefinition("testC",rootBeanDefinition);
}
}
2:代码演示:
- 首先定义三个类,用来生成一些 Bean
public class TestA { private void print() { System.out.println("TestAAAAAA"); } } public class TestB { private void print() { System.out.println("TestBBBBBB"); } } public class TestC { private void print() { System.out.println("TestCCCC"); } }
- 定义实现 ImportSelector 的类,用来注入 TestB 的实例
public class MySelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { return new String[]{"com.demo.TestB"}; } }
- 定义实现 ImportBeanDefinitionRegistrar 接口的类,用来注入 TestC 的实例
public class MyRegistrar implements ImportBeanDefinitionRegistrar { public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { //构造 BeanDefinition RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(TestC.class); //注册 bean, 并给其取个名字 registry.registerBeanDefinition("testC",rootBeanDefinition); } }
- 定义配置类,通过 @Import 注解注入 bean 实例
@Import({TestA.class, MySelector.class, MyRegistrar.class}) public class ImportDemo { }
- 编写测试类
public class ImportMain { public static void main(String[] args) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ImportDemo.class); String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames(); //打印出 IOC 容器里所有的 bean for (String name : beanDefinitionNames) { System.out.println(name); } } }
- 测试结果:第 1 部分是 spring 内置的 bean,importDemo 为配置类 bean,第 2 部分的三个bean即为通过 @Import 注解注入的三个 Bean。由此可以看出,通过@Import 直接引入类名和实现ImportSelector的类的bean的名称为全类名,而通过实现ImportBeanDefinitionRegistrar 的类注入的 bean 的名称为自己取的名字。
3:原理解析
- 如果不了解 ConfigurationClassPostProcessor 的同学,可以先去看我写的另一篇文章去了解一下Bean工厂后置处理器之 ConfigurationClassPostProcessor- Spring 到底怎么扫描到它所需要管理的bean的?,这篇文章比较详细的跟踪了 Spring 是如何扫描标注了 @Component 注解的 bean。
- 我们在上一篇文章中跟踪到了 doProcessConfigurationClass 方法,并进入了处理 @ComponentScans 的逻辑,但是同时我们也意识到了,还有单独处理 @Import 注解的逻辑,接下来我们就跟进一下,看看 Spring 到底是怎么处理的。
protected final SourceClass doProcessConfigurationClass( ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter) throws IOException { if (configClass.getMetadata().isAnnotated(Component.class.getName())) { // Recursively process any member (nested) classes first processMemberClasses(configClass, sourceClass, filter); } // Process any @PropertySource annotations for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), PropertySources.class, org.springframework.context.annotation.PropertySource.class)) { if (this.environment instanceof ConfigurableEnvironment) { processPropertySource(propertySource); } else { logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() + "]. Reason: Environment must implement ConfigurableEnvironment"); } } // Process any @ComponentScan annotations Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class); if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) { for (AnnotationAttributes componentScan : componentScans) { // The config class is annotated with @ComponentScan -> perform the scan immediately Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); // Check the set of scanned definitions for any further config classes and parse recursively if needed for (BeanDefinitionHolder holder : scannedBeanDefinitions) { BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition(); if (bdCand == null) { bdCand = holder.getBeanDefinition(); } if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) { parse(bdCand.getBeanClassName(), holder.getBeanName()); } } } } // Process any @Import annotations 接下来我们跟踪一下这个方法,看一下到底是怎么处理 @Import 注解的 processImports(configClass, sourceClass, getImports(sourceClass), filter, true); // Process any @ImportResource annotations AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class); if (importResource != null) { String[] resources = importResource.getStringArray("locations"); Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader"); for (String resource : resources) { String resolvedResource = this.environment.resolveRequiredPlaceholders(resource); configClass.addImportedResource(resolvedResource, readerClass); } } // Process individual @Bean methods Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass); for (MethodMetadata methodMetadata : beanMethods) { configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); } // Process default methods on interfaces processInterfaces(configClass, sourceClass); // Process superclass, if any if (sourceClass.getMetadata().hasSuperClass()) { String superclass = sourceClass.getMetadata().getSuperClassName(); if (superclass != null && !superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) { this.knownSuperclasses.put(superclass, configClass); // Superclass found, return its annotation metadata and recurse return sourceClass.getSuperClass(); } } // No superclass -> processing is complete return null; }
进入处理 @Import 的注解的方法如下
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter, 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 = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class, this.environment, this.resourceLoader, this.registry); Predicate<String> selectorFilter = selector.getExclusionFilter(); if (selectorFilter != null) { exclusionFilter = exclusionFilter.or(selectorFilter); } if (selector instanceof DeferredImportSelector) { this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector); } else { String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata()); Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter); processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, 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 = ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class, 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), exclusionFilter); } } } 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(); } } }
可以看到,上面有三个 if else 判断,分别处理上面我们列举出来的三种场景,1:通过@Import 引入的实现了 ImportSelector 接口的类 2: 通过@Import引入的实现了 ImportBeanDefinitionRegistrar 的类 3:通过@Import引入的处理引入的普通的 java 类
-
处理实现了 ImportSelector 的逻辑
if (candidate.isAssignable(ImportSelector.class)) { // Candidate class is an ImportSelector -> delegate to it to determine imports Class<?> candidateClass = candidate.loadClass(); ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class, this.environment, this.resourceLoader, this.registry); Predicate<String> selectorFilter = selector.getExclusionFilter(); if (selectorFilter != null) { exclusionFilter = exclusionFilter.or(selectorFilter); } //实现了 DeferredImportSelector 的会暂时放到一个 list 中,最后再处理 if (selector instanceof DeferredImportSelector) { this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector); } else { //这里可以看到调用了selectImports方法,就是我们实现Selector接口重写的方法,我们返回了class数组, 在这里就取到了我们的返回值 String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata()); Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter); //递归调用,因为导入的类可能又间接导入了其他类 processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false); } }
通过上面我们可以看到,这里调用了我们自己重写的方法,然后又将得到的class数组,继续递归调用,尝试解析其他 Import 的bean,但是我们目前却没有看到有关注册bean定义的逻辑,这里先不着急,我们接着看第三个 if else 逻辑,即没有实现ImportSelect 也没有实现 ImportBeanDefinitionRegistrar 的逻辑,因为随着我们的递归调用,最后一定会走到这一步
// 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), exclusionFilter);
可以看到,这里也是什么都没做,又重新递归调用解析配置类的逻辑,我们再回到解析配置类的逻辑看一下
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) 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); } // Otherwise ignore new imported config class; existing non-imported class overrides it. return; } else { // Explicit bean definition found, probably replacing an import. // Let's remove the old one and go with the new one. this.configurationClasses.remove(configClass); this.knownSuperclasses.values().removeIf(configClass::equals); } } // Recursively process the configuration class and its superclass hierarchy. SourceClass sourceClass = asSourceClass(configClass, filter); do { //又回到刚刚解析配置类的方法,如解析 @ComponentScan, @Import 等 sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter); } while (sourceClass != null); //最终会走到这里,把该 bean 放到一个 map 中保存 this.configurationClasses.put(configClass, configClass); }
-
- 我们在上一篇文章中跟踪到了 doProcessConfigurationClass 方法,并进入了处理 @ComponentScans 的逻辑,但是同时我们也意识到了,还有单独处理 @Import 注解的逻辑,接下来我们就跟进一下,看看 Spring 到底是怎么处理的。
可以看到,这里又要回到解析配置类的逻辑了,而假设我们Import的就是一个普通的类,就想要这个类的 bean, 它根本没有@Import, @ComponentScan之类的东西,可以预料到,再次进入解析配置类的逻辑也还是什么都没做的,即还是获取不到我们想要的 bean 定义,不过这是,我们留意到,最后一行会有一个 map 把这个类给存起来。那是不是有可能再解析完成之后的某个地方会调用这个map,来生成对应的bean定义呢?验证这一点的话,就得先回到解析配置类入口的地方。
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
parser.parse(candidates);
//parse方法执行结束,代表解析结束
parser.validate();
//可以看到,这里就获取了我们刚刚保存起来的bean
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
// Read the model and create bean definitions based on its content
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
//执行这个加载bean定义的方法,看起来就像是我们想寻找的
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);
可以看到,解析完成之后,通过调用 getConfigurationClasses 方法获取到一些类
public Set<ConfigurationClass> getConfigurationClasses() {
//可以看到,这就是我们刚刚保存时用到的 map, 这里将所有的key返回
return this.configurationClasses.keySet();
}
拿到类信息之后,首先去除已经被解析过的,然后就调用 this.reader.loadBeanDefinitions(configClasses);
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;
}
//解析通过Import导入的类
if (configClass.isImported()) {
registerBeanDefinitionForImportedConfigurationClass(configClass);
}
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
//解析通过 Registra 引入的类
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
这里我们不仅看到了处理@Import导入的普通类的逻辑,还看到了最后一行解析 Registra 引入的类,我们先看一下@Import导入的普通类的逻辑
private void registerBeanDefinitionForImportedConfigurationClass(ConfigurationClass configClass) {
AnnotationMetadata metadata = configClass.getMetadata();
AnnotatedGenericBeanDefinition configBeanDef = new AnnotatedGenericBeanDefinition(metadata);
ScopeMetadata scopeMetadata = scopeMetadataResolver.resolveScopeMetadata(configBeanDef);
configBeanDef.setScope(scopeMetadata.getScopeName());
String configBeanName = this.importBeanNameGenerator.generateBeanName(configBeanDef, this.registry);
AnnotationConfigUtils.processCommonDefinitionAnnotations(configBeanDef, metadata);
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(configBeanDef, configBeanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
this.registry.registerBeanDefinition(definitionHolder.getBeanName(), definitionHolder.getBeanDefinition());
configClass.setBeanName(configBeanName);
if (logger.isTraceEnabled()) {
logger.trace("Registered bean definition for imported class '" + configBeanName + "'");
}
}
这里就简单了,这就是我们想要寻找的,首先实例化bean定义,然后注册bean定义。
这里总结一下,目前通过导入Selector引入的类和@Import导入的普通类的逻辑已经清楚了,即selector会调用我们重写的方法,然后获取到我们重写方法返回的class数组,然后递归调用解析,最终会进入处理@Import普通类的逻辑,然后解析过程中没有进行 beanDefinition 的注册,而是在一个map中暂存起来。解析配置类完成之后,才把刚刚暂存起来的类信息再拿出来,进行bean定义的实例化和注册。
刚刚我们还留意到了,处理Registra的方法,这个很像我们@Import导入的另外一种方式,而正是唯一一个我们还没有理清逻辑的地方,我们点进去跟进一下
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
这个方法跟刚刚解析类数组类似,只不过改成了批量处理 registra
private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
registrars.forEach((registrar, metadata) ->
registrar.registerBeanDefinitions(metadata, this.registry, this.importBeanNameGenerator));
}
我们看到这里就会很眼熟,因为,我们在实现 ImportBeanDefinitionRegistrar 接口的时候,会重写这个方法,并在其进行了bean定义的注册,由此,我们知道了,我们重写的这个方法到底什么时候被调用了。此时的问题就是,这么多registra是从哪里来的,难道是跟刚刚的逻辑一样,也是解析配置类的时候,并没有真正注册bean定义,而是在某个地方暂存起来吗?看起来很可能是这样的。而且这里便利 registrars 的时候是直接遍历 map,所以很可能也是解析配置类的时候把这些 registrars 暂存到 map 里了。下面我们验证一下。
public class MyRegistrar implements ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//构造 BeanDefinition
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(TestC.class);
//注册 bean, 并给其取个名字
registry.registerBeanDefinition("testC",rootBeanDefinition);
}
}
我们再次回到解析 @Import 的逻辑
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// Candidate class is an ImportBeanDefinitionRegistrar ->
// delegate to it to register additional bean definitions
Class<?> candidateClass = candidate.loadClass();
//这里实例化了一个registrar
ImportBeanDefinitionRegistrar registrar =
ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
this.environment, this.resourceLoader, this.registry);
//难道这个方法就是把刚刚实例化的registrar给暂存起来?不是吧不是吧
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
public void addImportBeanDefinitionRegistrar(ImportBeanDefinitionRegistrar registrar, AnnotationMetadata importingClassMetadata) {
//果然,这里跟刚刚处理普通java类的逻辑一样,也是在一个map里暂存起来了
this.importBeanDefinitionRegistrars.put(registrar, importingClassMetadata);
}
而这个 map 就是刚刚我们 for 循环遍历的map
至此为止,三种 @Import 引入 Java Bean 的逻辑全部都理清了,当然这里只是扫描到了所有的 beanDefinitions,后面才会循环所有的 beanDefinitions 再调用 getBean,来创建所有的bean。这里暂时就先不展开了。
更多推荐
所有评论(0)