【SpringBoot】优化慢启动应用的用户体验
通过深入分析SpringBoot中WebServer的启动流程,插入自定义的Loading页面展示逻辑,优化软件使用时的用户体验。
背景
Java本身的特点,再加上开发人员能力差,软件开发工程化程度低等等问题,经过一段时间的迭代之后,经常会出现的一个问题就是应用启动越来越慢。
过往我也是收集了不少相关资料,例如 云音乐服务端应用启动时间下降40%实践分享,7min到40s: SpringBoot 启动优化实践!等等。
本文跳出与上述文章类似的技术角度,借鉴Jenkins的启动流程 —— Jenkins也是Java开发的,而且其启动速度虽然经过不少优化,但依然有着非常明显的延迟,但Jenkins通过加入Loading页面的方式,极大提升了用户在等待系统就绪期间的用户体验 —— 启动之后马上呈现给用户一个Loading页面,待后台服务能够正常提供服务之后冲向到常规Index页面。
实现
Jenkins默认适用的是winstone 容器,所以我在一开始所设想的"拿来主义"被掐死在了摇篮里,最后经过一番摸索,基于SpringBoot + Undertow实现了类似的效果。
思路:
- SpringBoot项目中,单单一个WebServer的启动是很快的,只是SpringBoot的启动流程里,其会在WebServer启动之前初始化所有的Bean,而这是整个启动流程里最耗时的。
- 所以,我们将在SpringBoot容器中的Bean实例化之前启动我们自定义的"minimal Undertow Server"来向用户提供对于 loading页面的响应。
- 待Spring容器准备进行WebServer的启动时,停止我们的"minimal Undertow Server",以避免端口占用。
- 前端loading页面将定期轮询后端服务启动情况,待其正常响应时跳转到相应的主体服务页面。
直接上代码:
// ====================== 1. 应用启动入口类// 应用启动入口public static void main(String[] args) {final SpringApplication springApplication = new SpringApplication(SpringBootTestApplication.class);// 读取用户配置, 决定启动方式final Boolean humanableStart = Convert.toBool(CommonUtil.getProperty("START_HUMANABLE", "false"));if (humanableStart) {springApplication.setApplicationContextClass(AnnotationConfigServletWebServerApplicationContextEx.class); // 自定义}// 启动Spring容器springApplication.run(args);}// ======================== 2. SpringBoot不同版本实现方式不同
// 这里以 SpringBoot 2.3.3为例, 更高版本需要实现 ApplicationContextFactory 接口
class AnnotationConfigServletWebServerApplicationContextWithHumanableStart extends AnnotationConfigServletWebServerApplicationContext {Undertow minimalUndertowserver;@Overrideprotected void prepareRefresh() {// 尽量提前"minimal Undertow Server"的创建, 优化用户体验.String property = this.getEnvironment().getProperty("server.port");minimalUndertowserver = minimalUndertowserver(Convert.toInt(property));super.prepareRefresh();}@Overrideprotected void onRefresh() {// ServletWebServerApplicationContext正是通过覆写本方法来实现 WebServer 创建的// 同时会向容器中注入webServerStartStop Bean,借助Spring的生命周期回调接口SmartLifecycle来负责将webServer的开启和关闭;super.onRefresh();}@Overrideprotected void finishRefresh() {// 关键流程: AbstractApplicationContext.refresh()// super.finishRefresh()中将触发 WebServerStartStopLifecycle.start() 以启动webserver, 所以我们得在它之前将我们的轻量级webserver关闭掉.minimalUndertowserver.stop();super.finishRefresh();}static Undertow minimalUndertowserver(int port) {// 这个loading.html是从jenkins里扒过来了, 也算是实现了部分"拿来主义"final String loadingHtml = ResourceUtil.readStr("static/loading.html", CharsetUtil.CHARSET_UTF_8);// Start the minimal Undertow serverUndertow undertow = Undertow.builder().addHttpListener(port, "0.0.0.0").setHandler(new HttpHandler() {@Overridepublic void handleRequest(HttpServerExchange exchange) throws Exception {exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/html");exchange.getResponseSender().send(loadingHtml);}}).build();undertow.start();return undertow;}
}
原理分析
Spring容器之所以启动慢,主要原因肯定就是各类Bean的实例化耗时叠加
// AbstractApplicationContext.java
// Spring核心启动流程
@Override
public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {// Prepare this context for refreshing.prepareRefresh(); // 我们在这里启动 minimal Undertow Server// Tell the subclass to refresh the internal bean factory.ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// Prepare the bean factory for use in this context.prepareBeanFactory(beanFactory);try {// Allows post-processing of the bean factory in context subclasses.postProcessBeanFactory(beanFactory);// Invoke factory processors registered as beans in the context.invokeBeanFactoryPostProcessors(beanFactory);// Register bean processors that intercept bean creation.registerBeanPostProcessors(beanFactory);// Initialize message source for this context.initMessageSource();// Initialize event multicaster for this context.initApplicationEventMulticaster();// Initialize other special beans in specific context subclasses.onRefresh();// Check for listener beans and register them.registerListeners();// Instantiate all remaining (non-lazy-init) singletons.finishBeanFactoryInitialization(beanFactory);// Last step: publish corresponding event.finishRefresh(); // 在这里关闭 minimal Undertow Server.} catch (BeansException ex) {if (logger.isWarnEnabled()) {logger.warn("Exception encountered during context initialization - " +"cancelling refresh attempt: " + ex);}// Destroy already created singletons to avoid dangling resources.destroyBeans();// Reset 'active' flag.cancelRefresh(ex);// Propagate exception to caller.throw ex;}finally {// Reset common introspection caches in Spring's core, since we// might not ever need metadata for singleton beans anymore...resetCommonCaches();}}
}
相关
- Jenkins源码 - loading页面
- Jenkins源码 - loading后端代码
- GitHub - jenkinsci-winstone
相关文章:

【SpringBoot】优化慢启动应用的用户体验
通过深入分析SpringBoot中WebServer的启动流程,插入自定义的Loading页面展示逻辑,优化软件使用时的用户体验。 背景 Java本身的特点,再加上开发人员能力差,软件开发工程化程度低等等问题,经过一段时间的迭代之后&…...

String str=“i“ 与 String str=new String (“i“) 一样吗?
String str"i" 与 String strnew String ("i") String str"i" 这种形式声明了一个 String 对象,其中 "i" 字符串字面量直接赋值给 str。在 Java 中,字符串字面量会自动放入字符串常量池中,这是一个…...

【数据结构】二叉树链式结构的实现
前置声明:在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。由于现在大家对二叉树结构掌握还不够深入,为了降低大家学习成本,此处手动快速创建一棵简单的二叉树,快速进入二叉…...

如何有效找到目标客户群体?
在激烈的市场竞争中,找到并锁定目标客户群体是企业成功的关键。以下是几种有效的策略,帮助您精准定位并吸引目标客户。 1. 明确市场定位与客户画像 首先,企业需要明确市场定位,并绘制详细的客户画像,包括年龄、性别、…...

机器学习-混淆矩阵
文章目录 一、混淆矩阵1.混淆矩阵简介2.混淆矩阵图列 二、混淆矩阵指标1. 准确率(Accuracy)2. 精确率(Precision)3. 召回率(Recall)4. F1分数(F1 Score) 三、总结 一、混淆矩阵 1.混…...

数据结构----栈
一丶概念 只能在一端进行插入和删除操作的线性表(又称为堆栈),进行插入和删除操作的一端称为栈顶,另一端称为栈底 二丶特点 先进后出 FILO first in last out 后进先出 LIFO last in first out 三丶顺序栈 逻辑结构&…...

STL六大组件
STL(Standard Template Library,标准模板库)是C标准库的一部分,提供了丰富且高效的数据结构和算法。STL主要由6大组件构成,分别是容器、算法、迭代器、适配器、仿函数和空间配置器。 容器(Containers&#…...

【机器学习】CNN的数学基础
🌈个人主页: 鑫宝Code 🔥热门专栏: 闲话杂谈| 炫酷HTML | JavaScript基础 💫个人格言: "如无必要,勿增实体" 文章目录 CNN的数学基础1. 引言2. 卷积运算2.1 连续卷积2.2 离散卷积2.3 互相关 3. 激活函…...

最小路径和[中等]
优质博文:IT-BLOG-CN 一、题目 给定一个包含非负整数的m x n网格grid,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。 说明:每次只能向下或者向右移动一步。 示例 1: 输入:grid [[…...

【题库】——数组 小鱼比可爱
#include<bits/stdc.h> using namespace std; int main() {int n,m,i;cin>>n;int arr[n]; for(i0;i<n;i) {int count 0;cin>>arr[i];for(mi;m>0;m--){if(arr[i]>arr[m])count;} cout<<count<<" "; } return 0; }...

基于飞腾平台的Hbase的安装配置
【写在前面】 飞腾开发者平台是基于飞腾自身强大的技术基础和开放能力,聚合行业内优秀资源而打造的。该平台覆盖了操作系统、算法、数据库、安全、平台工具、虚拟化、存储、网络、固件等多个前沿技术领域,包含了应用使能套件、软件仓库、软件支持、软件适…...

【springboot】springboot接口参数全局解密,解决request内容修改后如何重新设置回去的问题
文章目录 核心思路spring&servelt基础核心接口类核心代码 body解密核心原理讲解get解密核心原理讲解get query请求讲解get pathVariables请求讲解 总结 本文不仅介绍了body内容修改后如何传递,也介绍了get请求 在修改内容后如何继续传递。 【原创作者 csdn: 孟秋…...

yml基本语法
YAML(YAML Ain’t Markup Language)是一种简洁且易读的数据序列化格式,常用于配置文件。Spring Boot 中的 application.yml 文件使用 YAML 来配置应用程序的属性。 YAML 基本语法 1. 键值对 基本的键值对表示形式为:key: value…...

橙色简洁大气体育直播自适应模板赛事直播门户自适应网站源码
源码名称:酷黑简洁大气体育直播自适应模板赛事直播门户网站 源码开发环境:帝国cms 7.5 安装环境:phpmysql 带采集,可以挂着电脑上自动采集发布,无需人工操作! 橙色简洁大气体育直播自适应模板赛事直播门户…...

【启明智显技术分享】工业级HMI芯片Model系列GUI合成到项目中的指南
在工业自动化、智能终端HMI、车载仪表盘等领域,高性能的HMI(人机界面)芯片是不可或缺的核心组件。启明智显推出的Model系列(如Model3C、Model3、Model4)HMI芯片,以其卓越的性能和广泛的应用领域,…...

开源服务器运维工具1Panel
1Panel是杭州飞致云信息科技有限公司推出的一款现代化、开源的Linux服务器运维管理面板。 以下是对1Panel的详细介绍: 一、基本信息 产品名称:1Panel所属公司:杭州飞致云信息科技有限公司编写语言:Golang上线时间:20…...

新版本源2.0大模型发布:Yuan2-2B-July-hf
引言 近日,浪潮信息的新一代基础语言大模型源2.0 迎来了重要更新。浪潮信息正式发布了 Yuan2-2B-July-hf 模型,标志着源2.0系列模型在性能和功能上的进一步提升。这一版本将为开发者和研究人员提供更强大的工具,以满足各种语言处理需求。…...

用python生成GIF动图—用于博客插图或封面等
生成GIF动图🚀 由于目前自己是在做大模型,还有一些树莓派硬件之类的东西,一是大模型的流式输出的例子需要用到GIF,二是做单片机的时候例如一些灯的闪烁和变化需要用到,所以之前也是一直有这个打算所以就记录一下这个生…...

[RCTF2019]draw
下载是一个文本文档,百度AI cs pu lt 90 fd 500 rt 90 pd fd 100 rt 90 repeat 18[fd 5 rt 10] lt 135 fd 50 lt 135 pu bk 100 pd setcolor pick [ red orange yellow green blue violet ] repeat 18[fd 5 rt 10] rt 90 fd 60 rt 90 bk 30 rt 90 fd 60 pu lt 90 f…...

设计模式 - 责任链模式
💝💝💝首先,欢迎各位来到我的博客!本文深入理解设计模式原理、应用技巧、强调实战操作,提供代码示例和解决方案,适合有一定编程基础并希望提升设计能力的开发者,帮助读者快速掌握并灵活运用设计模式。 💝💝💝如有需要请大家订阅我的专栏【设计模式】哟!我会定…...

jpg怎么转换成pdf?6个简单方法,实现jpg转换成pdf
你是否也曾想将jpg图片转换为pdf格式文档呢?亦或者在处理文档或制作报告时,不知道怎么才能更快地将多张图片整合成一个pdf文件呢?如果你正在寻找简单快速的方法,又有哪些工具可以帮助您完成图片转pdf呢?别着急…...

ptrade排坑笔记——使用量化交易的时候有报错提示!
前言 今天要和大家分享一个遇见的问题,有客户反馈,自己在使用量化交易的时候,会有报错!会在后文分享我们是如何解决这个问腿的! 一、问题描述 客户主要遇见的问题是,量化在进行交易的过程中,…...

C#-MemoryMarshal
MemoryMarshal 类是 .NET 中用于处理内存的工具类,它提供了一组静态方法,用于在托管代码中以安全和高效的方式操作内存块。MemoryMarshal 类主要用于处理原始内存数据而不需要进行复制,这对于性能关键的操作非常有用。 MemoryMarshal 类包含…...

Java并发编程的艺术
Java作为一门面向对象的编程语言,自1995年推出以来,一直以其稳定性、跨平台性和丰富的API受到广大开发者的喜爱。在Java的发展历程中,并发编程一直是其重要的特性之一。本文将探讨Java并发编程的艺术,解析其核心概念和常用并发工具…...

华为 OLT 添加 ONU 配置 (SNMP管理模式)
上网业务数据规划 OLT PON口 0/8/0 ONU_ID 0 ONU 序列号 4857544323BE233B 外层 VLAN ID 2012 内层VLAN ID 35 用户 FE 端口 ONU 0/1/1 用户VLAN 35 DBA带宽类型 Type 2 流量模板编号 10 DBA 模板编号 30 ONU线路模板编号 40 T-CONT (网管) 0 T-CONT(业务_ 2 GEM (网管) 0 …...

【JavaScript】[]和{} 的转换
背景 ([])? true:false ({})? true:false ([] true)? true:false ({} true)? true:false ([] true)? true:false ({} true)? true:false分析 [ ]和{ } 都是复杂类型,以上都是三目运算符判断 1.判断[ ]和{ } 是否存在 声明了这些已经分配了内存…...

C#关于多线程的线程问题
using System.Text; namespace 平时练习8._19day06 {internal class Program{static async Task Main(string[] args){Console.WriteLine(Thread.CurrentThread.ManagedThreadId );StringBuilder sb new StringBuilder();for (int i 0; i < 10000; i){sb.Append("…...

eclipse打开失败 java was started but returned exit code=13
报错详细信息如下 原因:eclipse版本和jdk版本不一致。系统之前jdk是1.6,然后安装1.8之后默认修改了环境变量。导致eclipse启动失败 解决方案:修改eclipse目录下的eclipse.ini文件增加一下内容。文档说明:eclipse.ini - Eclipsepe…...

【计算机网络】应用层自定义协议与序列化
记得在上一节我们说过TCP中的读取时需要改进,这节就可以解决读取问题了。 目录 应用层再谈 "协议"网络版计算机方案一方案二 序列化 和 反序列化 重新理解 read、write、recv、send 和 tcp 为什么支持全双工 应用层 再谈 “协议” 我们在UDP与TCP中写的…...

企业级无线局域网(WLAN)架构:高效部署策略与技术指南
前言:无线网络直接影响整体网络性能,在当今企业网环境中,已有超过一半的数据流量通过无线信道传输,随着物联网技术的普及,无线网将承载更多的关键业务流量。企业/园区场景的无线网络值得考虑的关键因素有很多ÿ…...