spring 启动进度_在Web浏览器中显示Spring应用程序启动的进度

spring 启动进度

重新启动企业应用程序时,客户打开Web浏览器时会看到什么?

  1. 他们什么也没看到,服务器还没有响应,因此Web浏览器显示ERR_CONNECTION_REFUSED
  2. 应用程序前面的Web代理(如果有)注意到它已关闭,并显示“友好”错误消息
  3. 该网站需要永久加载-它接受了套接字连接和HTTP请求,但是等待响应,直到应用程序实际启动
  4. 您的应用程序进行了扩展,以便其他节点可以快速接收请求,而不会有人通知(并且会话始终得以复制)
  5. …或应用程序启动速度如此之快,以至于没有人注意到任何中断(嘿,普通的Spring Boot Hello world应用程序从点击java -jar ... [Enter]开始服务请求不到3秒)。 顺便说一句,请检出SPR-8767:启动过程中并行bean初始化

处于情况4和5.绝对更好。但是在本文中,我们将介绍对情况1和3的更强大的处理。

典型的Spring Boot应用程序会在所有Bean都加载完毕时(状态1),在最后启动Web容器(例如Tomcat)。这是一个非常合理的默认值,因为它会阻止客户端在完全配置之前无法访问我们的端点。 但是,这意味着我们无法区分启动了几秒钟的应用程序和关闭了的应用程序。 因此,其想法是要使一个应用程序在加载时显示一些有意义的启动页面,类似于显示“ 服务不可用 ”的Web代理。 但是,由于此类启动页面是我们应用程序的一部分,因此它可能会更深入地了解启动进度。 我们希望在初始化生命周期中更早地启动Tomcat,但要保留特殊目的的启动页面,直到Spring完全引导为止。 这个特殊页面应该拦截所有可能的请求-因此听起来像一个servlet过滤器。

渴望并尽早启动Tomcat。

在Spring启动servlet容器通过初始化EmbeddedServletContainerFactory创建的实例EmbeddedServletContainer 。 我们有机会使用EmbeddedServletContainerCustomizer截获此过程。 容器是在应用程序生命周期的早期创建的,但是在整个上下文完成后才开始创建。 所以我想我将只在自己的定制器中调用start()就是这样。 不幸的是ConfigurableEmbeddedServletContainer没有公开这样的API,所以我不得不像这样装饰EmbeddedServletContainerFactory

class ProgressBeanPostProcessor implements BeanPostProcessor {//...@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {if (bean instanceof EmbeddedServletContainerFactory) {return wrap((EmbeddedServletContainerFactory) bean);} else {return bean;}}private EmbeddedServletContainerFactory wrap(EmbeddedServletContainerFactory factory) {return new EmbeddedServletContainerFactory() {@Overridepublic EmbeddedServletContainer getEmbeddedServletContainer(ServletContextInitializer... initializers) {final EmbeddedServletContainer container = factory.getEmbeddedServletContainer(initializers);log.debug("Eagerly starting {}", container);container.start();return container;}};}
}

您可能会认为BeanPostProcessor是一个过大的功能,但是稍后它将变得非常有用。 我们在这里所做的是,如果遇到从应用程序上下文中被请求的EmbeddedServletContainerFactory ,我们将返回一个装饰器,该装饰器急切地启动Tomcat。 这给我们带来了相当不稳定的设置,即Tomcat接受到尚未初始化的上下文的连接。 因此,让我们放置一个servlet过滤器来拦截所有请求,直到上下文完成为止。

启动期间拦截请求

我仅通过将FilterRegistrationBean添加到Spring上下文开始,希望它会拦截传入的请求,直到上下文启动为止。 这是徒劳的:我不得不等待很长时间才能注册过滤器并准备好,因此从用户的角度来看,应用程序已挂起。 后来我什至尝试使用Servlet API( javax.servlet.ServletContext.addFilter() )在Tomcat中直接注册过滤器,但显然必须预先引导整个DispatcherServlet 。 记住,我想要的只是来自即将初始化的应用程序的快速反馈。 因此,我最终得到了Tomcat的专有API: org.apache.catalina.ValveValve与Servlet过滤器类似,但它是Tomcat体系结构的一部分。 Tomcat自己捆绑了多个阀门,以处理各种容器功能,例如SSL,会话群集和X-Forwarded-For处理。 Logback Access也使用此API,因此我不会感到内。 阀门看起来像这样:

package com.nurkiewicz.progress;import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.valves.ValveBase;
import org.apache.tomcat.util.http.fileupload.IOUtils;import javax.servlet.ServletException;
import java.io.IOException;
import java.io.InputStream;public class ProgressValve extends ValveBase {@Overridepublic void invoke(Request request, Response response) throws IOException, ServletException {try (InputStream loadingHtml = getClass().getResourceAsStream("loading.html")) {IOUtils.copy(loadingHtml, response.getOutputStream());}}
}

阀门通常委托给链中的下一个阀门,但是这次我们只为每个单个请求返回static loading.html页面。 注册这样的阀门非常简单,Spring Boot为此提供了一个API!

if (factory instanceof TomcatEmbeddedServletContainerFactory) {((TomcatEmbeddedServletContainerFactory) factory).addContextValves(new ProgressValve());
}

定制阀门原来是个好主意,它从Tomcat立即开始并且非常易于使用。 但是,您可能已经注意到,即使在应用程序启动后,我们也不会放弃提供loading.html 。 那很糟。 Spring上下文可以通过多种方式发出初始化信号,例如,使用ApplicationListener<ContextRefreshedEvent>

@Component
class Listener implements ApplicationListener<ContextRefreshedEvent> {private static final CompletableFuture<ContextRefreshedEvent> promise = new CompletableFuture<>();public static CompletableFuture<ContextRefreshedEvent> initialization() {return promise;}public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {return bean;}@Overridepublic void onApplicationEvent(ContextRefreshedEvent event) {promise.complete(event);}}

我知道您的想法是“ static ”吗? 但是在Valve内部,我根本不想接触Spring上下文,因为如果我在错误的时间点从随机线程请​​求某个bean,它可能会引入阻塞甚至死锁。 完成promiseValve注销自身:

public class ProgressValve extends ValveBase {public ProgressValve() {Listener.initialization().thenRun(this::removeMyself);}private void removeMyself() {getContainer().getPipeline().removeValve(this);}//...}

这是一个令人惊讶的干净解决方案:当不再需要Valve我们无需从处理管道中删除它,而不必为每个请求支付费用。 我不会演示它如何工作以及为什么起作用,让我们直接转到目标解决方案。

监控进度

监视Spring应用程序上下文启动的进度非常简单。 另外,与EJB或JSF等API和规范驱动的框架相比,Spring框架的“可破解性”也让我感到惊讶。 在Spring中,我可以简单地实现BeanPostProcessor ,以通知每个正在创建和初始化的bean( 完整的源代码 ):

package com.nurkiewicz.progress;import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import rx.Observable;
import rx.subjects.ReplaySubject;
import rx.subjects.Subject;class ProgressBeanPostProcessor implements BeanPostProcessor, ApplicationListener<ContextRefreshedEvent> {private static final Subject<String, String> beans = ReplaySubject.create();public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {beans.onNext(beanName);return bean;}@Overridepublic void onApplicationEvent(ContextRefreshedEvent event) {beans.onCompleted();}static Observable<String> observe() {return beans;}
}

每次初始化新bean时,我都会将其名称发布到RxJava的可观察对象中。 整个应用程序初始化后,我完成了Observable 。 任何人都可以使用此Observable ,例如我们的自定义ProgressValve ( 完整的源代码 ):

public class ProgressValve extends ValveBase {public ProgressValve() {super(true);ProgressBeanPostProcessor.observe().subscribe(beanName -> log.trace("Bean found: {}", beanName),t -> log.error("Failed", t),this::removeMyself);}@Overridepublic void invoke(Request request, Response response) throws IOException, ServletException {switch (request.getRequestURI()) {case "/init.stream":final AsyncContext asyncContext = request.startAsync();streamProgress(asyncContext);break;case "/health":case "/info":response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);break;default:sendHtml(response, "loading.html");}}//...}

ProgressValve现在变得更加复杂,我们还没有完成。 它可以处理多个不同的请求,例如,我有意在/health/info Actuator端点上返回503,以便该应用程序看起来像在启动期间处于关闭状态。 除了所有其他请求init.stream表明熟悉loading.html/init.stream是特殊的。 这是服务器发送的事件端点,它将在每次初始化新bean时推送消息(很抱歉,上面没有代码):

private void streamProgress(AsyncContext asyncContext) throws IOException {final ServletResponse resp = asyncContext.getResponse();resp.setContentType("text/event-stream");resp.setCharacterEncoding("UTF-8");resp.flushBuffer();final Subscription subscription = ProgressBeanPostProcessor.observe().map(beanName -> "data: " + beanName).subscribeOn(Schedulers.io()).subscribe(event -> stream(event, asyncContext.getResponse()),e -> log.error("Error in observe()", e),() -> complete(asyncContext));unsubscribeOnDisconnect(asyncContext, subscription);
}private void complete(AsyncContext asyncContext) {stream("event: complete\ndata:", asyncContext.getResponse());asyncContext.complete();
}private void unsubscribeOnDisconnect(AsyncContext asyncContext, final Subscription subscription) {asyncContext.addListener(new AsyncListener() {@Overridepublic void onComplete(AsyncEvent event) throws IOException {subscription.unsubscribe();}@Overridepublic void onTimeout(AsyncEvent event) throws IOException {subscription.unsubscribe();}@Overridepublic void onError(AsyncEvent event) throws IOException {subscription.unsubscribe();}@Overridepublic void onStartAsync(AsyncEvent event) throws IOException {}});
}private void stream(String event, ServletResponse response) {try {final PrintWriter writer = response.getWriter();writer.println(event);writer.println();writer.flush();} catch (IOException e) {log.warn("Failed to stream", e);}
}

这意味着我们可以使用简单的HTTP接口(!)来跟踪Spring应用程序上下文启动的进度:

$ curl -v localhost:8090/init.stream
> GET /init.stream HTTP/1.1
> User-Agent: curl/7.35.0
> Host: localhost:8090
> Accept: */*< HTTP/1.1 200 OK
< Content-Type: text/event-stream;charset=UTF-8
< Transfer-Encoding: chunkeddata: org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration$EmbeddedTomcatdata: org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration$TomcatWebSocketConfigurationdata: websocketContainerCustomizerdata: org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfigurationdata: toStringFriendlyJsonNodeToStringConverterdata: org.hibernate.validator.internal.constraintvalidators.bv.NotNullValidatordata: serverPropertiesdata: org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration...data: beanNameViewResolverdata: basicErrorControllerdata: org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration$JpaWebConfiguration$JpaWebMvcConfiguration

该端点将实时初始化(请参阅: 使用RxJava和SseEmitter的服务器发送的事件 )每个初始化的单个bean名称。 有了如此出色的工具,我们将建立更强大的( React性的 ,在这里,我说过) loading.html页面。

花式进度前端

首先,我们需要确定哪些Spring bean代表了系统中的哪些子系统 ,高级组件(甚至可能是有限的上下文 )。 我使用data-bean自定义属性在HTML内部对此进行了编码:

<h2 data-bean="websocketContainerCustomizer" class="waiting">Web socket support
</h2><h2 data-bean="messageConverters" class="waiting">Spring MVC
</h2><h2 data-bean="metricFilter" class="waiting">Metrics
</h2><h2 data-bean="endpointMBeanExporter" class="waiting">Actuator
</h2><h2 data-bean="mongoTemplate" class="waiting">MongoDB
</h2><h2 data-bean="dataSource" class="waiting">Database
</h2><h2 data-bean="entityManagerFactory" class="waiting">Hibernate
</h2>

CSS class="waiting"表示给定的模块尚未初始化,即给定的bean尚未出现在SSE流中。 最初,所有组件都处于"waiting"状态。 然后,我订阅init.stream并更改CSS类以反映模块状态更改:

var source = new EventSource('init.stream');
source.addEventListener('message', function (e) {var h2 = document.querySelector('h2[data-bean="' + e.data + '"]');if(h2) {h2.className = 'done';}
});

简单吧? 显然,无需使用jQuery就可以在纯JavaScript中编写前端。 加载所有bean后, Observable在服务器端event: complete ,SSE发出event: complete ,让我们处理一下:

source.addEventListener('complete', function (e) {window.location.reload();
});

因为前端是在应用程序上下文启动时通知的,所以我们可以简单地重新加载当前页面。 那时,我们的ProgressValve已经注销,因此重新加载将打开真实的应用程序,而不是loading.html占位符。 我们的工作完成了。 另外,我还计算了启动的bean数量,并知道总共有多少bean(我用JavaScript对其进行了硬编码,请原谅),我可以用百分比来计算启动进度。 图片值一千个字,让此截屏视频向您展示我们取得的成果:

后续模块启动良好,我们不再关注浏览器错误。 以百分比衡量的进度使整个启动进度感觉非常顺利。 最后但并非最不重要的一点是,当应用程序启动时,我们将自动重定向。 希望您喜欢这个概念证明,整个工作示例应用程序都可以在GitHub上找到。

翻译自: https://www.javacodegeeks.com/2015/09/displaying-progress-of-spring-application-startup-in-web-browser.html

spring 启动进度

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/337263.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言内存泄漏问题及其检视方法

点击蓝字关注我们来源于网络&#xff0c;侵删通过介绍内存泄漏问题原理及检视方法&#xff0c;希望后续能够从编码检视环节就杜绝内存泄漏导致的网上问题发生。本文通过介绍内存泄漏问题原理及检视方法&#xff0c;希望后续能够从编码检视环节就杜绝此类问题发生。预防内存泄漏…

未定义与 struct 类型的输入参数相对应的函数 fetch_引入鲁棒性作为连续参数,这种新损失函数实现了自适应、随时变换...

编辑&#xff1a;陈萍损失函数是机器学习里最基础也是最为关键的一个要素&#xff0c;其用来评价模型的预测值和真实值不一样的程度。最为常见的损失函数包括平方损失、指数损失、log 对数损失等损失函数。这里回顾了一种新的损失函数&#xff0c;通过引入鲁棒性作为连续参数&a…

清理jdk注册表_JDK 9早期版本安装后的Windows注册表清理

清理jdk注册表在我的上一篇博文中 &#xff0c;我演示了在安装早期版本的JDK 9&#xff08;内部版本68&#xff09;之后围绕Oracle Java符号链接 &#xff08;基于Windows的计算机上的C:\ProgramData\Oracle\Java\javapath\目录&#xff09;的问题的解决方案。这似乎阻止了早期…

汇编语言调用C语言/C++实例:乘法表

点击蓝字关注我们来源于网络&#xff0c;侵删现在编写一个简单的应用程序&#xff0c;提示用户输入整数&#xff0c;通过移位的方式将其与 2 的幕 (2〜2ⁿ) 相乘&#xff0c;并用填充前导空格的形式再次显示每个乘积。输入-输出使用 C。汇编模块将调用 3 个 C 编写的函数。程序…

rect函数_R函数不会写,quot;抄quot;总会吧!

前面我们简单的介绍了R函数。有些人可能会说&#xff0c;我现在的R水平有限&#xff0c;还不足以写出很高级的函数&#xff0c;该怎么办&#xff1f;俗话说前人栽树后人乘凉&#xff0c;他山之石可以攻玉&#xff0c;鲁迅同志也提出过“拿来”主义。已经有前人&#xff0c;高手…

rest服务swagger_使用Swagger轻松记录您的Play Framework REST API

rest服务swagger该帖子最初在http&#xff1a;// swag ger.io&#xff08;7/30/2015&#xff09;上发布 我一直在使用Play Framework作为多个项目的基于Java的&#xff0c;闪电般的REST后端框架。 后来&#xff0c;我很高兴找到Swagger&#xff0c;并努力将其集成到几个项目中…

10个超赞的C语言开源项目,强烈推荐!

点击蓝字关注我们来源于网络&#xff0c;侵删今天分享10个超赞的C语言开源项目&#xff0c;希望这些内容能对大家有所帮助&#xff01;目录&#xff1a;1. Webbench2. Tinyhttpd3. cJSON4. CMockery5. Libev6. Memcached7. Lua8. SQLite9. UNIX v610. NETBSD1. WebbenchWebbenc…

python使用欧氏距离knn_python运用sklearn实现KNN分类算法

KNN(K-Nearest-Neighbours Classiflication)分类算法&#xff0c;供大家参考&#xff0c;具体内容如下最简单的分类算法&#xff0c;易于理解和实现实现步骤&#xff1a;通过选取与该点距离最近的k个样本&#xff0c;在这k个样本中哪一个类别的数量多&#xff0c;就把k归为哪一…

jboss性能指标_JBoss BRMS复杂事件处理(CEP)性能基准

jboss性能指标技术来了又去&#xff0c;但是一件事保持不变。 在设计企业解决方案时&#xff0c;我们喜欢使我们的生活更轻松的复杂组件&#xff0c;作为建筑师和开发人员&#xff0c;我们一直在寻找使我们的生活更轻松的方法。 一种方法是跟上与感兴趣的技术有关的流行新站点…

C语言经验分享:二维指针与二维数组的两种错误用法

点击蓝字关注我们来源于网络&#xff0c;侵删引子首先看一段代码:void test(int *p) {}int main() {int arr[] {30, 450,14,5};test(arr);return 0; }毫无疑问&#xff0c;上面这段代码是运行OK的。因为C语言标准中有以下规则:在函数参数的声明中&#xff0c;数组名被编译器当作…

word 公式编号 右侧对齐_写论文要求公式居中编号右对齐的方法

非常实用&#xff0c;推荐给大家1&#xff0c;输入公式和序号&#xff0c;公式和序号之间加入“#”符号2.把光标放到公式的最后&#xff08;一定要在公式内&#xff09;&#xff0c;然后按下“enter”键&#xff0c;即可完成公式居中并且序号自动对齐最右方的操作。

camel 使用_使用Camel从WildFly 8向WebLogic 12发送JMS消息

camel 使用系统集成是一个很好的挑战。 特别是当您在寻找通信标准和可靠的解决方案时。 在当今的微服务世界中&#xff0c;每个人都在谈论REST服务和基于http的协议。 实际上&#xff0c;对于大多数通常具有更复杂的需求集的大多数企业项目来说&#xff0c;这是远远不够的。 合…

C++异常处理控制流下的OLLVM混淆

点击蓝字关注我们来源于网络&#xff0c;侵删Inflated!!!C异常化处理OLLVM-控制流平坦化Two PuzzlesException一般碰到C异常逆向&#xff0c;确定了异常分发、处理部分&#xff0c;直接把call throw改为jmp catch块&#xff0c;再F5即可。PS: 多个catch块根据rdx来当为异常处理…

【微服务】springboot整合kafka-stream使用详解

目录 一、前言 二、kafka stream概述 2.1 什么是kafka stream 2.2 为什么需要kafka stream 2.2.1 对接成本低 2.2.2 节省资源 2.2.3 使用简单 2.3 kafka stream特点 2.4 kafka stream中的一些概念 2.5 Kafka Stream应用场景 三、环境准备 3.1 搭建zk 3.1.1 自定义d…

maven项目 jetty_如何使用Java,Maven,Jetty创建Web应用程序项目

maven项目 jetty在本文中&#xff0c;我们使用Maven Archetype插件创建一个简单的Web应用程序。 我们将在一个名为Jetty的Servlet容器中运行此Web应用程序&#xff0c;添加一些依赖项&#xff0c;编写简单的Servlet&#xff0c;并生成WAR文件。 在本文的结尾&#xff0c;您还可…

如何解决python中编码错误的问题_【总结】Python 2.x中常见字符编码和解码方面的错误及其解决办法...

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼对于Python解析器而Python解析器所干的事情&#xff0c;就是&#xff1a;Python解析器&#xff0c;根据当前的所用的字符串编码类型此字符串编码类型&#xff0c;是你自己所设置的不论是在Python的IDLE中&#xff0c;还是Python文件…

C语言知识总结一:C语言的基本知识汇总

点击蓝字关注我们来源于网络&#xff0c;侵删C语言是一种计算机程序设计语言。它既有高级语言的特点&#xff0c;又具有汇编语言的特点。它可以作 为系统设计语言&#xff0c;编写工作系统应用程序&#xff0c;也可以作为应用程序设计语言&#xff0c;编写不依赖计算机 硬件的应…

jboss8日志级别设置_罐中研讨会:设置JBoss BPM Suite全日研讨会

jboss8日志级别设置是否在寻找一种简单的方法来宣传&#xff0c;展示或演示JBoss业务流程管理套件&#xff08;BPM Suite&#xff09;产品的入门难度&#xff1f; 别无所求&#xff0c;因为我们已经召集了这个研讨会&#xff0c;因此您可以围绕JBoss BPM Suite构建一个晚上&a…

yapi 接口文档_1分钟docker部署顶尖 API 文档管理系统

YApi 是高效、易用、功能强大的 api 管理平台&#xff0c;旨在为开发、产品、测试人员提供更优雅的接口管理服务。可以帮助开发者轻松创建、发布、维护 API&#xff0c;YApi 还为用户提供了优秀的交互体验&#xff0c;开发人员只需利用平台提供的接口数据写入工具以及简单的点击…

微软CTO建议业界弃用C/C++采用Rust,遭C++之父回怼

点击蓝字关注我们来源于网络&#xff0c;侵删近日&#xff0c;Microsoft Azure CTO、Sysinternals 的主要开发者 Mark Russinovich 在其社交账号上发布动态称&#xff0c;开发人员是时候停止使用 C/C 来启动新项目&#xff0c;并建议可在需要使用 non-GC 语言的场景中使用 Rust…