Spring Boot 源码研读之 SpringApplication 对象的创建
SpringApplication 对象创建
一般 Spring Boot 项目都是通过在main函数中执行SpringApplication.run(XXXX.class,args);来启动的,而run方法的内部执行首先会进行SpringApplication对象的创建,让我们来看看SpringApplication对象创建做了哪些事。
通过代码跟踪最终 SpringApplication 对象创建调用的是如下构造函数:
public 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(); this.bootstrapRegistryInitializers = new ArrayList<>( getSpringFactoriesInstances(BootstrapRegistryInitializer.class)); setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass(); }通过代码分析,我可以看到 SpringApplication 对象的创建主要有如下操作:
- resourceLoader:默认赋值null
- primarySources:Set<Class<?>> 存放当前启动类 XXXX.class 集合,注意:启动类可能在其他位置
- webApplicationType:通过判断相关类是否加载,确定web服务类型是 REACTIVE,还是 SERVLET。
- bootstrapRegistryInitializers:通过加载并读取
META-INF/spring.factories文件中的配置并结合反射的方式来实例化所有BootstrapRegistryInitializer接口的实现类。 - setInitializers:通过加载并读取
META-INF/spring.factories文件中的配置并结合反射的方式来实例化所有ApplicationContextInitializer接口的实现类。 - setListeners:通过加载并读取
META-INF/spring.factories文件中的配置并结合反射的方式来实例化所有ApplicationListener接口的实现类。 - mainApplicationClass:通过当前线程的堆栈来找到main()函数所在的类,即启动类
getSpringFactoriesInstances(Clazz.class)实现
getSpringFactoriesInstances方法的实现如下图所示,核心逻辑就是读取META-INF/spring.factories配置文件,并通过反射的方式创建指定接口类型的实现类实例。具体使用也可以参考另外一篇文章 《Spring 源码之 SpringFactoriesLoader 类简介-CSDN博客》
通过这一步我们就获取到了所有BootstrapRegistryInitializer、ApplicationContextInitializer、ApplicationListener接口的实现类。
补充
spring.factories加载机制
- 核心机制:
SpringFactoriesLoader.loadSpringFactories()走classpath*:/META-INF/spring.factories,解析key=全限定接口名,value=实现类全限定名,用反射newInstance()批量实例化。 - 那为什么SpringApplication 在容器还没创建时就能拿到这些扩展点?
- 因为它走的是classpath 静态扫描 + 反射,不依赖 BeanFactory。这是 Spring Boot启动期扩展点机制,和运行期的
BeanPostProcessor是两套体系。
- 因为它走的是classpath 静态扫描 + 反射,不依赖 BeanFactory。这是 Spring Boot启动期扩展点机制,和运行期的
- 演进史
- Spring Boot 2.7+:引入
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(一行一个自动配置类),逐步替代 spring.factories 里的 auto-configuration 部分 - Spring Boot 3.0+:spring.factories 里的 auto-configuration 段正式废弃,仅保留少量非 auto-configuration 段
- Spring Boot 3.2+:spring.factories 文件完全废弃(实际并没有完全废弃)
- Spring Boot 2.7+:引入
- 原因:spring.factories 是一个 key-value 巨型文件,所有扩展点都堆在一起,加载时全量解析;改成
*.imports后按需加载,启动更快、更易维护 - 总结一句话:"SpringApplication 构造函数用 classpath 扫描 + 反射加载扩展点,绕开 Spring 容器,让扩展点在容器起来之前就能用。2.7 之后 Spring 把这条路从 spring.factories 拆成 .imports,3.2 完全废弃,本质是从'全量加载'变'按需加载'。"
WebApplicationType补充
- 三种类型:
REACTIVE(WebFlux)/SERVLET(Spring MVC)/NONE(非 Web 应用) - 判断逻辑:
deduceFromClasspath()用ClassUtils.isPresent依次探测:- 存在
DispatcherServlet→SERVLET(但这个判断有问题,见下) - 存在
WebFlux相关类且没有DispatcherServlet→REACTIVE - 都不存在 →NONE
- 存在
- ⚠️ 误判场景:项目引了
spring-web(因为某个工具包传递依赖),没真用 Spring MVC,可能被误判为 SERVLET,启动报"找不到 DispatcherServlet"或加载多余 MVC Bean - 解法:手动
setWebApplicationType(WebApplicationType.NONE),或在 SpringApplication 启动前排除多余依赖
deduceMainApplicationClass 链式启动支持
- 实现:
new RuntimeException().getStackTrace()拿堆栈,遍历找到main方法所在类 - 为什么用堆栈而不是参数传入?为了支持
SpringApplicationBuilder链式/嵌套启动——用户可能从SpringBootServletInitializer或别的启动类拉起,主类在运行期才确定 - Trade-off:实现简单(不传参) vs 启动类可动态决定——选后者
注:spring boot 版本为3.2.3
