@[TOC]
一、前言
我们前四篇博文,详细讨论了SpringBoot整个启动流程。博文如下:
1> 《SpringBoot启动流程一》:万字debug梳理SpringBoot如何加载并处理META-INF/spring.factories文件中的信息;
2> 《SpringBoot启动流程二》:七千字源码分析SpringApplication构造阶段;
3> 《SpringBoot启动流程三》:两万+字图文带你debug源码分析SpringApplication准备阶段(含配置文件加载时机、日志系统初始化时机);
4> 《SpringBoot启动流程四》:图文带你debug源码分析SpringApplication运行阶段和运行后阶段。
在启动流程中涉及SpringBoot的自动装配,虽然在之前我们聊过<SpringBoot自动装配机制原理>,但其中没有聊到@EnableAutoConfiguration
,@Import
注解是在何时被扫描的,本文就这一部分展开讨论。
注:Spring Boot版本:2.3.7.RELEASE。
二、入口
在Spring应用上下文准备阶段prepareContext()方法将应用的启动类加到Context中。
在Spring应用上下文启动阶段,会进入到refreshContext()方法,具体代码执行流程如下:
看ServletWebServerApplicationContext
的类图:
ServletWebServerApplicationContext间接继承自AbstractApplicationContext
,所以最终会进入到AbstractApplicationContext#refresh()
方法。
走到AbstractApplicationContext#refresh()
方法便意味着Spring应用上下文进入Spring生命周期,Spring Boot核心特性随之启动,比如:自动装配。
三、处理自动装配
1、处理自动装配的入口
最终进入到PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory,List<BeanFactoryPostProcessor>)
方法中,自动装配在其中实现;
invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory,List<BeanFactoryPostProcessor>)
方法中,传入接收到的入参beanFactory类型为DefaultListableBeanFactory
:
再看DefaultListableBeanFactory的类图:
其实现了BeanDefinitionRegistry
接口,所以会进入到if代码块:
在进入if代码块之后,会做两个操作:
1> 首先,遍历传入的三个BeanFactoryPostProcessor对其做分类;
分为常规后置处理器集合regularPostProcessors 和 注册处理器集合registryProcessors;
分类之后,regularPostProcessors有一个成员,registryProcessors中有两个成员。
final class PostProcessorRegistrationDelegate {
public static void invokeBeanFactoryPostProcessors( ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
....
// Do not initialize FactoryBeans here: We need to leave all regular beans
// uninitialized to let the bean factory post-processors apply to them!
// Separate between BeanDefinitionRegistryPostProcessors that implement
// PriorityOrdered, Ordered, and the rest.
List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();
// First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered.
// 这里只有一个值:org.springframework.context.annotation.internalConfigurationAnnotationProcessor
String[] postProcessorNames =
beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
for (String ppName : postProcessorNames) {
// internalConfigurationAnnotationProcessor实现了PriorityOrdered接口
if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
// 将ConfigurationClassPostProcessor添加到currentRegistryProcessors中
currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
processedBeans.add(ppName);
}
}
// 对currentRegistryProcessors做一个排序
sortPostProcessors(currentRegistryProcessors, beanFactory);
registryProcessors.addAll(currentRegistryProcessors);
// 走到这里registryProcessors中有三个对象了
// todo 核心所在
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
....
}
}
接着从BeanFactory中获取到BeanDefinitionRegistryPostProcessor
的实现类ConfigurationClassPostProcessor
,并将其添加到registryProcessors中;此时registryProcessors中有三个成员:
- SharedMetadataReaderFactoryContextInitializer的静态内部类
CachingMetadataReaderFactoryPostProcessor
;- ConfigurationWarningsApplicationContextInitializer的静态内部类
ConfigurationWarningsPostProcessor
;ConfigurationClassPostProcessor
;
2> 其次,执行当前注册处理器ConfigurationClassPostProcessor;
代码执行流程如下:
由于postProcessors中只有一个成员ConfigurationClassPostProcessor,进入到ConfigurationClassPostProcessor
的postProcessBeanDefinitionRegistry(BeanDefinitionRegistry)
方法。
从ConfigurationClassPostProcessor
#processConfigBeanDefinitions(BeanDefinitionRegistry)
方法开始真正进入到处理自动装配的核心逻辑。
2、处理自动装配内容
1)找启动类,构建ConfigurationClassParser解析器准备解析启动类
首先从DefaultLisableBeanFactory中获取所有已经注册的BeanDefinition名称;
candidateNames中包含了我们的启动类,此外还有6个internalXxx类;然后遍历找到启动类,将其加到configCandidates集合中。
找到启动类(saintSpringBootApplicatioin)之后,构建一个配置类解析器ConfigurationClassParser,其中包括ComponentScanAnnotationParser、ConditionEvaluator,分别用于包扫描和条件装配;
接着调用ConfigurationClassParser#parse()方法开始解析启动类进行应用程序的启动。
2)解析启动类
以ConfigurationClassParser#parse()方法为入口,部分代码执行流程如下:
其中在做条件装配时,有个点需要注意一下:ConfigurationCondition
接口内部的枚举类ConfigurationPhase
中有两个值PARSE_CONFIGURATION
、REGISTER_BEAN
,分别表示:在类解析阶段做条件装配、在类注册阶段做条件装配。
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
1> 继续看获取SourceClass的代码逻辑:
其中会校验启动类上@SpringBootApplication注解的合法性,然后将启动类和其注解元数据AnnotationMetadata封装到SourceClass中返回。
如果获取不到SourceClass,则不会执行配置类(启动类)的处理。
2> 获取到SourceClass之后,处理配置类和SourceClass:ConfigurationClassParser#doProcessConfigurationClass(ConfigurationClass,SourceClassPredicate<String>)
方法中会处理如下内容:
- 如果启动类configClass被@Component的衍生注解(递归注解的父注解可以找到@Component)标注,则首先递归处理所有成员(嵌套)类:即 configClass类内部如果找到成员类,会递归调用doProcessConfigurationClass()方法处理所有成员类。
- 解析启动类中所有的@PropertySource、@ComponentScan、@Import、@ImportResource、@Bean注解。
具体代码如下:
class ConfigurationClassParser {
....
@Nullable
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
throws IOException {
// 启动类configClass被@Component的衍生注解(递归注解的父注解可以找到@Component)标注
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
// 首先递归处理所有成员(嵌套)类:configClass类内部如果找到成员类,会递归调用doProcessConfigurationClass()方法处理所有成员类。
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)) {
// componentScan中包含11个成员,对应于@ComponentScan中11个属性
for (AnnotationAttributes componentScan : componentScans) {
// 处理@ComponentScan 中的属性,返回所有派生的@Component注解标注的类,然后立即进行扫描
// 此处会找到basePackages,其默认为启动类所在的目录
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
// TODO 实际业务中这里定义了多个派生@Component注解标注的类,这里就会循环多少次
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
// getImports()方法从启动类中获取所有的@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;
}
....
}
就一个最简单、最干净的SpringBoot程序来看,其中没有@PropertySource、@ImportResource注解,平时工程中也很少使用。所以本文我们着重看@ComponentScan、@Import两个注解的处理流程(也就是我们自动装配的核心所在)。
3)解析启动类中的@ComponentScan注解
// 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)) {
// componentScan中包含11个成员,对应于@ComponentScan中11个属性
for (AnnotationAttributes componentScan : componentScans) {
// 处理@ComponentScan 中的属性,返回所有派生的@Component注解标注的类,然后立即进行扫描
// 此处会找到basePackages,其默认为启动类所在的目录
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
// TODO 实际业务中这里定义了多个派生@Component注解标注的类,这里就会循环多少次
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());
}
}
}
}
首先获取启动类@SpringBootApplication注解中的11个属性,然后调用ComponentScanAnnotationParser#parse()
方法处理@SpringBootApplication注解中的注解 并 设置到类路径BeanDefinition扫描器ClassPathBeanDefinitionScanner
的相应属性中。
class ComponentScanAnnotationParser {
....
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, String declaringClass) {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
BeanUtils.instantiateClass(generatorClass));
ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
scanner.setScopedProxyMode(scopedProxyMode);
}
else {
Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
}
scanner.setResourcePattern(componentScan.getString("resourcePattern"));
for (AnnotationAttributes includeFilterAttributes : componentScan.getAnnotationArray("includeFilters")) {
List<TypeFilter> typeFilters = TypeFilterUtils.createTypeFiltersFor(includeFilterAttributes, this.environment,
this.resourceLoader, this.registry);
for (TypeFilter typeFilter : typeFilters) {
scanner.addIncludeFilter(typeFilter);
}
}
for (AnnotationAttributes excludeFilterAttributes : componentScan.getAnnotationArray("excludeFilters")) {
List<TypeFilter> typeFilters = TypeFilterUtils.createTypeFiltersFor(excludeFilterAttributes, this.environment,
this.resourceLoader, this.registry);
for (TypeFilter typeFilter : typeFilters) {
scanner.addExcludeFilter(typeFilter);
}
}
boolean lazyInit = componentScan.getBoolean("lazyInit");
if (lazyInit) {
scanner.getBeanDefinitionDefaults().setLazyInit(true);
}
Set<String> basePackages = new LinkedHashSet<>();
String[] basePackagesArray = componentScan.getStringArray("basePackages");
for (String pkg : basePackagesArray) {
String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
Collections.addAll(basePackages, tokenized);
}
for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
// 默认会走到这里
if (basePackages.isEmpty()) {
// 默认,basePackages为启动类所在的目录。eg:启动类为com.saint.SaintSpringBootApplication,basePackages为:com.saint
basePackages.add(ClassUtils.getPackageName(declaringClass));
}
scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
@Override
protected boolean matchClassName(String className) {
return declaringClass.equals(className);
}
});
return scanner.doScan(StringUtils.toStringArray(basePackages));
}
}
basePackages
属性为@ComponentScan注解的默认扫描包路径,如果没指定该属性,则会将启动类所在的包作为默认值 赋值basePackages
属性上(以启动类SaintSpringBootApplication为例,其默认扫包路径为:com.saint)。
给ClassPathBeanDefinitionScanner制定完所有属性之后,会调用其doScan(String...)
方法扫描basePackages
目录下的所有标注了@Component
衍生注解(比如:@Controller、@Service、@Repository)的类。
具体代码执行流程如下:
获取并注册完所有的@Component衍生类之后,在递归对这些类做解析。
4)解析启动类中的@Import注解
在此之前,我聊SpringBoot自动装配都是说,@EnableAutoConfiguration注解中通过@Import注解导入了AutoConfigurationImportSelector.class,@EnableAutoConfiguration注解中的@AutoConfigurationPackage中通过@Import注解导入了AutoConfigurationPackages.Registrar.class类,但我并不知道这里的@Import是在何时处理的!!这里我们就看一下针对@Import注解是怎么处理的。
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
这里分两步,首先通过getImports()
方法获取启动类中的@Import注解,然后再通过processImports()方法处理所有的@Import注解。
1> getImports()方法获取启动类中所有的@Import注解:
具体递归流程如下:
最终获取的@import注解有两个:
2> processImports()方法处理获取到的启动类中所有的@Import注解:
先处理AutoConfigurationPackages.Registrar.class类,再处理AutoConfigurationImportSelector类;
对AutoConfigurationPackages.Registrar.class类的处理比较简单,利用反射将其实例化之后,添加到启动类的importBeanDefinitionRegistrars
属性中。
由于AutoConfigurationImportSelector实现了DeferredImportSelector接口,所以会对AutoConfigurationImportSelector进行一个处理:将AutoConfigurationImportSelector
封装为DeferredImportSelectorHolder
对象,然后添加到ConfigurationClassParser
类的deferredImportSelectors
属性中(供后面处理@Import内容)。
5)处理所有的自动装配类
最后回到ConfigurationClassParser#parse()
方法中:
代码执行流程如下:
最终进入到AutoConfigurationImportSelector
#getAutoConfigurationEntry(AnnotationMetadata)
方法,这里的代码逻辑相信大家嘎嘎眼熟,在SpringBoot自动装配机制原理一文中我们聊过。
AutoConfigurationImportSelector
#getAutoConfigurationEntry(AnnotationMetadata)
方法源代码如下:
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 1. 获取@EnableAutoConfiguration标注类的元信息
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 2. 返回自动装配类的候选类名集合
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 3. 移除重复对象,因为 自动装配组件存在重复定义的情况
configurations = removeDuplicates(configurations);
// 4. 自动装配组件的排除名单
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// 5.1. 检查自动装配Class排除集合的合法性
checkExcludedClasses(configurations, exclusions);
// 5.2 排除掉不需要自动装配的Class
configurations.removeAll(exclusions);
// 6. 进一步过滤
configurations = getConfigurationClassFilter().filter(configurations);
// 7. 触发自动装配的导入事件,事件包括候选的装配组件类名单和排除名单。
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
其负责拿到所有自动配置的节点,大致分为六步;
- 第一步,在
getCandidateConfigurations()
方法中利用Spring Framework工厂机制的加载器SpringFactoriesLoader
,通过SpringFactoriesLoader#loadFactoryNames(Class, ClassLoader)
方法读取所有META-INF/spring.factories
资源中@EnableAutoConfiguration
所关联的自动装配Class集合。- 第二步,利用Set不可重复性对
自动装配Class集合
进行去重,因为自动装配组件存在重复定义的情况;- 第三步,读取当前配置类所标注的@EnableAutoConfiguration注解的属性exclude和excludeName,并与
spring.autoconfigure.exclude
配置属性的值 合并为自动装配class排除集合。- 第四步,校验自动装配Class排除集合的合法性、并排除掉
自动装配Class排除集合
中的所有Class(不需要自动装配的Class)。- 第五步,再次过滤后候选自动装配Class集合中不符合条件装配的Class成员;
- 最后一步,触发自动装配的导入事件。
phase1> getCandidateConfigurations() --> 获取自动装配类:
代码执行流程如下:getSpringFactoriesLoaderFactoryClass()
方法返回我们熟悉的EnableAutoConfiguration
注解类;
紧接着,SpringFactoriesLoader.loadFactoryNames(Class<?>, ClassLoader)
方法会获取所有META-INF/Spring.factories的配置文件,进而获取到所有的自动装配类;
loadFactoryNames()原理如下:
- 搜索指定ClassLoader下所有的META-INF/spring.fatories资源内容;
- 将搜索到的资源内容作为Properties文件读取,合并为一个Key为
接口的全类名
、Value为实现类全类名 列表
的Map,作为方法的返回值;- 最后从上一步返回的Map中查找并返回方法指定类型 对应的
实现类全类名列表
。
loadFactoryNames()方法源码解释如下:
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
// 先从缓存中获取
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
/**
* 查找所有我们依赖的jar包,并找到对应有META-INF/spring.factories⽂件,然后获取⽂件中的内容
*
* 第一次循环:file:/.../org/springframework/spring-beans/5.2.12.RELEASE/spring-beans-5.2.12.RELEASE.jar!/META-INF/spring.factories
* 第二次循环:file:/.../org/springframework/boot/spring-boot/2.3.7.RELEASE/spring-boot-2.3.7.RELEASE.jar!/META-INF/spring.factories
* 第三次循环:file:/../org/springframework/boot/spring-boot-autoconfigure/2.3.7.RELEASE/spring-boot-autoconfigure-2.3.7.RELEASE.jar!/META-INF/spring.factories
*/
URL url = urls.nextElement();
// 获取资源
UrlResource resource = new UrlResource(url);
// 获取资源的内容
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
- 第一次扫描:
- 第二次扫描:
- 第三次扫描:
phase5> getConfigurationClassFilter().filter(configurations) --> 过滤候选自动装配Class集合中不符合条件装配的Class成员:
这里分两步:
第一步:获取所有的Filter
首先通过getConfigurationClassFilter()
方法从所有META-INF/spring.factories
文件中获取所有的自动装配过滤器AutoConfigurationImportFilter
的实现类(一共有三个),然后实例化AutoConfigurationImportSelector
类的内部类ConfigurationClassFilter
,并将获取多的所有AutoConfigurationImportFilter
的实现类集合赋值到ConfigurationClassFilter的filters
属性中(后面会用到它做条件装配)。
第二步:执行条件装配
然后对获取到的所有自动装配类(最干净、最简单的SpringBoot程序有127个)执行过滤操作(条件装配)后,还剩23个自动装配类。
关于此处为什么127个自动装配类经过AutoConfigurationImportFilter
过滤后只剩23个了,且听下回分解《SpringBoot自动装配中的条件装配》。
后面就一路返回返回返回!!!!