在传统的Spring应用中,开发者需要手动配置大量数据,这种配置通常包括数据源、事务管理、视图解析器等。这种手动配置方式在大型项目中显得特别繁琐且容易出错。
Spring Boot引入自动配置机制,只需要添加相关依赖,无需配置。这极大地简化了Spring应用的配置, 大幅减少了开发者的工作量,帮助开发者快速创建应用。
SpringBoot
的自动配置是通过@EnableAutoConfiguration
和META-INF/spring.factories
文件实现的,而这些机制的底层依赖于ConfigurationClassPostProcessor
来解析和注册自动配置类。
本文会先介绍一些重要概念,然后进入SpringApplication.run
源码逐一串联起这些重要概念,以此来探讨SpringBoot
自动配置机制 的实现原理。
分析基于SpringBoot 2.3.4
1. @EnableAutoConfiguration
是一个组合注解,@EnableAutoConfiguration
是组合注解@SpringBootApplication
其中的一个注解, 其作用是启用自动配置机制
1 |
|
Spring框架提供的各种名字为@Enable开头的Annotation,比如@EnableScheduling、@EnableCaching、@EnableMBeanExport等,@EnableAutoConfiguration的理念和“做事方式”其实一脉相承,简单概括一下就是,它们的工作方式都是借助@Import的支持,收集和注册特定场景相关的bean定义:
- @Enable Scheduling是通过@Import将Spring调度框架相关的bean定义都加载到IoC容器。
- @Enable M Bean Export是通过@Import将JMX相关的bean定义加载到IoC容器。
@EnableAutoConfiguration 一样,也是借助@Import的帮助,将所有符合自动配置条件的bean定义加载到IoC容器,仅此而已!
@EnableAutoConfiguration
本身也是由多个注解组成1
2
3
4
5
6
7
8
9
10
11
12
public EnableAutoConfiguration {
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
- 自动配置包扫描:
@AutoConfigurationPackage
注解标记了包路径,这意味着Spring Boot会自动扫描该包及其子包中的所有组件。 - 导入选择器:
@Import(AutoConfigurationImportSelector.class)
用于导入AutoConfigurationImportSelector
类,后者负责选择并加载自动配置类。
1.1 AutoConfigurationImportSelector
AutoConfigurationImportSelector
会根据各种条件注解(如@ConditionalOnClass
、@ConditionalOnMissingBean
等)来决定是否导入某个自动配置类。
借 助AutoConfigurationImportSelector
, @EnableAutoConfiguration
可以帮助SpringBoot应用将所有符合条件的@Configuration
配置都加载到当前SpringBoot创建并使用的IoC容器
1 | public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, |
各个Aware
类和Ordered
接口, 在 Spring IOC 容器启动过程拓展点一文中已经都介绍过,有需要可以点击查看。DeferredImportSelector
比较陌生,下面来介绍一下
1.2 DeferredImportSelector
1 | public interface DeferredImportSelector extends ImportSelector {} |
DeferredImportSelector
扩展了ImportSelector
接口。DeferredImportSelector
的主要作用是延迟配置类的导入,确保在所有常规的@Configuration
类和@Import
注解处理之后再进行处理。这种延迟处理机制提供了更大的灵活性,特别是在复杂的自动配置场景中,能够根据先前的配置结果决定是否导入额外的配置类。
1 | // 处理和决定哪些自动配置类应该被包含或排除在 Spring Boot 应用程序的自动配置过程中。该方法通过一系列步骤和检查,最终返回一个包含自动配置类和排除类的 `AutoConfigurationEntry` 实例。 |
getCandidateConfigurations 查找所有满足条件的自动配置类
2. META-INF/spring.factories 文件
META-INF/spring.factories
用于定义各种扩展点(如自动配置类、环境后处理器等)的实现类。
文件使用简单的键值对格式,每一行定义一个映射关系。键是接口或抽象类的完全限定名,值是实现类的完全限定名,多个实现类用逗号分隔。
反斜杠 \
在属性文件中用于将长行拆分为多行。这样做的目的是为了使文件更易于阅读和维护。在 spring.factories
文件中,这通常用于定义多行值。
SpringBoot
自动配置实现的过程中,会查找类路径下所有的META-INF/spring.factories
文件。
所有指的是包括应用本身及其所有依赖JAR包中的META-INF/spring.factories
文件。
在SpringBoot项目实际启动过程中,查找到的META-INF/spring.factories
文件包括以下4个
- spring-boot-autoconfigure-2.3.4.RELEASE.jar META-INF/spring.factories
- spring-boot-2.3.4.RELEASE.jar META-INF/spring.factories
- spring-beans-5.2.9.RELEASE.jar META-INF/spring.factories
- mybatis-spring-boot-autoconfigure-2.1.4.jar META-INF/spring.factories
META-INF/spring.factories
文件的具体加载逻辑在SpringFactoriesLoader。loadSpringFactories
方法中具体实现
3. SpringFactoriesLoader
SpringFactoriesLoader.loadFactoryNames
用来加载获取所有META-INF/spring.factories
中的配置信息, 其加载流程大致有以下步骤
- 获取资源路径:
loadSpringFactories
方法首先会获取类加载器,并查找类路径下所有名为META-INF/spring.factories
的资源文件。 读取并解析文件:对每个找到的
META-INF/spring.factories
文件,使用PropertiesLoaderUtils.loadProperties
方法将其加载为Properties
对象。处理文件内容:遍历
Properties
对象的每一个条目,将键(工厂类的名称)和值(实现类的全限定名列表)存入结果Map
中。返回结果:返回一个
Map
,其中键是工厂类的名称,值是实现类名称的列表。
1 | package org.springframework.core.io.support; |
针对META-INF/spring.factories
的加载过程有2点需要注意
3.1 缓存机制
1 | private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>(); |
该缓存的作用是在同一个ClassLoader下,避免了对同一个spring.factories文件的重复加载,因为后面的代码有同样的流程。
所以该缓存的存在可以提高SpringBoot 项目的启动速度。
3.2 MultiValueMap
cache 缓存中的value 是MultiValueMap。
MultiValueMap 在实际开发中不太常用, 从debug信息中可以看到,就是同一个key 可以对应多个value 。
其getOrDefault
方法的执行逻辑可以描述如下:
- 检查键是否存在:
- 使用
containsKey
方法检查给定键是否存在于映射中。
- 使用
- 返回值或默认值:
- 如果键存在,则返回与该键关联的值。
- 如果键不存在,则返回提供的默认值。
3.3 条件过滤
经过loadSpringFactories
处理 META-INF/spring.factories
文件所有的配置信息均已经存储在cache中。
回到loadFactoryNames
方法中,通过MultiValueMap.getOrDefault
获取指定类型 的配置信息。
1 | public static List<String> loadFactoryNames(Class<?> factoryType, { ClassLoader classLoader) |
4. 自动配置类-@Configuration
Spring Boot的自动配置类通过一系列@Configuration
类和条件注解来实现,并通过@EnableAutoConfiguration
注解启用。
在 Spring IOC 容器启动过程拓展点 一文中提到过,在Spring IOC容器中,Bean的定义方式确实有三种,XML配置、注解配置和Java Config配置。
在实际业务开发中, Java Config 使用较少。 不过在SpringBoot 自动配置的实现过程中, Java config 有大量使用。
Java Config配置通过Java类和方法来定义和配置Bean,利用了Spring的@Configuration
和@Bean
注解。
@Configuration
类:Spring 会扫描所有带有@Configuration
注解的类。- 解析
@Bean
方法:ConfigurationClassPostProcessor
会解析这些类中的@Bean
方法。 - 注册
BeanDefinition
:对于每个@Bean
方法,会被ConfigurationClassPostProcessor
解析,Spring 会创建一个BeanDefinition
对象,并将其注册到BeanFactory
中。BeanDefinition
的名称默认是@Bean
方法的方法名,除非在@Bean
注解中显式指定了name
属性。
4.1 示例代码-MybatisLanguageDriverAutoConfiguration
以在 mybatis-spring-boot-autoconfigure-2.1.4.jar META-INF/spring.factories 出现的MybatisLanguageDriverAutoConfiguration
为例看一下具体使用
在自动配置类中,有我们熟悉的sqlSessionFactory
1 |
|
4.2 条件配置 and 条件注解
自动配置类依赖于条件注解来确保只有在满足特定条件时才会进行配置。这些注解包括:
@ConditionalOnClass
:只有在类路径中存在指定的类时,才会启用该配置。@ConditionalOnMissingBean
:只有在Spring上下文中不存在指定的Bean时,才会创建该Bean。@ConditionalOnProperty
:只有在配置文件中存在指定属性并且值匹配时,才会启用该配置。
4.3 排除自动配置
在某些情况下,开发者可能希望排除某些自动配置类。可以通过@SpringBootApplication
注解的exclude
属性来实现:
1 |
|
或者在配置文件中排除:
1 | spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration |
4.4 自定义自动配置
开发者可以创建自定义的自动配置类,以满足特定需求。自定义自动配置类需要使用@Configuration
和条件注解,并将其打包到JAR文件中,以便在其他Spring Boot应用中使用。例如:1
2
3
4
5
6
7
8
9
10
public class MyCustomAutoConfiguration {
public MyCustomService myCustomService() {
return new MyCustomServiceImpl();
}
}
5. ConfigurationClassPostProcessor
1 | public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor, |
ConfigurationClassPostProcessor
的职责 主要有两个
- 解析
@Configuration
及其相关注解成BeanDefinition
- 将解析得到的
BeanDefinition
注册到BeanFactory
中。
解析的注解包括以下内容
@Configuration
类:解析所有标记为@Configuration
的类, 所有自动配置类均有@Configuration
注解。@ComponentScan
注解:处理@ComponentScan
注解,扫描指定包路径下的组件(如@Component
,@Repository
,@Service
,@Controller
等注解标记的类)。@Import
注解:处理@Import
注解,导入其他配置类或处理ImportSelector
和ImportBeanDefinitionRegistrar
。@Bean
方法:解析@Bean
方法,将其定义的 bean 注册为BeanDefinition
。@PropertySource
注解:处理@PropertySource
注解,加载属性源文件。
5.1 实现继承关系
5.1.1 BeanFactoryPostProcessor
从实现继承图中可以看出, ConfigurationClassPostProcessor
实现了BeanFactoryPostProcessor
接口,BeanFactoryPostProcessor
的作用时机是invokeBeanFactoryPostProcessors
。
关于BeanFactoryPostProcessor
, 更多可以阅读 Spring IOC 容器启动过程拓展点
5.1.2 BeanDefinitionRegistryPostProcessor
关于BeanDefinitionRegistryPostProcessor
, Spring IOC 容器启动过程拓展点已经介绍过,它继承了BeanFactoryPostProcessor
, 并增加了一个方法。1
2
3
4
5public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
}
在ConfigurationClassPostProcessor
中,postProcessBeanDefinitionRegistry
方法会被调用,用于处理配置类和自动配置类。
5.2 internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
是Spring框架内部使用的一个特殊的Bean名称,用于标识和注册ConfigurationClassPostProcessor
的实例。
这是Spring框架内部使用的一种机制,用于在Spring容器启动时处理@Configuration
注解及相关配置。
1 | public abstract class AnnotationConfigUtils { |
在 Spring 容器启动过程中,ConfigurationClassPostProcessor
会进行以下步骤:
- 扫描
MybatisLanguageDriverAutoConfiguration
类:识别该类是一个配置类。 - 解析
@Bean
方法:- 识别
defaultLanguageDriver
方法和anotherLanguageDriver
方法上的@Bean
注解。
- 识别
- 创建
BeanDefinition
:- 为每个
@Bean
方法创建一个BeanDefinition
对象。
- 为每个
- 注册
BeanDefinition
:- 使用
defaultLanguageDriver
作为beanName
注册defaultLanguageDriver
方法的BeanDefinition
。 - 使用
anotherLanguageDriver
作为beanName
注册anotherLanguageDriver
方法的BeanDefinition
。
- 使用
最终,Spring 容器中会有两个 bean,它们的名称分别是 defaultLanguageDriver
和 anotherLanguageDriver
,对应的 BeanDefinition
是通过解析 @Bean
方法得到的。
6. ApplicationContextInitializer 接口
在 Spring Boot 中,ApplicationContextInitializer
是一个扩展点接口,主要用于在应用上下文ConfigurableApplicationContext
执行一在些refresh
之前的额外配置。
1 | public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> { |
Spring Boot 提供了一些内置的 ApplicationContextInitializer
实现,它们在应用启动过程中起着重要的作用。
SharedMetadataReaderFactoryContextInitializer
:用于共享MetadataReaderFactory
。ConditionEvaluationReportLoggingListener
:用于在条件评估报告时记录日志。ConfigurationWarningsApplicationContextInitializer
:用于在配置警告时初始化应用上下文。
在自动配置实现的过程中,ApplicationContextInitializer
非常重要,其具体实现类SharedMetadataReaderFactoryContextInitializer
与ConfigurationClassPostProcessor
有密切关系。
6.1 注册方式
可以通过多种方式注册 ApplicationContextInitializer
,以下是几种常见的方法:
6.1.1 通过 Spring Boot 的 SpringApplication
在 Spring Boot 应用中,可以在 SpringApplication
中注册 ApplicationContextInitializer
:
1 | public class MyApplication { |
6.1.2 通过 META-INF/spring.factories
可以在 META-INF/spring.factories
文件中声明 ApplicationContextInitializer
:
1 | org.springframework.context.ApplicationContextInitializer=com.example.MyApplicationContextInitializer |
7. SharedMetadataReaderFactoryContextInitializer
SharedMetadataReaderFactoryContextInitializer
的作用是Spring 应用程序上下文初始化期间,提供一个共享的 MetadataReaderFactory
实例,并通过缓存元数据读取来提高性能。
SharedMetadataReaderFactoryContextInitializer
通过在 Spring 应用程序启动期间注册和配置共享的 MetadataReaderFactory
实现了优化性能的目的。其主要作用是确保所有需要读取元数据的组件共享同一个 MetadataReaderFactory
实例,从而减少重复的元数据读取操作,提高启动和运行效率。
1 | class SharedMetadataReaderFactoryContextInitializer |
7.1 CachingMetadataReaderFactoryPostProcessor
CachingMetadataReaderFactoryPostProcessor
是SharedMetadataReaderFactoryContextInitializer
的一个静态内部类, 它实现了BeanDefinitionRegistryPostProcessor
, PriorityOrdered
两个接口。
关于BeanDefinitionRegistryPostProcessor
, PriorityOrdered
在 Spring IOC 容器启动过程拓展点已经介绍过
BeanDefinitionRegistryPostProcessor
继承了BeanFactoryPostProcessor
, 并增加了一个方法PriorityOrdered
是Ordered
接口的一个子接口,允许BeanFactoryPostProcessor
在其他普通Ordered
执行。这主要用于那些必须首先应用的处理器,比如那些涉及配置如何加载的处理器。
1 | private static class CachingMetadataReaderFactoryPostProcessor |
7.1.1 postProcessBeanFactory
BeanFactoryPostProcessor
中定义的方法, CachingMetadataReaderFactoryPostProcessor
对该方法是空实现1
2
3
4
5
public interface BeanFactoryPostProcessor {
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}
7.1.2 postProcessBeanDefinitionRegistry
BeanDefinitionRegistryPostProcessor
中定义的接口1
2
3public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
}
CachingMetadataReaderFactoryPostProcessor
重写该方法时包含2个重要步骤1
2
3
4
5
6
7
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
// 注册共享的 `MetadataReaderFactory`。
register(registry);
// 配置 `ConfigurationClassPostProcessor`
configureConfigurationClassPostProcessor(registry);
}
7.1.2.1 register-SharedMetadataReaderFactoryBean的BeanDefinition注册
向注册表中添加一个 SharedMetadataReaderFactoryBean
的 Bean 定义。1
2
3
4
5
6
7
8
9private static class CachingMetadataReaderFactoryPostProcessor{
// 向注册表中添加一个 `SharedMetadataReaderFactoryBean` 的 Bean 定义。
private void register(BeanDefinitionRegistry registry) {
BeanDefinition definition = BeanDefinitionBuilder
.genericBeanDefinition(SharedMetadataReaderFactoryBean.class, SharedMetadataReaderFactoryBean::new)
.getBeanDefinition();
registry.registerBeanDefinition(BEAN_NAME, definition);
}
}
从代码中可以看到, SharedMetadataReaderFactoryBean
的bean 定义注册是通过内部标识org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory
完成的, 相当于在bean 实例化的过程中, 遇到beanName 是org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory
时,就知道需要实例化SharedMetadataReaderFactoryBean
这个工厂bean
7.1.2.2 configureConfigurationClassPostProcessor
配置 ConfigurationClassPostProcessor
, 将共享的 MetadataReaderFactory
关联到其 metadataReaderFactory
属性。1
2
3
4
5
6
7
8
9
10private void configureConfigurationClassPostProcessor(BeanDefinitionRegistry registry) {
try {
BeanDefinition definition = registry
.getBeanDefinition(AnnotationConfigUtils.CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME);
definition.getPropertyValues().add("metadataReaderFactory", new RuntimeBeanReference(BEAN_NAME));
}
catch (NoSuchBeanDefinitionException ex) {
}
}
}
从代码中可以看到, ConfigurationClassPostProcessor
的bean 定义注册也是通过内部特使标识完成的, 其标识是org.springframework.context.annotation.internalConfigurationAnnotationProcessor
相当于在bean 实例化的过程中, 遇到beanName 是org.springframework.context.annotation.internalConfigurationAnnotationProcessor
时,就知道需要实例化 ConfigurationClassPostProcessor
这个类。
7.2 SharedMetadataReaderFactoryBean
在6.1.2.1 中注册的BeanDefinition
是SharedMetadataReaderFactoryBean
。
SharedMetadataReaderFactoryBean
也是SharedMetadataReaderFactoryContextInitializer
的一个静态内部类, 它是一个FactoryBean
1 | static class SharedMetadataReaderFactoryBean |
7.2.1 ConcurrentReferenceCachingMetadataReaderFactory
ConcurrentReferenceCachingMetadataReaderFactory
是 Spring Framework 中一个用于缓存和并发优化的类,专门用于读取类的元数据(如注解、方法、字段等)时提高性能。它通过使用并发数据结构和缓存机制,显著减少了重复的元数据读取操作,从而提升了性能。
以上都是SpringBoot 自动配置机制实现过程中会涉及到的重要知识点,提前了解他们有助于帮助更好地了解SpringBoot 自动配置的实现。
现在思考,自动配置类是如何从配置文件中的一行文字变成Spring 容器中可实际发挥作用的实例bean
- 加载自动配置类名称:读取
META-INF/spring.factories
文件,找到所有的自动配置类,由SpringFactoriesLoader.loadFactoryNames
完成 - 注册自动配置类BeanDefinition:所有的自动配置类如果想成为实例bean, 要在Spring 容器BeanFactory中先有BeanDefinition , 自动配置类BeanDefinition的生成由
ConfigurationClassPostProcessor
完成 - 自动配置类的的实例化:根据BeanDefinition 实例化自动配置类。自动配置类有了bean definition 后, 就可以通过getBean流程被实例成bean, 即getBean
在 Spring bean 实例化一文中,已经详细介绍了以refresh
方法为入口的Spring IOC 容器启动过程,不论是以哪种方式启动, refresh 都是入口, 例如ClassPathXmlApplicationContext
方式refresh也是入口
1 | ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); |
我们需要来看下, 在SpringBoot 启动的Spring 项目中, 在进入refresh
入口前进行了哪些操作,这些都是SpringBoot 实现自动配置的重要步骤
8. 加载自动配置类名称
我们在分析 Spring bean 实例化时, 只分析了以refresh方法为入口的代码, 如果想要充分了解SpringBoot 自动配置的机制, 不只要了解refresh 里面的流程,还要分析SpringApplication.run
为入口的代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public class CodingInActionApplication {
public static void main(String[] args) { SpringApplication.run(CodingInActionApplication.class, args);
}
public class SpringApplication {
/**
* The class name of application context that will be used by default for web
* environments.
*/
public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot."
+ "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
}
8.1 new SpringApplication
1 | public class SpringApplication { |
8.1.1 setInitializers
8.1.2 SpringFactoriesLoader.loadFactoryNames
从SpringApplication.run
为入口深入源码, 可以在SpringApplication的构造函数中看到如下代码,1
2
3
4
5
6
7
8
9public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
在getSpringFactoriesInstances
方法中调用了SpringFactoriesLoader.loadFactoryNames
, 该方法完成了对META-INF/spring.factories
文件的解析,进而完成自动配置类名称的自动加载工作
1 | private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) { |
根据前文说过,在SpringBoot项目实际启动过程中,查找到的META-INF/spring.factories
文件包括以下4个
- spring-boot-autoconfigure-2.3.4.RELEASE.jar META-INF/spring.factories
- spring-boot-2.3.4.RELEASE.jar META-INF/spring.factories
- spring-beans-5.2.9.RELEASE.jar META-INF/spring.factories
- mybatis-spring-boot-autoconfigure-2.1.4.jar META-INF/spring.factories
从这4个文件中, 一共找到13个key。
经过loadSpringFactories
处理, 4个META-INF/spring.factories
文件所有的配置信息均已经存储在result 这个Map 中的。
回到loadFactoryNames
方法中,通过MultiValueMapgetOrDefault
获取指定类型 的配置信息。
1 | public static List<String> loadFactoryNames(Class<?> factoryType, { ClassLoader classLoader) |
在setInitializers
方法中, 需要的是 org.springframework.context.ApplicationContextInitializer
对应的自动配置类。1
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
从图中可以看出, 满足条件的自动配置类有7个, 接下来我们会重点关注其中的org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer
8.1.3 反射-createSpringFactoriesInstances
获取到满足满足条件的自动配置类全限定类名后,就是用经典的反射流程完成相应类的实例化。
关于反射,可以点击阅读 java 反射 实践与原理
8.2 run
在SpringApplication 实例化完成后,开始执行run逻辑,其中refreshContext
进去后,就是 Spring 容器的启动过程,创建 BeanFactory
、以及BeanFactory 容器中各个bean 的实例化过程。
在 Spring 容器的启动之前,先来看下ApplicationContext
做了哪些准备
⚠️:这里我们还是要区分以下BeanFactory
和ApplicationContext
在Spring IOC 容器启动过程拓展点一文中曾提到过,BeanFactory
是Spring框架中最基础的IOC容器。它提供了基本的依赖注入机制,负责管理Bean的实例化、配置和生命周期。ApplicationContext
是在BeanFactory
基础上扩展的高级容器,提供了更多面向应用的功能,它还提供了国际化支持和框架事件体系,更易于创建实际应用。
一般称BeanFactory为IoC容器,而称ApplicationContext为应用上下文。
1 | // 该方法只保留了部分代码 |
8.2.1 prepareContext
1 | //该方法只保留了部分代码 |
8.2.2 applyInitializers
1 | //该方法只保留了部分代码 |
在preContext
逻辑中, 只重点关注applyInitializers
, 在SpringApplication.setInitializers
方法中设置了从配置文件读取并通过反射创建7个ApplicationContextInitializer
的具体实现类实例。 每个实现类在这里都会执行重写的initialize
方法
8.2.2 SharedMetadataReaderFactoryContextInitializer
在ApplicationContextInitializer
的7个具体实现类实例中, 关注SharedMetadataReaderFactoryContextInitializer
从代码中可以看到SharedMetadataReaderFactoryContextInitializer.initialize
的逻辑是将 CachingMetadataReaderFactoryPostProcessor
这个BeanFactoryPostProcessor
添加到应用上下文 ApplicationContext
中进行记录管理。
1 | public abstract class AbstractApplicationContext extends DefaultResourceLoader |
以上内容都还是在应用上下文中进行处理,我们已经将配置在所有META-INF/spring.factories
文件中的信息全部解析完成,并将配置信息存储在了SpringFactoriesLoader
的cache
, 方便后面流程快速读取,接下来将分析进入refresh 后,自动配置类的处理过程。
9. 注册自动配置类的BeanDefinition
9.1 invokeBeanFactoryPostProcessors
在 Spring IOC 容器启动过程拓展点一文中,已经讲过,BeanFactoryPostProcessor
是 Spring 框架在bean 开始整个创建流程之前 起作用。其具体时机是refresh 中的invokeBeanFactoryPostProcessors
方法
1 | public abstract class AbstractApplicationContext extends DefaultResourceLoader |
接下来的内容,要重点分析在SharedMetadataReaderFactoryContextInitializer.initialize
方法中创建的BeanFactoryPostProcessor
: CachingMetadataReaderFactoryPostProcessor
9.2 CachingMetadataReaderFactoryPostProcessor
前面讲过,在applyInitializers
逻辑中,SharedMetadataReaderFactoryContextInitializer.initialize
的逻辑是将 CachingMetadataReaderFactoryPostProcessor
这个BeanFactoryPostProcessor
添加到应用上下文 ApplicationContext
中进行记录管理。
同时CachingMetadataReaderFactoryPostProcessor
也是一个BeanDefinitionRegistryPostProcessor
, 在 PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors
逻辑中调用其重写的postProcessBeanDefinitionRegistry
,进行SharedMetadataReaderFactoryBean
和ConfigurationClassPostProcessor
的BeanDefinition
注册
9.3 ConfigurationClassPostProcessor
现在当前Spring 容器beanFactory 中已经有了ConfigurationClassPostProcessor
的的BeanDefinition,
同时ConfigurationClassPostProcessor
也是一个BeanDefinitionRegistryPostProcessor
1 | public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor, |
所以当invokeBeanFactoryPostProcessors
方法执行到如下逻辑时,ConfigurationClassPostProcessor
对应的beanName org.springframework.context.annotation.internalConfigurationAnnotationProcessor
会返回1
2
3public static void invokeBeanFactoryPostProcessors(
ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) { beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
}
beanFactory.getBean
会触发ConfigurationClassPostProcessor
的实例化过程
9.4 postProcessBeanDefinitionRegistry
ConfigurationClassPostProcessor
的实例化完成后,继续执行`invokeBeanDefinitionRegistryPostProcessors
1 | invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry) |
9.4.1 获取解析候选类
获取当前beanFactory中, 所有 BeanDefinition
名称,检查每个对应的 BeanDefinition
, 找出被@Configuretion
标注且还未处理的类, 显然被@SpringBootApplication
注解标注的启动类名称是符合条件的
1 | List<BeanDefinitionHolder> configCandidates = new ArrayList<>(); |
9.4.2 ConfigurationClassParser
前面我们说过,ConfigurationClassPostProcessor
的职责 主要有两个
- 将
@Configuration
及其相关注解,解析成BeanDefinition
- 将解析得到的
BeanDefinition
注册到BeanFactory
中。
其实实际执行解析工作的核心组件是 ConfigurationClassParser
。
循环处理过滤出的候选类,使用ConfigurationClassParser 对候选配置进行解析直到所有配置类都被解析和注册。
1 | ConfigurationClassParser parser = new ConfigurationClassParser( |
针对不同类的BeanDefinition进行不同的parse 操作, 一个SpringBoot 项目,其启动代码标注的注解@SpringBootApplication
是AnnotatedBeanDefinition
类型的注解 , 所以这里是我们需要关注的逻辑1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31class 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();
}
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
}
}
9.4.3 parse
解析的过程,通过parse 的递归完成项目中所有BeanDefinition的定义1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26// 只保留了部分代码
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
// Recursively process the configuration class and its superclass hierarchy.
SourceClass sourceClass = asSourceClass(configClass, filter);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
}
while (sourceClass != null);
this.configurationClasses.put(configClass, configClass);
}
private SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
processMemberClasses(configClass, sourceClass);
processPropertySources(sourceClass);
processComponentScan(sourceClass);
processImports(configClass, sourceClass);
processImportSource(configClass, sourceClass);
processInterfaces(configClass, sourceClass);
processSuperclass(configClass, sourceClass);
return null;
}doProcessConfigurationClass
是伪代码, 下面具体展开介绍
9.4.3.1 @ComponentScan
@ComponentScan
是复合注解@SpringBootApplication
的组成部分,@ComponentScan
对应XML配置形式中的@ComponentScan
自动扫描的范围,如果不指定,则默认Spring框架实现会从声明@ComponentScan
所在类的package进行扫描。
@ComponentScan
扫描的包括@Component
、@Service
、@Repository
、@Controller
这段逻辑处理后,一个项目中定义的各种业务bean, 都会被解析到形成configClass
1 | // Process any @ComponentScan annotations |
这里会递归调用parse,处理每个解析的业务bean 定义
9.4.3.2 @Import
1 | // Process any @Import annotations |
getImports
从sourceClass中找出@Import
processImports
方法处理@Import
注解,导入配置类或特定的配置选择器(如ImportSelector
或ImportBeanDefinitionRegistrar
)。
一般来讲,一个SpringBoot项目的启动类如果配置如下1
2
3
public class CodingInActionApplication {}
那么可以找到以下3个通过@Import
注解导入的类
- AutoConfigurationImportSelector
- AutoConfigurationPackages.Registrar
- MapperScannerRegistrar
1 |
|
1 | private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, |
这段代码处理了三种类型的候选类(candidate
)在@Import
注解中的情况:ImportSelector
、ImportBeanDefinitionRegistrar
和普通的@Configuration
类。
- 候选类是
ImportSelector
类型- 如果
ImportSelector
对象是DeferredImportSelector
的实例,则将其交给deferredImportSelectorHandler
处理。 - 否则递归处理这些导入的类。
- 如果
- 候选类是
ImportBeanDefinitionRegistrar
类型:通过ParserStrategyUtils.instantiateClass
方法实例化ImportBeanDefinitionRegistrar
对象。将该对象注册到configClass
中,以便它可以注册额外的bean定义。 - 如果候选类既不是
ImportSelector
也不是ImportBeanDefinitionRegistrar
类型,则将其视为普通的@Configuration
类。递归处理该类
我们重点关注AutoConfigurationImportSelector
,它和自动配置类的解析有关, 且它是一个DeferredImportSelector
,所以会执行deferredImportSelectorHandler.handle
逻辑
在processImports
逻辑中,就可以遇到
parse 执行完成后, 业务上注册的bean 其配置信息均已处理完成,接下来就可以处理自动配置类了
9.4.4 DeferredImportSelectorHandler
AutoConfigurationImportSelector
,它和自动配置类的解析有关, 且它是一个DeferredImportSelector
,所以会执行deferredImportSelectorHandler.handle
先将其记录下来,等所有其他正常数据处理完成后再来处理AutoConfigurationImportSelector
1 | class ConfigurationClassParser { |
9.4.4.1 handle
handle
方法,用于处理ConfigurationClass
和DeferredImportSelector
的配对。具体来说:
- 如果当前没有
DeferredImportSelectorHolder
,则立即处理新的延迟导入选择器。 - 如果已有未处理的
DeferredImportSelectorHolder
,则将新的延迟导入选择器添加到列表中,以便稍后批量处理
DeferredImportSelectorHolder
:一个持有ConfigurationClass
和DeferredImportSelector
的配对类。
1 | class ConfigurationClassParser { |
9.4.4.2 process
1 | class ConfigurationClassParser { |
process 方法会在parse 结束后, 才执行deferredImportSelectorHandler.process
逻辑, 符合它延迟执行的特性。下面来具体看下DeferredImportSelector 是如何解析自动配置类信息的1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43class ConfigurationClassParser {
private class DeferredImportSelectorHandler {
public void process() {
List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
this.deferredImportSelectors = null;
try {
if (deferredImports != null) {
DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
deferredImports.forEach(handler::register);
handler.processGroupImports();
}
}
finally {
this.deferredImportSelectors = new ArrayList<>();
}
}
}
private class DeferredImportSelectorGroupingHandler{
public void processGroupImports() {
for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
Predicate<String> exclusionFilter = grouping.getCandidateFilter();
grouping.getImports().forEach(entry -> {
ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
try {
processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter),
Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)),
exclusionFilter, false);
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configurationClass.getMetadata().getClassName() + "]", ex);
}
});
}
}
}
}
register
先进入register, 将AutoConfigurationImportSelector
这个DeferredImportSelector
添加到configurationClasses
这个map h中,前面经过parse 阶段解析出业务bean 信息都在map 中
getImports
在这里能够看到getImports,最终调用的的是SpringFactoriesLoader.loadFactoryNames
, 这个方法SpringApplication
初始化时已经执行读取过所有的META-INF/spring.factories
文件,所以这里会直接使用cache 里面的数据,不会重复读取META-INF/spring.factories
文件了。
从下图可以看到AutoConfigurationEntry
存储了自动配置类的信息
processImports
拿到自动配置类信息后,就可以进行解析了,
processImports在9.3.5.2 节中讲过有3种逻辑
- 候选类是
ImportSelector
类型- 如果
ImportSelector
对象是DeferredImportSelector
的实例,则将其交给deferredImportSelectorHandler
处理。 - 否则递归处理这些导入的类。
- 如果
- 候选类是
ImportBeanDefinitionRegistrar
类型:通过ParserStrategyUtils.instantiateClass
方法实例化ImportBeanDefinitionRegistrar
对象。将该对象注册到configClass
中,以便它可以注册额外的bean定义。 - 如果候选类既不是
ImportSelector
也不是ImportBeanDefinitionRegistrar
类型,则将其视为普通的@Configuration
类,执行processConfigurationClass
递归处理该类
针对自动配置类会走到第3个逻辑, 以MybatisAutoConfiguration
为例,解析该自动配置类会处理其用@Bean标注的2个bean
9.4.5 loadBeanDefinitions(configClasses)
parse 工作完成后,就可以loadBeanDefinitions
到BeanFactory中了1
2
3
4
5
6
7
8class ConfigurationClassBeanDefinitionReader {
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
for (ConfigurationClass configClass : configurationModel) {
loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
}
}
}
9.5 postProcessBeanFactory
以上注解解析+BeanDefinition 注册都是ConfigurationClassPostProcessor
中重写的BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry
中的逻辑
现在来看ConfigurationClassPostProcessor
中重写的BeanFactoryPostProcessor.postProcessBeanFactory
1 | public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor, |
这行代码最重要的逻辑是 enhanceConfigurationClasses
方法,它生成 @Configuration
类的 CGLIB 代理,以确保 @Bean
方法的正确性和单例性。代理类会覆盖 @Bean
方法,并在需要时调用 Spring 容器以获取已经创建的单例 bean。
为什么要给@Configuration
类 生成代理对象
- Spring 容器中的每个 bean 默认是单例的,即使
@Bean
方法被多次调用,也应返回相同的实例。 - 如果直接调用
@Bean
方法,每次调用都会创建一个新的实例,违反单例模式。
关于CGLIB 动态代理对象的生成, 可阅读查看Mybaits 通过自动配置与SpringBoot 集成的文章
10.自动配置类的的实例化
至此,整个项目中所有定义的bean,不论是SpringBoot 框架的自动配置类,,还是业务代码中手动配置的, 所有bean的BeanDefinition 全部注册完成。
普通业务bean得实例化过程我们已经非常熟悉了, 这里我们以Mybatis 自动配置类也是用同样的getBean流程完成实例化,具体实例化流程可以点击阅读Spring 集成 Mybatis
11 自动配置实现总结
自动配置类是如何从配置文件中的一行文字变成Spring 容器中可实际发挥作用的实例bean
11.1 加载自动配置类名称
自动配置类的名称在SpringApplication 实例化的过程中会完成加载工作。
具体的加载由SpringFactoriesLoader.loadFactoryNames
读取所有META-INF/spring.factories
文件,将文件中的信息用MultiValueMap
存储,同时加载后的信息会缓存下来供后续使用,防止重复加载。
同时在这个过程中有SharedMetadataReaderFactoryContextInitializer
添加到了ApplicationContext
中进行管理, 当后续执行其initialize
时会
注册ConfigurationClassPostProcessor
这个BeanPostFactoryProcessor
11.2 注册自动配置类BeanDefinition
所有的自动配置类如果想成为实例bean, 要在Spring 容器BeanFactory中先有BeanDefinition , 自动配置类BeanDefinition
的注册由ConfigurationClassPostProcessor
完成。
ConfigurationClassPostProcessor
作为BeanPostFactoryProcessor
, 其作用时机是invokeBeanFactoryPostProcessors
.
ConfigurationClassPostProcessor
的职责 主要有两个
- 将
@Configuration
及其相关注解,解析成BeanDefinition
- 具体解析是有
- 再具体一点其实是由完成的
3.
- 将解析得到的
BeanDefinition
注册到BeanFactory
中。
将@Configuration
及其相关注解,解析成BeanDefinition
实际是由ConfigurationClassParser
完成的
ConfigurationClassParser
通过对@Import
注解的处理,解析出AutoConfigurationImportSelector
。AutoConfigurationImportSelector
才是最终负责自动配置类解析的工具, 因为它是DeferredImportSelector
,所以AutoConfigurationImportSelector
会先通过反射实例化然后记录下来,- 等其他bean parse 工作完成后,再把
AutoConfigurationImportSelector
找出来处理自动配置类,将@Bean
标注的方法解析成BeanDefinition
- 当所有bean, 包括业务代码定义的bean 和自动配置类涉及到的bean, 全部注册到Spring容器BeanFactory 中
11.3 自动配置类的的实例化
根据BeanDefinition 实例化自动配置类。自动配置类有了bean definition 后, 就可以通过getBean流程被实例成bean, 即getBean