当前位置: 首页 > news >正文

技术面:SpringBoot(启动流程、如何优雅停机)

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);// 应用所有的ApplicationContentInitializerthis.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: /healthport: 8080initialDelaySeconds: 10periodSeconds: 5
readinessProbe:httpGet:path: /healthport: 8080initialDelaySeconds: 10periodSeconds: 5

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

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

相关文章:

  • 网站建设软硬件平台有哪些郑州铭功路网站建设
  • 青岛网站建设方案优化网络实施方案怎么写
  • 网站在线留言设计师培训经历怎么写
  • 建各公司网站要多少钱互联网app开发
  • 网站建设费用做做什么科目价格划算的常州做网站
  • 怎么用小米音箱控制HA打开MQTT协议的智能开关?
  • 宁波网站优化体验wordpress密码保护文章
  • 兰州网络营销网站电子商务平台经营者的特点体现在
  • 网站语音转写怎么做wordpress主题 收费
  • 【UI自动化相关】
  • 有限公司网站建设 中企动力重庆搜狗短网址生成
  • 网站建设公司哪家中国建设网官方网站6
  • 我要外包网站网站建设怎么设置权限
  • 个人网站seowordpress 传值
  • 网站打开是目录结构图广东网页空间租赁
  • 游戏事件中心:解耦模块的利器
  • 高端网站建设的流程是什么产品推广介绍
  • php网站源码删除手机网站seo怎么做
  • 网站完成上线时间js怎么做网站
  • 建门户网站要多少钱找兼职做网站建设
  • 跨境电商网站开发文档修改wordpress用户名密码忘记
  • 山东烟台城乡建设学校官方网站建设行政主管部门政务网站
  • 声-振物理信息约束引导深度学习的球磨机滚动轴承故障诊断方法
  • VB.NET 数据库查询与界面交互
  • 个人网站创建平台要多少钱wordpress中的template
  • KingbaseES数据库操作指南(1):SQL语法从入门到精通
  • 网站建设术语阿里云服务器价格
  • 网站版面如何设计Wordpress页面图片设计
  • 网站建设的seo策略crm和scrm有什么区别
  • 杭州集团网站建设方案深圳专业app网站开发