手把手debug自动装配源码顺带弄懂了@Import等相关的源码(全文3w字超详细)
Posted 张子行的博客
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手把手debug自动装配源码顺带弄懂了@Import等相关的源码(全文3w字超详细)相关的知识,希望对你有一定的参考价值。
本文目录
- 前言
- SpringBoot启动的时候都做了一些什么事情?
- @SpringBootApplication注解结构
- @EnableAutoConfiguration、开启自动装配
- @AutoConfigurationPackage(确定扫描组件的范围)
- AutoConfigurationImportSelector <==> DeferredImportSelector
- DeferredImportSelector 源码开始解读
- 研究deferredImport、import源码入口
- parse方法是如何解析 BeanDefinition的?(processConfigurationClass方法)
- 真正处理解析configClass的地方(doProcessConfigurationClass)
- 解析启动类上的@ComponentScan注解源码探究
- 解析启动类上的@Import注解源码探究(processImports)
- 小结processImports()方法
- DeferredImportSelector源码解析
- DeferredImportSelector 怎么知道要装配哪些类的BeanDefinition(答)
- DeferredImportSelector是如何注入这些类的BeanDefinition?
- BeanDefinitionMap中的数据从哪来的?(loadBeanDefinitions)
- 个人阅读spring源码后的感受
- 附页之importSelector与DeferredImportSelector 的差别?
- 附页之Bean实例化所需的BeanDefinition从何而来?
前言
说到SpringBoot我们肯定不陌生,自动化的配置让我们在开发的过程中使用的十分爽,例如传统的mvc项目开启aop需要在xml文件中写上一大坨,而在SpringBoot项目中只需加上@EnableAspectJAutoProxy注解就完事了,还是那句话我们不应该只局限于会用,更应该懂原理,这样日后涉及到高阶的知识、或者我们需要横向扩展的时候将会顺畅很多。
SpringBoot启动的时候都做了一些什么事情?
曾几何时不知读者有没有思考过,为什么我们只要书写如下的一个SpringBoot启动类,项目中所有被@Component、@Configuration…注解的类就会被当做Bean来解析,注入到 IOC容器中去了,why?下图是一个很简单SpringBoot项目的启动类。简简单单一个@SpringBootApplication 注解帮我们搞定了所有的事情
@SpringBootApplication注解结构
@EnableAutoConfiguration:开启自动装配(不算怎么算本文的核心内容)
@ComponentScan:解析被@Component注解的标注的类,当做一个Bean注入到IOC容器中 (里面配置了一些过滤规则,不在本文讨论范围)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = @Filter(
type = FilterType.CUSTOM,
classes = TypeExcludeFilter.class
), @Filter(
type = FilterType.CUSTOM,
classes = AutoConfigurationExcludeFilter.class
)
)
public @interface SpringBootApplication
@AliasFor(
annotation = EnableAutoConfiguration.class
)
Class<?>[] exclude() default ;
@AliasFor(
annotation = EnableAutoConfiguration.class
)
String[] excludeName() default ;
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackages"
)
String[] scanBasePackages() default ;
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackageClasses"
)
Class<?>[] scanBasePackageClasses() default ;
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
@EnableAutoConfiguration、开启自动装配
@AutoConfigurationPackage:自动装配作用于哪些包(具体的这个自动装配的范围是多大,下文分析)
@AutoConfigurationImportSelector.class:导入 AutoConfigurationImportSelector 组件(具体这个组件在哪里解析的,下文分析!!! 如果读者着急可以直接跳到本文目录:解析启动类上的@Import注解源码探究)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default ;
String[] excludeName() default ;
@AutoConfigurationPackage(确定扫描组件的范围)
导入 Registrar的这么一个组件
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(Registrar.class)
public @interface AutoConfigurationPackage
Registrar作用:给容器中注入了一个BeanDefinition对象(封装了描述bean的各种信息的一个对象),debug进去看一下
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports
Registrar()
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry)
AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());
public Set<Object> determineImports(AnnotationMetadata metadata)
return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata));
可以看到我们的BeanDefinitionRegistry(BeanDefinition注册表)中会将已经是bean形态的BeanDefinition进行一个记录,注册到BeanDefinitionRegistry中。从图中我们可以看到我们com.zzh.payment 以及下面的子包符合条件的类都被当做 bean 注入到IOC容器中。
AutoConfigurationImportSelector <==> DeferredImportSelector
可以看到 AutoConfigurationImportSelector 本质为一个 DeferredImportSelector, DeferredImportSelector实现了ImportSelector 接口,效果和 ImportSelector 差不多都是给 IOC 容器中注入一些 BeanDefinition(注意只是注入BeanDefinition,并没有进行实例化Bean),至于差别下文具体分析。下面来思考俩个核心问题
- DeferredImportSelector 怎么知道要注入哪些 类 的BeanDefinition?
- DeferredImportSelector 是如何注入这些类 的 BeanDefinition?
DeferredImportSelector 源码开始解读
emmm想了一会如何接着写下文,如果不先搞懂@import的源码着实无法将DeferredImportSelector的源码给说明白,所以下文的源码部分有点长,并且这部分会牵涉到spring的源码部分。所以想要了解 @import、 DeferredImportSelector、ImportSelector、ImportBeanDefinitionRegistrar、@ComponentScan 的源码,debug进入下图标黄的地方就好了,spring在实例化Bean之前会做一些准备工作,调用BeanFactory的后置处理器(这些后置处理器的作用就是,通过解析@Configuration、@Component.、@Bean、@Import…这些注解以及DeferredImport、ImportSelector …这些接口,获取对应的BeanDefinition信息,然后将所有的BeanDefinition放入一个叫做beanDefinitionMap的容器中,最后在this.finishBeanFactoryInitialization() 中取出beanDefinitionMap中所有的BeanDefinition进行实例化Bean)
debug来到 invokeBeanFactoryPostProcessors()方法中,调用各种后置处理器,如果beanFactory是BeanDefinitionRegistry类型,从beanFactory中拿出ConfigurationClassBeanPostProcessor 的一个bean,然后紧接着去调用它(这个后置处理器的意思,顾名思义就是初始化处理和配置有关的类),注意里面这段代码 beanFactory.getBean(ppName,BeanDefinitionRegistryPostProcessor.class)涉及到Spring Ioc的源码,没读过Ioc源码的读者,可以这么简单理解:如果没有Bean就创建一个Bean然后返回,如果不存在Bean那么会先创建一个Bean,然后返回。总之一定会返回对应名字的一个Bean出来
值得一提的是,调用后置处理器的顺序是,先是处理实现了 PriorityOrdered 接口的、其次是处理实现了 Ordered接口的、最后处理什么接口都没有实现的后置处理器。如果beanFactory是ConfigurableListableBeanFactory类型,那么后置处理器执行依然是顺序PriorityOrdered 》Ordered 》other
接着debug进入invokeBeanDefinitionRegistryPostProcessors()来到 processConfigBeanDefinitions(),看看spring是如何解析我们的配置类的,利用我们的解析器 ConfigurationClassParser 去解析图中黄色对应类(SpringBoot项目的启动类 PaymentApplication )的 BeanDefinition 信息,具体的解析过程debug进入 parse()方法
研究deferredImport、import源码入口
大体可以分为2种情况来解析 BeanDefinitionHolder:
- 解析由注解方式注入的类(被@import、@Bean、@Configtion、@RestController…标注的类 )
- 解析由自动装配注入进来的类(META-INF目录下spring.factories文件中的类)
public void parse(Set<BeanDefinitionHolder> configCandidates)
//获取配置类
Iterator var2 = configCandidates.iterator();
while(var2.hasNext())
//获取配置类Holder
BeanDefinitionHolder holder = (BeanDefinitionHolder)var2.next();
//通过Holder来获取配置类的BeanDefinition
BeanDefinition bd = holder.getBeanDefinition();
try
//解析注解方式注入的类
if (bd instanceof AnnotatedBeanDefinition)
//解析配置类、import、compentscan等源码在里面有体现
this.parse(((AnnotatedBeanDefinition)bd).getMetadata(), holder.getBeanName());
else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition)bd).hasBeanClass())
this.parse(((AbstractBeanDefinition)bd).getBeanClass(), holder.getBeanName());
else
this.parse(bd.getBeanClassName(), holder.getBeanName());
catch (BeanDefinitionStoreException var6)
throw var6;
catch (Throwable var7)
throw new BeanDefinitionStoreException("Failed to parse configuration class [" + bd.getBeanClassName() + "]", var7);
//解析自动装配注入进来的类
this.deferredImportSelectorHandler.process();
注释写的很详细了,可以看到SpringBoot的启动类PaymentApplication的BeanDefinition是 AnnotatedBeanDefinition类型的,接着debug,进入 this.parse(((AnnotatedBeanDefinition)bd).getMetadata(), holder.getBeanName()) ,开始分析@Import的源码。
parse方法是如何解析 BeanDefinition的?(processConfigurationClass方法)
parse()方法本质为 processConfigurationClass()方法,processConfigurationClass()方法将我们的 BeanDefinition包装成了一个ConfigurationClass 对象,然后来到 processConfigurationClass 方法中,不着急debug,先来分析一下源码的大体结构,可以先看一下注释
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException
//判断configClass是否需要跳过解析
if (!this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION))
ConfigurationClass existingClass = (ConfigurationClass)this.configurationClasses.get(configClass);
//如果configurationClasses中已经存在configClass
if (existingClass != null)
if (configClass.isImported())
if (existingClass.isImported())
//configClass、existingClass都没有被import的情况下,将已有的configClass,与configurationClasses中的configClass进行合并
existingClass.mergeImportedBy(configClass);
return;
//configClass不是被import导入,但是已经存在于configurationClasses,移除当前configClass
this.configurationClasses.remove(configClass);
this.knownSuperclasses.values().removeIf(configClass::equals);
ConfigurationClassParser.SourceClass sourceClass = this.asSourceClass(configClass, filter);
do
//真正处理我们配置类的地方、核心
sourceClass = this.doProcessConfigurationClass(configClass, sourceClass, filter);
while(sourceClass != null);
//将configClass放入 configurationClasses、圈重点
this.configurationClasses.put(configClass, configClass);
顺着主脉络走,发现spring会将我们的主启动类(PaymentApplication)榨干,只要 sourceClass != null,那么将会一直循环执行this.doProcessConfigurationClass(configClass, sourceClass, filter),最终将我们的configClass(PaymentApplication)存入configurationClasses这个Map集合中,表示你已经被我解析过了
真正处理解析configClass的地方(doProcessConfigurationClass)
doProcessConfigurationClass方法源码
@Nullable
protected final ConfigurationClassParser.SourceClass doProcessConfigurationClass(ConfigurationClass configClass, ConfigurationClassParser.SourceClass sourceClass, Predicate<String> filter) throws IOException
if (configClass.getMetadata().isAnnotated(Component.class.getName()))
this.processMemberClasses(configClass, sourceClass, filter);
Iterator var4 = AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), PropertySources.class, PropertySource.class).iterator();
AnnotationAttributes importResource;
while(var4.hasNext())
importResource = (AnnotationAttributes)var4.next();
if (this.environment instanceof ConfigurableEnvironment)
this.processPropertySource(importResource);
else
this.logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() + "]. Reason: Environment must implement ConfigurableEnvironment");
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN))
Iterator var14 = componentScans.iterator();
while(var14.hasNext())
AnnotationAttributes componentScan = (AnnotationAttributes)var14.next();
Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
Iterator var8 = scannedBeanDefinitions.iterator();
while(var8.hasNext())
BeanDefinitionHolder holder = (BeanDefinitionHolder)var8.next();
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null)
bdCand = holder.getBeanDefinition();
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory))
this.parse(bdCand.getBeanClassName(), holder.getBeanName());
this.processImports(configClass, sourceClass, this.getImports(sourceClass), filter, true);
importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
if (importResource != null)
String[] resources = importResource.getStringArray("locations");
Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
String[] var20 = resources;
int var22 = resources.length;
for(int var23 = 0; var23 < var22; ++var23)
String resource = var20[var23];
String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
configClass.addImportedResource(resolvedResource, readerClass);
Set<MethodMetadata> beanMethods = this.retrieveBeanMethodMetadata(sourceClass);
Iterator var18 = beanMethods.iterator();
while(var18.hasNext())
MethodMetadata methodMetadata = (MethodMetadata)var18.next();
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
this.processInterfaces(configClass, sourceClass);
if (sourceClass.getMetadata().hasSuperClass())
String superclass = sourceClass.getMetadata().getSuperClassName();
if (superclass != null && !superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass))
this.knownSuperclasses.put(superclass, configClass);
return sourceClass.getSuperClass();
return null;
解析启动类上的@ComponentScan注解源码探究
在解析我们的主启动类之前,会先对SpringBoot项目启动类上的@ComponentScan注解进行解析,根据解析到被@Component标注的类的的BeanDefinition,重复执行上文分析过的Parse方法。
可以看到我项目中所有被@Component、@Configuration 、@RestController、@Import…这些注解修饰的类,都被@ComponentScan注解给扫描进来了,然后逐个解析。
解析启动类上的@Import注解源码探究(processImports)
解析完了@ComponentScan注解,然后来解析我们的Import注解了,debug进入this.processImports()。其实无论是解析什么注解,最终都会执行同一个方法,这里先卖个关子,先不说
先不着急分析this.processImports()的源码,我们先来关注一下,它里面的入参是些什么东西,what fark?this.getImports(sourceClass)?????
其实 this.getImports(sourceClass),就是获取主启动类上面所导入的Import组件,其中不乏开篇(@EnableAutoConfiguration、开启自动装配)说过的 AutoConfigurationImportSelector 类,此类即为自动装配的核心,原来你是在这里进行解析的啊!!!!!!!o my gad
对照我们的启动类来看,是不是瞬间清楚了很多呢
现在开始分析正文,来到 processImports()方法咯,这里的代码和@Import、ImportSelector 、ImportBeanDefinitionRegistrar 、DeferredImportSelector的源码有关系,不着急分析源码,先来大致过一遍源码的大体脉络
private void processImports(ConfigurationClass configClass, ConfigurationClassP以上是关于手把手debug自动装配源码顺带弄懂了@Import等相关的源码(全文3w字超详细)的主要内容,如果未能解决你的问题,请参考以下文章
spring boot 自动装配的实现原理和骚操作,不同版本实现细节调整,debug 到裂开......