SpringBoot是如何启动的
基于SpringBoot2.6.2版本
我们的启动类一般都是这样的:
从上面代码看,调用了SpringApplication的静态方法run。这个run方法会构造一个SpringApplication的实例,然后再调用这里实例的run方法就表示启动SpringBoot。
构造SpringApplication的实例
SpringApplication#run(java.lang.Class<?>, java.lang.String...)
SpringApplication(org.springframework.core.io.ResourceLoader, java.lang.Class<?>...)
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();
//加载META-INF/spring.factories路径ApplicationContextInitializer.class
this.bootstrapRegistryInitializers = new ArrayList<>(
getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//加载META-INF/spring.factories路径ApplicationListener.class
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
分析:
- 通过ClassLoader.getResources加载META-INF/spring.factories路径下的 文件信息,从中找key为ApplicationContextInitializer.class,并实例化。
- 通过ClassLoader.getResources加载META-INF/spring.factories路径下的 文件信息ApplicationListener.class对应类,并实例化。
SpringApplication#run(java.lang.String...)
SpringApplication实例构造好后,就会调用SpringApplication的run方法。
SpringApplication#run(java.lang.String...)
- 找到所有SpringApplicationRunListener的实现类并将其实例化
- 发布应用开始启动事件ApplicationStartedEvent
- 初始化应用参数
- 创建并配置当前SpringBoot应用将要使用的Environment
- 打印banner
- 根据WebApplicationType创建应用上下文ConfigurableApplicationContext
- 预处理上下文:加载environment,执行ApplicationContextInitializer的initialize()方法,SpringApplicationRunListener的contextLoaded()方法
- 刷新应用上下文:跟Spring的流程大体一致,这里就包括通过@EnableAutoConfiguration导入的各种自动配置类
- 再一次刷新上下文,其实是空方法,可能是为了后续扩展。
- 发布应用已经启动的事件
- 遍历所有注册的ApplicationRunner和CommandLineRunner,并执行其run()方法
public ConfigurableApplicationContext run(String... args) {
long startTime = System.nanoTime();
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
/*
* 配置属性:
* 设置系统属性 java.awt.headless,为 true 则启用headless模式
* headless模式是应用的一种配置模式,在服务器缺少显示设备、键盘、鼠标等外设的情况下可以使用该模式
* 比如我们使用的Linux服务器就是缺少前述的这些设备,但是又需要使用这些设备提供的能力
*/
configureHeadlessProperty();
/*
* 1、获取监听器
* 通过SpringFactoriesLoader检索META-INF/spring.factories,
* 找到声明的所有SpringApplicationRunListener的实现类并将其实例化。
* 实际上只有EventPublishingRunListener一个实现类,以下实际调用的都是调用EventPublishingRunListener对应的方法。
*/
SpringApplicationRunListeners listeners = getRunListeners(args);
// 2、发布应用开始启动事件ApplicationStartedEvent
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
/* 初始化参数 */
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
/*
* 3、配置环境
* 创建并配置当前SpringBoot应用将要使用的Environment(包括配置要使用的PropertySource以及Profile),
* 并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法,广播Environment准备完毕。
*/
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
//4、配置需要忽略哪些Bean
configureIgnoreBeanInfo(environment);
//5、打印banner,如果在resources目录下创建了我们自己的banner就会进行打印,否则默认使用spring的
Banner printedBanner = printBanner(environment);
//6、根据WebApplicationType创建应用上下文ConfigurableApplicationContext
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
/*
* 7、预处理上下文
* 为ApplicationContext加载environment,之后逐个执行ApplicationContextInitializer的initialize()方法来进一步封装ApplicationContext,
* 并调用所有的SpringApplicationRunListener的contextPrepared()方法,【EventPublishingRunListener只提供了一个空的contextPrepared()方法】,
* 之后初始化IoC容器,并调用SpringApplicationRunListener的contextLoaded()方法,广播ApplicationContext的IoC加载完成。
*/
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
//8、重要!!! 刷新应用上下文,跟Spring的流程大体一致,这里就包括通过@EnableAutoConfiguration导入的各种自动配置类。
refreshContext(context);
//再一次刷新上下文,其实是空方法,可能是为了后续扩展。
afterRefresh(context, applicationArguments);
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
}
//9、发布应用已经启动的事件
listeners.started(context, timeTakenToStartup);
/* 10、遍历所有注册的ApplicationRunner和CommandLineRunner,并执行其run()方法。
* 我们可以实现自己的ApplicationRunner或者CommandLineRunner,来对SpringBoot的启动过程进行扩展。
*/
callRunners(context, applicationArguments);
} catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
//11、发布应用已经启动完成的监听事件
listeners.ready(context, timeTakenToReady);
} catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}
下面我们就对其中最主要的一些步骤进行讲解。
第1步 SpringApplicationRunListeners listeners = getRunListeners(args);
获取监听器。
通过SpringFactoriesLoader检索META-INF/spring.factories,找到声明的所有SpringApplicationRunListener的实现类并将其实例化。实际上只有EventPublishingRunListener一个实现类,以下实际调用的都是调用EventPublishingRunListener对应的方法。
第2步 listeners.starting(bootstrapContext, this.mainApplicationClass);
org.springframework.boot.context.event.EventPublishingRunListener#starting
发布应用开始启动事件ApplicationStartedEvent。
第3步 ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
创建并配置当前SpringBoot应用将要使用的Environment(包括配置要使用的PropertySource以及Profile),并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法,广播Environment准备完毕。
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// Create and configure the environment
// 得到环境对象ConfigurableEnvironment
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 并配置环境信息;对listeners初始化环境属性
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
// 发布ApplicationEnvironmentPreparedEvent事件
listeners.environmentPrepared(bootstrapContext, environment);
DefaultPropertiesPropertySource.moveToEnd(environment);
Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
"Environment prefix cannot be set via properties.");
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = convertEnvironment(environment);
}
ConfigurationPropertySources.attach(environment);
return environment;
}
第4步 configureIgnoreBeanInfo(environment)
配置哪些Bean是需要被忽略的。
第5步 Banner printedBanner = printBanner(environment);
打印banner,如果在resources目录下创建了我们自己的banner就会进行打印,否则默认使用spring的。
#有console、log、off三个配置项,默认是有console
spring.main.banner-mode=off
第6步 context = createApplicationContext();
根据WebApplicationType创建应用上下文AnnotationConfigServletWebServerApplicationContext,它是ServletWebServerApplicationContext的子类。
这里使用的是 函数式接口@FunctionalInterface
this.webApplicationType在构建SpringApplication实例的时候由WebApplicationType.deduceFromClasspath();进行赋值了。
第7步 prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
预处理上下文。
为ApplicationContext加载environment,之后逐个执行ApplicationContextInitializer的initialize()方法来进一步封装ApplicationContext,并调用所有的SpringApplicationRunListener的contextPrepared()方法,【EventPublishingRunListener只提供了一个空的contextPrepared()方法】,之后初始化IoC容器,并调用SpringApplicationRunListener的contextLoaded()方法,广播ApplicationContext的IoC加载完成。
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
// 对ApplicationContext设置环境变量;
context.setEnvironment(environment);
// 配置属性ResourceLoader和ClassLoader属性;
postProcessApplicationContext(context);
// 循环ApplicationContextInitializer,执行initialize方法
applyInitializers(context);
// 发布上下文预准备事件
listeners.contextPrepared(context);
bootstrapContext.close(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {
((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences);
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
//创建BeanDefinitionLoader,调用其load()方法
load(context, sources.toArray(new Object[0]));
//发布上下文loaded事件
listeners.contextLoaded(context);
}
第8步 refreshContext(context)
刷新应用上下文,跟Spring的流程大体一致,这里就包括通过@EnableAutoConfiguration导入的各种自动配置类。
org.springframework.boot.SpringApplication#refresh
这个applicationContext是refreshContext(context)传进来的,在第6步中,我们可以看到创建的是AnnotationConfigServletWebServerApplicationContext实例,它的父类是ServletWebServerApplicationContext。
所以,refresh()方法调用的是org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#refresh,如下:
org.springframework.context.support.AbstractApplicationContext#refresh
终于看到我们熟悉的方法,上下文刷新,跟Spring的流程基本一致。
第①个红圈会去查找SpringBoot的自动配置化类,并组装成BeanDefinition,注册加入到ioc容器中。
如何组装自动化配置类,请参考:SpringBoot是如何将自动化配置类加入到ioc容器中
第②个红圈会调用org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#onRefresh,其中 createWebServer() 方法是用来启动web服务的,但是还没有真正启动 Tomcat,只是通过ServletWebServerFactory 创建了一个 WebServer。
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#getSelfInitializer
prepareWebApplicationContext方法会初始化Root WebApplicationContext(这里有个疑问:Spring中是有父子容器的,那DispatcherServlet的mvc环境是如何创建呢?)。
获取到一个org.springframework.boot.web.servlet.ServletContextInitializer后,因为内嵌webserver默认是Tomcat,是进入org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory#getWebServer方法。
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
/** 1、创建Tomcat实例 **/
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
/** 2、给创建好的tomcat设置连接器connector **/
tomcat.setConnector(connector);
/** 设置不自动部署 **/
tomcat.getHost().setAutoDeploy(false);
/** 3、配置Tomcat容器引擎 **/
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
/**
* 准备Tomcat的StandardContext,并添加到Tomcat中,同时把initializers 注册到类型为
* TomcatStarter的ServletContainerInitializer中
*/
prepareContext(tomcat.getHost(), initializers);
/** 将创建好的Tomcat包装成WebServer返回**/
return getTomcatWebServer(tomcat);
}
getWebServer() 这个方法创建了 Tomcat 对象,并且做了两件重要的事情:
- 把连接器 Connector 对象添加到 Tomcat 中;
- 配置容器引擎,configureEngine(tomcat.getEngine());
首先说一下这个 Connector 连接器,Tomcat 有两个核心功能:
- 处理 Socket 连接,负责网络字节流与 Request 和 Response 对象的转化。
- 加载和管理 Servlet,以及具体处理 Request 请求。
针对这两个功能,Tomcat 设计了两个核心组件来分别完成这两件事,即:连接器(Connector)和容器(Container)。
整个过程大致就是:Connector 连接器接收连接请求,创建Request和Response对象用于和请求端交换数据,然后分配线程让Engine(也就是Servlet容器)来处理这个请求,并把产生的Request和Response对象传给Engine。当Engine处理完请求后,也会通过Connector将响应返回给客户端。
这里面提到了 Engine,这个是 Tomcat 容器里的顶级容器(Container),我们可以通过 Container 类查看其他的子容器:Engine、Host、Context、Wrapper。
4者的关系是:Engine 是最高级别的容器,Engine 子容器是 Host,Host 的子容器是 Context,Context 子容器是 Wrapper,所以这4个容器的关系就是父子关系,即:Wrapper > Context > Host > Engine (>表示继承)。
至此我们了解了 Engine 这个就是个容器,然后我们再看一下这个 configureEngine(tomcat.getEngine()) 具体干了啥:
private void configureEngine(Engine engine) {
engine.setBackgroundProcessorDelay(this.backgroundProcessorDelay);
Iterator var2 = this.engineValves.iterator();
while(var2.hasNext()) {
Valve valve = (Valve)var2.next();
engine.getPipeline().addValve(valve);
}
}
其中 engine.setBackgroundProcessorDelay(this.backgroundProcessorDelay) 是指定背景线程的执行间隔,例如背景线程会在每隔多长时间后判断session是否失效之类。
再回到 getWebServer() 方法,最终 getWebServer() 方法返回了 TomcatWebServer。
return this.getTomcatWebServer(tomcat);
通过 getTomcatWebServer() 方法,继续下沉:
/**
* 构造函数实例化 TomcatWebServer
**/
public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null;
initialize();
}
private void initialize() throws WebServerException {
/** 我们在启动 Spring Boot 时经常看到打印这句话 **/
logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
addInstanceIdToEngineName();
Context context = findContext();
context.addLifecycleListener((event) -> {
if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
// Remove service connectors so that protocol binding doesn't
// happen when the service is started.
removeServiceConnectors();
}
});
/** 启动 tomcat **/
// Start the server to trigger initialization listeners
this.tomcat.start();
// We can re-throw failure exception directly in the main thread
rethrowDeferredStartupExceptions();
try {
ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
}
catch (NamingException ex) {
// Naming is not enabled. Continue
}
// Unlike Jetty, all Tomcat threads are daemon threads. We create a
// blocking non-daemon to stop immediate shutdown
startDaemonAwaitThread();
}
catch (Exception ex) {
stopSilently();
destroySilently();
throw new WebServerException("Unable to start embedded Tomcat", ex);
}
}
}
至此,Tomcat 启动。
第9步 listeners.started(context, timeTakenToStartup);
发布应用已经启动的事件。
第10步 callRunners(context, applicationArguments);
遍历所有注册的ApplicationRunner和CommandLineRunner,并执行其run()方法。 我们可以实现自己的ApplicationRunner或者CommandLineRunner,来对SpringBoot的启动过程进行扩展。
第11步 listeners.ready(context, timeTakenToReady);
发布应用已经启动完成的监听事件
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!