惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

P
Proofpoint News Feed
H
Hacker News: Front Page
C
CXSECURITY Database RSS Feed - CXSecurity.com
C
Cisco Blogs
P
Palo Alto Networks Blog
Know Your Adversary
Know Your Adversary
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
Cybersecurity and Infrastructure Security Agency CISA
AWS News Blog
AWS News Blog
Spread Privacy
Spread Privacy
S
Schneier on Security
The Hacker News
The Hacker News
Cyberwarzone
Cyberwarzone
T
Tenable Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
T
Tailwind CSS Blog
S
Secure Thoughts
N
Netflix TechBlog - Medium
T
The Exploit Database - CXSecurity.com
I
Intezer
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Help Net Security
Help Net Security
K
Kaspersky official blog
Google Online Security Blog
Google Online Security Blog
L
LangChain Blog
Martin Fowler
Martin Fowler
L
LINUX DO - 热门话题
Hacker News: Ask HN
Hacker News: Ask HN
www.infosecurity-magazine.com
www.infosecurity-magazine.com
有赞技术团队
有赞技术团队
P
Privacy International News Feed
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Recent Announcements
Recent Announcements
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
The Register - Security
The Register - Security
云风的 BLOG
云风的 BLOG
Google DeepMind News
Google DeepMind News
阮一峰的网络日志
阮一峰的网络日志
WordPress大学
WordPress大学
Recorded Future
Recorded Future
The Last Watchdog
The Last Watchdog
G
Google Developers Blog
T
Threatpost
小众软件
小众软件
S
Securelist
Recent Commits to openclaw:main
Recent Commits to openclaw:main
O
OpenAI News

博客园 - 纪莫

技术面:如何让你的系统抗住高并发的流量? 技术面:MySQL篇(InnoDB的锁机制) 技术面:MySQL篇(InnoDB事务执行过程、事务隔离级别、事务并发异常) 技术面:如何解决缓存和数据库一致性的问题? 技术面:MySQL(一条SQL在MySQL的执行过程?、MyISAM和InnoDB的区别?数据库事务机制?) 技术面:MySQL篇(为啥会有非关系型数据库?MySQL的数据存储一定在磁盘吗?) 技术面:SpringCloud(SpringCloud有哪些组件,SpringCloud与Dubbo的区别) 技术面:SpringBoot(springboot的类加载和传统的双亲委派有什么区别、如何按顺序实例化Bean) 技术面:Spring(循环依赖,spring与springboot的区别) 技术面:Spring (事务传播机制、事务失效的原因、BeanFactory和FactoryBean的关系) 阿里一面:类加载的过程是怎么样的? 双亲委派的优点和缺点? 产生fullGC的情况有哪些? spring的动态代理有哪些?区别是什么? 如何排查CPU使用率过高? 技术面:Spring (bean的生命周期、创建方式、注入方式、作用域) 技术面:Java并发(线程同步、死锁、多线程编排) 技术面:Java并发(线程池、ForkJoinPool) 技术面:Java并发(上下文切换、线程安全、并发与并行、守护线程、虚拟线程) DDD领域驱动设计的理解 Kafka如何保证「消息不丢失」,「顺序传输」,「不重复消费」,以及为什么会发生重平衡(reblanace) Kafka为什么吞吐量大,速度快? Elasticsearch写入数据的过程是什么样的?以及是如何快速更新索引数据的?
技术面:SpringBoot(启动流程、如何优雅停机)
纪莫 · 2025-10-28 · via 博客园 - 纪莫

SpringBoot的启动流程

下面的代码是SpingBoot启动类里最基础的代码,SpringBoot的启动的入口就在这里,本文是在SpringBoot3的基础上进行的梳理。

@SpringBootApplication
public class Application {
	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}
}

在网上能搜到,各种博客和文章来讲解SpingBoot启动流程的,无一不是长篇大论的,把各个步骤,各个阶段执行的代码都贴出来。
确实,内容详尽固然有助于理解,但面试时只需记住主干流程即可,
谁在工作过程中,也不会无故的研究这个启动流程,心里有个大概的了解就可以了,如果实在隔得时间太长了,写代码时又真的会需要了解一下这个启动流程,那么直接在网上搜索,或者直接问AI都是可以的。

所以本次我总结这个启动流程的时候就是尽量的精简步骤,然后进行总结,让大家用自己的话,能给面试官解释清整个流程就行了。

另外还要说明一下,启动流程和实现自动装配不是一回事,启动流程里面是包括自动装配的。如果想了解SpringBoot是如何实现自动装配的,可以看之前写的文章【你来讲一下springboot的启动时的一个自动装配过程吧】

创建SpringBootApplication应用与刷新

首先最核心的一段代码SpringApplication.run(Application.class, args);这段代码会经历一系列的步骤,完成应用的初始化与启动。
这段代码的最后是会调用到SpringApplication.classConfigurableApplicationContext方法此方法里面执行的代码为(new SpringApplication(sources)).run(args),因此这个执行过程就分成的两部分,第一部分是new SpringApplication(sources)另一部分是.run(args)

public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
    return (new SpringApplication(sources)).run(args);
}

new SpringApplication(sources)

这个构造方法的代码如下图所示
在这里插入图片描述
弄明白了initialize()方法其实就可以了。

  • 添加应用源 this.sources.addAll(Arrays.asList(sources));这段代码的是添加应用源的逻辑,将提供的源(通常是配置类)添加到应用的源列表中。
  • 设置 Web 环境 this.deduceWebEnvironment();判断应用是否应该运行在 Web 环境中,这会影响后续的 Web 相关配置。
  • 加载初始化器this.getSpringFactoriesInstances(ApplicationContextInitializer.class)从 spring.factories 文件中加载所有列出的 ApplicationContextInitializer实现,并将它们设置到 SpringApplication 实例中,以便在应用上下文的初始化阶段执行它们。
  • 设置监听器this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class))加载和设置 ApplicationListener实例,以便应用能够响应不同的事件。
  • 确定主应用类this.deduceMainApplicationClass(),这个主应用程序类通常是包含 public static void main(String[]args)方法的类,是启动整个 Spring Boot 应用的入口点。

SpringApplication::run(args)

此方法就是用于启动SpringBoot应用,也是核心流程所在。
在这里插入图片描述

1、启动计时器

stopWatch.start();启动计时器,用于记录启动耗时。

2、获取和启动监听器
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();

这一步从spring.factories中解析初始所有的SpringApplicationRunListener 实例,并通知
他们应用的启动过程已经开始。

3、装配环境参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);

这一步主要是用来做参数绑定的,prepareEnvironment 方法会加载应用的外部配置。这包括application.propertiesapplication.yml 文件中的属性,环境变量,系统属性等。所以,我们自定义的那些参数就是在这一步被绑定的。

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
    ConfigurableEnvironment environment = this.getOrCreateEnvironment();
    this.configureEnvironment(environment, applicationArguments.getSourceArgs());
    listeners.environmentPrepared(environment);
    if (!this.webEnvironment) {
        environment = (new EnvironmentConverter(this.getClassLoader())).convertToStandardEnvironmentIfNecessary(environment);
    }
    return environment;
}
4、创建应用上下文
context = this.createApplicationContext();

到这一步就真的开始启动了,第一步就是先要创建一个Spring的上下文出来,只有有了这个上下文才能进行Bean的加载、配置等工作。

5、准备上下文

这一步很关键了,有很多核心操作在这里。

this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);

preareContent()方法代码如下,每一步都加上了注释说明。

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
	  // 设置环境变量
    context.setEnvironment(environment);
    // 对应用上下文进行处理,一些自定义处理逻辑也在这里
    this.postProcessApplicationContext(context);
    // 应用所有的ApplicationContentInitializer
    this.applyInitializers(context);
    // 通知监听器上下文准备完毕工作已完成。
    listeners.contextPrepared(context);
    // 记录启动信息和使用的配置文件信息
    if (this.logStartupInfo) {
        this.logStartupInfo(context.getParent() == null);
        this.logStartupProfileInfo(context);
    }

// 向应用上下文中添加指定的单例Bean    
context.getBeanFactory().registerSingleton("springApplicationArguments", applicationArguments);
    if (printedBanner != null) {
        context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
    }
		// 加载配置应用源
    Set<Object> sources = this.getSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    this.load(context, sources.toArray(new Object[sources.size()]));
    // 通知监听器上下文加载已完成
    listeners.contextLoaded(context);
}
6、刷新上下文

this.refreshContext(context);这一步,是Spring启动的核心步骤了,这一步骤包括了实例化所有的 Bean、设置它们之间的依赖关系以及执行其他的初始化任务。

public void refresh() throws BeansException, IllegalStateException {
        synchronized(this.startupShutdownMonitor) {
         // 为此刷新操作准备
         this.prepareRefresh();
         // 通知子类刷新内部bean工厂
         ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
         // 为刷新做好bean工厂的准备
         this.prepareBeanFactory(beanFactory);

         try {
         // 允许在上下文子类中对bean工厂进行处理
             this.postProcessBeanFactory(beanFactory);
             // 调用在上下文中注册为Bean的工厂处理器
             this.invokeBeanFactoryPostProcessors(beanFactory);
             // 注册拦截bean创建的bean处理器
             this.registerBeanPostProcessors(beanFactory);
             // 初始化此上下文的消息源
             this.initMessageSource();
             // 初始化上下文的的时间多播器
             this.initApplicationEventMulticaster();
             // 在特定上下文子类中初始化其他特殊Bean(tomcat等内嵌服务器也是在这一步启动)
             this.onRefresh();
             // 检测监听器Bean并,注册到容器
             this.registerListeners();
             // 实例化所有剩余的(非懒加载)单例
             this.finishBeanFactoryInitialization(beanFactory);
             // 最后一步,发布相应的事件
             this.finishRefresh();
         } catch (BeansException var9) {
             if (this.logger.isWarnEnabled()) {
                 this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
             }
						// 销毁已经创建的单利
             this.destroyBeans();
             // 重置”激活“标志
             this.cancelRefresh(var9);
             // 抛出异常
             throw var9;
         } finally {
         // 在spring的核心中重置常见的内存缓存,因为我们可能不再需要单例Bean的元数据
             this.resetCommonCaches();
         }

     }
 }
7、执行 CommandLineRunner 和 ApplicationRunner

this.afterRefresh(context, applicationArguments);执行启动参数命令或在应用完全启动后执行一些初始化逻辑,常用于数据初始化。

8、启动完成,返回 ApplicationContext

最终,run() 方法返回一个 ConfigurableApplicationContext,即 Spring 容器上下文,应用正式运行。

总结一下,可以用下面的话来回答面试:

SpringBoot启动流程从SpringApplication.run()开始,通过new SpringApplication()初始化应用源、判断Web环境、加载spring.factories中的初始化器和监听器,并确定主类;随后在run()中准备环境(加载配置)、创建ApplicationContext(如Web应用为ServletWebServerApplicationContext),通过refresh()完成Bean定义加载(含@EnableAutoConfiguration自动配置)、实例化单例Bean并启动内嵌服务器(如Tomcat),最后执行CommandLineRunner等初始化逻辑,最终返回ApplicationContext使应用运行。
在这里插入图片描述

SpringBoot如何优雅的停机

优雅停机(Graceful Shutdown)是指在应用停止时,系统能够有序地完成当前正在处理的任务,拒绝新请求,释放资源,从而避免请求中断和数据丢失,确保用户体验和数据一致性。

SpringBoot 2.3版本开始,框架原生支持优雅停机机制,这是最简单且官方推荐的实现方式。

使用方式,只需要在配置文件里面进行配置即可。

# 启用优雅停机模式
server.shutdown=graceful

# 设置等待请求完成的超时时间(默认30秒)
spring.lifecycle.timeout-per-shutdown-phase=30s

工作原理

当应用接收到停止信号(如SIGTERM)时,内嵌Web服务器(Tomcat/Jetty/Undertow)会执行以下步骤:

  • 停止接收新请求:内嵌服务器停止接收新的HTTP请求。
  • 等待处理中请求:继续处理已接收的请求,直到超时。
  • 关闭应用上下文:按顺序销毁所有Bean(执行@PreDestroy方法),停止Web服务器。
  • 释放资源:释放数据库连接、线程池等资源。

不同 web服务器,优雅停机行为会有一定的差异。

服务器 优雅停机行为
Tomcat 停止接收新连接,等待处理中的请求完成,超时后强制关闭
Jetty 停止接收新请求,等待活跃请求完成
Undertow 关闭监听端口,等待活跃请求完成

使用SpringBoot Actuator实现优雅停机

使用SpringBoot Actuator实现优雅停机,也就是需要HTTP端点触发优雅停机,需要额外配置:
pom.xml增加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

配置文件

# 启用shutdown端点
management.endpoint.shutdown.enabled=true
# 暴露shutdown端点
management.endpoints.web.exposure.include=shutdown

触发优雅停机

curl -X POST http://localhost:8080/actuator/shutdown

Kubernetes环境下的最佳实践

在Kubernetes环境中,优雅停机需要与K8s生命周期配合。
首先,配置文件里面需要进行配置

# 启用优雅停机模式
server.shutdown=graceful
# 设置等待请求完成的超时时间(默认30秒)
spring.lifecycle.timeout-per-shutdown-phase=30s

Kubernetes Deployment配置

terminationGracePeriodSeconds: 30
livenessProbe:
  httpGet:
    path: /health
    port: 8080
  initialDelaySeconds: 10
  periodSeconds: 5
readinessProbe:
  httpGet:
    path: /health
    port: 8080
  initialDelaySeconds: 10
  periodSeconds: 5

优雅停机具体的实现原理,主要有这几步

  • 信号接收阶段:通过Runtime.getRuntime().addShutdownHook()注册JVM关闭钩子,或通过Actuator端点接收停机请求
  • 应用上下文关闭阶段:发布ContextClosedEvent事件,按顺序销毁所有Bean,停止内嵌Web服务器
  • 连接器优雅停止阶段:不同服务器实现方式不同,但都确保等待处理中请求完成