@Import注解源码解析

news/2024/12/5 1:37:48/

文章目录

  • 一、简介
  • 二、@Import注解的几种用法
  • 三、@Import注解源码解析
    • 1、ConfigurationClassPostProcessor
    • 2、ConfigurationClassPostProcessor 类的 postProcessBeanDefinitionRegistry 方法
    • 3、ConfigurationClassParser 类的 parse 方法
    • 4、处理 ImportBeanDefinitionRegistrar 接口实现类
    • 5、处理 ImportSelector 接口实现类
    • 6、处理 DeferredImportSelector 接口实现类

一、简介

本文将对 Spring 如何处理 @Import 注解的源码进行解析

二、@Import注解的几种用法

在@Import注解的参数里可以填写一个类的数组,而源码上已经告诉我们常用的几种导入类型

image-20230703111310107

  • 导入的类是一个标注了 @Configuration 的配置类:Spring容器实例化这个配置类
  • 导入的类是 ImportSelector 接口的实现类:如果导入的类是 ImportSelector 接口的实现类,实例化这个类之后,会执行其 selectImports 方法,如果实现的是 ImportSelector 接口的子类 DeferredImportSelector,会执行其 selectImports 方法,但是时机会延后,这个后面具体讲
  • 导入的类是 ImportBeanDefinitionRegistrar 接口的实现类:如果导入的类是 ImportBeanDefinitionRegistrar 接口的实现类,实例化这个类之后,会执行其 registerBeanDefinitions 方法

三、@Import注解源码解析

1、ConfigurationClassPostProcessor

下面通过源码来看 Spring 是怎么处理 @Import 注解的,关于@Import注解的处理过程,在 ConfigurationClassPostProcessor 这个类里,这个类是用来处理 @Configuration 注解相关逻辑的,它实现了 BeanDefinitionRegistryPostProcessor 接口,而 BeanDefinitionRegistryPostProcessor 接口又是 BeanFactoryPostProcessor 的子接口,熟悉 Spring 源码的同学会比较熟悉这两个类,这是Spring的后置处理器

  • BeanFactoryPostProcessor:用于修改Bean定义(BeanDefinition
  • BeanDefinitionRegistryPostProcessor:用来注册 BeanDefinition 到Spring容器里

具体这里就不展开了,想了解的可以去看看 Spring 的核心方法 refresh 方法里的 invokeBeanFactoryPostProcessors(beanFactory);,这里只需要知道,在 Spring 容器启动过程中,会调用 ConfigurationClassPostProcessor 类的 postProcessBeanDefinitionRegistry 方法,下面我们从这个方法的入口开始看

2、ConfigurationClassPostProcessor 类的 postProcessBeanDefinitionRegistry 方法

image-20230703135037237

前面那一段,是一个防重复处理的逻辑,不用关心,重点是 processConfigBeanDefinitions 方法,跟进去看下

//ConfigurationClassPostProcessor
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {//1、找到所有的配置类List<BeanDefinitionHolder> configCandidates = new ArrayList<>();String[] candidateNames = registry.getBeanDefinitionNames();for (String beanName : candidateNames) {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);}}else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));}}// Return immediately if no @Configuration classes were found//2、如果没找到配置类就直接返回if (configCandidates.isEmpty()) {return;}// Sort by previously determined @Order value, if applicable//3、为找到的配置类排序configCandidates.sort((bd1, bd2) -> {int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());return Integer.compare(i1, i2);});// Detect any custom bean name generation strategy supplied through the enclosing application context//4、如果registry是SingletonBeanRegistry类型,且包含这个CONFIGURATION_BEAN_NAME_GENERATOR实例,就设置两个属性SingletonBeanRegistry sbr = null;if (registry instanceof SingletonBeanRegistry) {sbr = (SingletonBeanRegistry) registry;if (!this.localBeanNameGeneratorSet) {BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);if (generator != null) {this.componentScanBeanNameGenerator = generator;this.importBeanNameGenerator = generator;}}}if (this.environment == null) {this.environment = new StandardEnvironment();}// Parse each @Configuration class//5、实例化ConfigurationClassParser对象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 {//6、处理配置类parser.parse(candidates);//7、验证配置类的合法性和正确性parser.validate();Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());configClasses.removeAll(alreadyParsed);// Read the model and create bean definitions based on its contentif (this.reader == null) {this.reader = new ConfigurationClassBeanDefinitionReader(registry, this.sourceExtractor, this.resourceLoader, this.environment,this.importBeanNameGenerator, parser.getImportRegistry());}//加载Bean定义到Spring容器里this.reader.loadBeanDefinitions(configClasses);alreadyParsed.addAll(configClasses);candidates.clear();if (registry.getBeanDefinitionCount() > candidateNames.length) {String[] newCandidateNames = registry.getBeanDefinitionNames();Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));Set<String> alreadyParsedClasses = new HashSet<>();for (ConfigurationClass configurationClass : alreadyParsed) {alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());}for (String candidateName : newCandidateNames) {if (!oldCandidateNames.contains(candidateName)) {BeanDefinition bd = registry.getBeanDefinition(candidateName);if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&!alreadyParsedClasses.contains(bd.getBeanClassName())) {candidates.add(new BeanDefinitionHolder(bd, candidateName));}}}candidateNames = newCandidateNames;}}while (!candidates.isEmpty());// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classesif (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());}if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {// Clear cache in externally provided MetadataReaderFactory; this is a no-op// for a shared cache since it'll be cleared by the ApplicationContext.((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();}
}

这个方法可以简单的概括成以下几个步骤:

  1. 找到所有的配置类;
  2. 循环解析每个配置类;
  3. 把配置类加载到 Spring 容器里;

最重要的就是解析配置类,ConfigurationClassParser 类的 parse 方法,我们跟进去看下

3、ConfigurationClassParser 类的 parse 方法

//ConfigurationClassParser
public void parse(Set<BeanDefinitionHolder> configCandidates) {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);}}this.deferredImportSelectorHandler.process();
}

不论是哪个 if 条件,最终都会走到 ConfigurationClassParser 类的 processConfigurationClass 方法里,而且这些方法会将 DeferredImportSelector 接口的实现类放到 deferredImportSelectorHandler 里,在这个方法最后,调用这些类

image-20230703151637688

这里先跳过,我们先看 ConfigurationClassParser 类的 processConfigurationClass 方法

//ConfigurationClassParser
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);}// 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);do {sourceClass = doProcessConfigurationClass(configClass, sourceClass);}while (sourceClass != null);this.configurationClasses.put(configClass, configClass);
}

重点是 doProcessConfigurationClass 方法,跟进

//ConfigurationClassParser
@Nullable
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)throws IOException {if (configClass.getMetadata().isAnnotated(Component.class.getName())) {// Recursively process any member (nested) classes firstprocessMemberClasses(configClass, sourceClass);}// Process any @PropertySource annotationsfor (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 annotationsSet<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 immediatelySet<BeanDefinitionHolder> scannedBeanDefinitions =this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());// Check the set of scanned definitions for any further config classes and parse recursively if neededfor (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 annotationsprocessImports(configClass, sourceClass, getImports(sourceClass), true);// Process any @ImportResource annotationsAnnotationAttributes 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 methodsSet<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);for (MethodMetadata methodMetadata : beanMethods) {configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));}// Process default methods on interfacesprocessInterfaces(configClass, sourceClass);// Process superclass, if anyif (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 recursereturn sourceClass.getSuperClass();}}// No superclass -> processing is completereturn null;
}

这个方法也是很长的一段,关于 @Import 注解的其实就在 processImports 方法里,我们跟进看下

//ConfigurationClassParser
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)) {// Candidate class is an ImportSelector -> delegate to it to determine importsClass<?> 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)) {// Candidate class is an ImportBeanDefinitionRegistrar ->// delegate to it to register additional bean definitionsClass<?> 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 {// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->// process it as an @Configuration classthis.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();}}
}

可以看到核心代码就是对 @Import 注解的参数,那个 Class 数组做循环处理,而 if 条件是按我们之前所说的三种情况做区分的

image-20230703151309460

@Configuration 注解类型的就不讲了,就是再回去调用处理 @Configuration 注解的方法,下面重点看下 ImportBeanDefinitionRegistrar 接口和 ImportSelector 接口的场景

4、处理 ImportBeanDefinitionRegistrar 接口实现类

image-20230703154651578

先是实例化这个类,如果这个类实现了 Aware 相关的接口,就去设置下相关的属性,最后调用 addImportBeanDefinitionRegistrar 方法,将对象放到 importBeanDefinitionRegistrars 这个 map 里

image-20230703162607872

那么这个类的 registerBeanDefinitions 方法究竟在什么时候调用的呢?

上面我们讲到在解析完配置类之后,会调用 this.reader.loadBeanDefinitions(configClasses); 方法,我们跟进这个方法

image-20230703162836338

继续跟进 loadBeanDefinitionsForConfigurationClass 方法

image-20230703162909334

configClass.getImportBeanDefinitionRegistrars() 方法就是获取的上面所说的 importBeanDefinitionRegistrars 这个map,继续跟进 loadBeanDefinitionsFromRegistrars 方法

image-20230703162945514

可以看到循环map,调用了 registerBeanDefinitions 方法

5、处理 ImportSelector 接口实现类

image-20230703170552852

同样的,先实例化对象,然后处理 Aware 相关属性注入,然后开始区分了,如果是 DeferredImportSelector 接口的实现类,就封装成 DeferredImportSelectorHolder 对象,添加到 deferredImportSelectors 这个集合里,这个前面有说过,为了延迟调用,如果不是 DeferredImportSelector 接口的实现类,那就是 ImportSelector 接口的实现类,那么就先调用其 selectImports 方法,这个方法会返回 bean 的名字,然后转成 SourceClass 对象,再调用 processImports 方法处理,然后就看 selectImports 方法返回的 bean 是什么类型,再判断处理

总结一下,就是

  • 如果实现了 DeferredImportSelector 接口,那么就存到 deferredImportSelectors 集合里,等待后续处理
  • 如果没有实现 DeferredImportSelector 接口,那么是实现了 ImportSelector 接口,直接调用其 selectImports 方法

所以,实现了 DeferredImportSelector 接口的类的 selectImports 方法,在什么时候调用的呢?

6、处理 DeferredImportSelector 接口实现类

我们回到 ConfigurationClassParser 类的 parse 方法

image-20230703180344801

可以看到最后一步,就是处理所有 DeferredImportSelector 接口的实现类(后面简称deferredImports)的方法,我们跟进去

image-20230703180437893

这个方法里,先把所有的 deferredImports 排序,然后调用 DeferredImportSelectorGroupingHandlerregister 方法,把这些类进行分组,最后再调用 processGroupImports 方法处理,跟进这个方法

image-20230703180836187

可以看到对分组后的map,获取其内容,然后进行循环,获取每个分组中的 deferredImports 循环,调用 processImports 方法,我们看下 grouping.getImports() 返回的是什么

image-20230703181212745

可以看到,正是调用 selectImports 方法的返回,所以处理 DeferredImportSelector 接口的实现类,和处理 ImportSelector 接口的实现类,大致是差不多的,只不过 DeferredImportSelector 要延迟到最后处理,而且会根据不同的顺序和分组,分批处理。
dlerregister方法,把这些类进行分组,最后再调用processGroupImports` 方法处理,跟进这个方法

image-20230703180836187

可以看到对分组后的map,获取其内容,然后进行循环,获取每个分组中的 deferredImports 循环,调用 processImports 方法,我们看下 grouping.getImports() 返回的是什么

image-20230703181212745

可以看到,正是调用 selectImports 方法的返回,所以处理 DeferredImportSelector 接口的实现类,和处理 ImportSelector 接口的实现类,大致是差不多的,只不过 DeferredImportSelector 要延迟到最后处理,而且会根据不同的顺序和分组,分批处理。


http://www.ppmy.cn/news/716653.html

相关文章

nginx配置https加密

以下操作版本为Ubuntu2004&#xff0c;文件位置可能略有不同 https 功能 Web网站的登录页面通常都会使用https加密传输的&#xff0c;加密数据以保障数据的安全&#xff0c;HTTPS能够加密信息&#xff0c;以免敏感信息被第三方获取&#xff0c;所以很多银行网站或电子邮箱等等…

强化学习王者荣耀项目修改

修改模型参数中的名字 chkpt torch.load(weights/model_weights_StateJudgmentPre, map_location"cuda:0")chkpt[fn_layer.weight] chkpt.pop(图转.weight) chkpt[fn_layer.bias] chkpt.pop(图转.bias) chkpt[evaluate.weight] chkpt.pop(评价.weight) chkpt[eva…

修改定位工具相关的app

修改定位工具相关 修改定位工具1&#xff08;修改经纬度&#xff09;&#xff1a;110_45b1627a34040c58f39d02036c0a030c.apk修改定位工具2&#xff08;修改经纬度&#xff09;&#xff1a;商店搜索 小鹏定位助手 工具3&#xff1a;根据经纬度去查询工具3&#xff1a;可以利用…

逍遥模拟器过检测_王者荣耀怎么修改定位变更荣耀战区提升排位--逍遥模拟器电脑版更换荣耀战区...

王者荣耀怎么修改定位变更荣耀战区提升排位呢?我看了很多的答案,要么就是要就加微信号的假冒或者骗子信息,要么就是使用收费的软件,然后无法保证效果。真的没有简单好用还免费的方法吗? 下面我就将目前最简单方便修改定位变更荣耀战区提升排位的方法分享给大家,简单三步就…

iOS/Android 王者荣耀更换战区

玩王者的小伙伴&#xff0c;有时候人不在当地&#xff0c;但是需要将排位战区设置到指定地区&#xff0c;因为方便英雄排名&#xff0c;玩的爽&#xff0c;这个时候就需要通过手机虚拟定位&#xff0c;将战区修改到指定区域了。 注意&#xff1a;王者只支持 每周一 修改或设置…

王者该服务器未获取角色信息,王者荣耀荣耀榜尚未获取定位信息是怎么回事

王者荣耀游戏中出现一种情况&#xff0c;会显示荣耀榜尚未获取定位信息&#xff0c;很玩家并不知道为什么出现这种情况&#xff0c;那么王者荣耀荣耀榜尚未获取定位信息是怎么回事?下面就是针对荣耀榜尚未获取定位信息情况的详细解答&#xff0c;一起看看吧。 在目前游戏中荣耀…

安卓车机root改流浪地球_教你王者荣耀改战区

王者荣耀怎么改荣耀战区呢?今天小编给大家带来的是王者荣耀改荣耀战区方法哦!想知道的小伙伴就和小编一起来看看吧! 1、首先玩家需要下载一个多开器&#xff0c;保证能够多开王者荣耀&#xff0c;多开器安卓的可以直接市场里面找&#xff0c;苹果的在App Store也能找到。 2、接…

王者该服务器未获取角色信息,王者荣耀荣耀榜尚未获取定位信息是怎么回事[多图]...

王者荣耀游戏中出现一种情况&#xff0c;会显示荣耀榜尚未获取定位信息&#xff0c;很玩家并不知道为什么出现这种情况&#xff0c;那么王者荣耀荣耀榜尚未获取定位信息是怎么回事?下面就是针对荣耀榜尚未获取定位信息情况的详细解答&#xff0c;一起看看吧。 在目前游戏中荣耀…