Spring 应用合并之路(二):峰回路转,柳暗花明 | 京东云技术团队
书接上文,前面在 [Spring 应用合并之路(一):摸石头过河]介绍了几种不成功的经验,下面继续折腾…
四、仓库合并,独立容器
在经历了上面的尝试,在同事为啥不搞两个独立的容器提醒下,决定抛开 Spring Boot 内置的父子容器方案,完全自己实现父子容器。
如何加载 web
项目?
现在的难题只有一个:如何加载 web
项目?加载完成后,如何持续持有 web
项目?经过思考后,可以创建一个 boot
项目的 Spring Bean,在该 Bean 中加载并持有 web
项目的容器。由于 Spring Bean 默认是单例的,并且会伴随 Spring 容器长期存活,就可以保证 web
容器持久存活。结合 Spring 扩展点概览及实践 中介绍的 Spring 扩展点,有两个地方可以利用:
1.可以利用 ApplicationContextAware 获取 boot 容器的 ApplicationContext 实例,这样就可以实现自己实现的父子容器;
2.可以利用 ApplicationListener 获取 ContextRefreshedEvent 事件,该事件表示容器已经完成初始化,可以提供服务。在监听到该事件后,来进行 web 容器的加载。
思路确定后,代码实现就很简单了:
package com.diguage.demo.boot.config;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Component;/*** @author D瓜哥 · https://www.diguage.com*/
@Component
public class WebLoaderListener implements ApplicationContextAware,ApplicationListener<ApplicationEvent> {private static final Logger logger = LoggerFactory.getLogger(WebLoaderListener.class);/*** 父容器,加载 boot 项目*/private static ApplicationContext parentContext;/*** 子容器,加载 web 项目*/private static ApplicationContext childContext;@Overridepublic void setApplicationContext(ApplicationContext ctx) throws BeansException {WebLoaderListener.parentContext = ctx;}@Overridepublic void onApplicationEvent(ApplicationEvent event) {logger.info("receive application event: {}", event);if (event instanceof ContextRefreshedEvent) {WebLoaderListener.childContext = new ClassPathXmlApplicationContext(new String[]{"classpath:web/spring-cfg.xml"},WebLoaderListener.parentContext);}}
}
容器重复加载的问题
这次自己实现的父子容器,如同设想的那样,没有同名 Bean 的检查,省去了很多麻烦。但是,观察日志,会发现 com.diguage.demo.boot.config.WebLoaderListener#onApplicationEvent
方法被两次执行,也就是监听到了两次 ContextRefreshedEvent
事件,导致 web
容器会被加载两次。由于项目的 RPC 服务不能重复注册,第二次加载抛出异常,导致启动失败。
最初,怀疑是 web
容器,加载了 WebLoaderListener
,但是跟踪代码,没有发现 childContext
容器中有 WebLoaderListener
的相关 Bean。
昨天做了个小实验,又调试了一下 Spring 的源代码,发现了其中的奥秘。直接贴代码吧:
SPRING/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java
/*** Publish the given event to all listeners.* <p>This is the internal delegate that all other {@code publishEvent}* methods refer to. It is not meant to be called directly but rather serves* as a propagation mechanism between application contexts in a hierarchy,* potentially overridden in subclasses for a custom propagation arrangement.* @param event the event to publish (may be an {@link ApplicationEvent}* or a payload object to be turned into a {@link PayloadApplicationEvent})* @param typeHint the resolved event type, if known.* The implementation of this method also tolerates a payload type hint for* a payload object to be turned into a {@link PayloadApplicationEvent}.* However, the recommended way is to construct an actual event object via* {@link PayloadApplicationEvent#PayloadApplicationEvent(Object, Object, ResolvableType)}* instead for such scenarios.* @since 4.2* @see ApplicationEventMulticaster#multicastEvent(ApplicationEvent, ResolvableType)*/
protected void publishEvent(Object event, @Nullable ResolvableType typeHint) {Assert.notNull(event, "Event must not be null");ResolvableType eventType = null;// Decorate event as an ApplicationEvent if necessaryApplicationEvent applicationEvent;if (event instanceof ApplicationEvent applEvent) {applicationEvent = applEvent;eventType = typeHint;}else {ResolvableType payloadType = null;if (typeHint != null && ApplicationEvent.class.isAssignableFrom(typeHint.toClass())) {eventType = typeHint;}else {payloadType = typeHint;}applicationEvent = new PayloadApplicationEvent<>(this, event, payloadType);}// Determine event type only once (for multicast and parent publish)if (eventType == null) {eventType = ResolvableType.forInstance(applicationEvent);if (typeHint == null) {typeHint = eventType;}}// Multicast right now if possible - or lazily once the multicaster is initializedif (this.earlyApplicationEvents != null) {this.earlyApplicationEvents.add(applicationEvent);}else if (this.applicationEventMulticaster != null) {this.applicationEventMulticaster.multicastEvent(applicationEvent, eventType);}// Publish event via parent context as well...// 如果有父容器,则也将事件发布给父容器。if (this.parent != null) {if (this.parent instanceof AbstractApplicationContext abstractApplicationContext) {abstractApplicationContext.publishEvent(event, typeHint);}else {this.parent.publishEvent(event);}}
}
在 publishEvent
方法的最后,如果父容器不为 null
的情况下,则也会向父容器广播容器的相关事件。
看到这里就清楚了,不是 web
容器持有了 WebLoaderListener
这个 Bean,而是 web
容器主动向父容器广播了 ContextRefreshedEvent
事件。
容器销毁
除了上述问题,还有一个问题需要思考:如何销毁 web
容器?如果不能销毁容器,会有一些意想不到的问题。比如,注册中心的 RPC 提供方不能及时销毁等等。
这里的解决方案也比较简单:同样基于事件监听,Spring 容器销毁会有 ContextClosedEvent
事件,在 WebLoaderListener
中监听该事件,然后调用 AbstractApplicationContext#close
方法就可以完成 Spring 容器的销毁工作。
父子容器加载及销毁
结合上面的所有论述,完整的代码如下:
package com.diguage.demo.boot.config;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Component;import java.util.Objects;/*** 基于事件监听的 web 项目加载器** @author D瓜哥 · https://www.diguage.com*/
@Component
public class WebLoaderListener implements ApplicationContextAware,ApplicationListener<ApplicationEvent> {private static final Logger logger = LoggerFactory.getLogger(WebLoaderListener.class);/*** 父容器,加载 boot 项目*/private static ApplicationContext parentContext;/*** 子容器,加载 web 项目*/private static ClassPathXmlApplicationContext childContext;@Overridepublic void setApplicationContext(ApplicationContext ctx) throws BeansException {WebLoaderListener.parentContext = ctx;}/*** 事件监听** @author D瓜哥 · https://www.diguage.com*/@Overridepublic void onApplicationEvent(ApplicationEvent event) {logger.info("receive application event: {}", event);if (event instanceof ContextRefreshedEvent refreshedEvent) {ApplicationContext context = refreshedEvent.getApplicationContext();if (Objects.equals(WebLoaderListener.parentContext, context)) {// 加载 web 容器WebLoaderListener.childContext = new ClassPathXmlApplicationContext(new String[]{"classpath:web/spring-cfg.xml"},WebLoaderListener.parentContext);}} else if (event instanceof ContextClosedEvent) {// 处理容器销毁事件if (Objects.nonNull(WebLoaderListener.childContext)) {synchronized (WebLoaderListener.class) {if (Objects.nonNull(WebLoaderListener.childContext)) {AbstractApplicationContext ctx = WebLoaderListener.childContext;WebLoaderListener.childContext = null;ctx.close();}}}}}
}
五、参考资料
1.Spring 扩展点概览及实践 - "地瓜哥"博客网
2.Context Hierarchy with the Spring Boot Fluent Builder API
3.How to revert initial git commit?
作者:京东科技 李君
来源:京东云开发者社区 转载请注明来源
相关文章:
Spring 应用合并之路(二):峰回路转,柳暗花明 | 京东云技术团队
书接上文,前面在 [Spring 应用合并之路(一):摸石头过河]介绍了几种不成功的经验,下面继续折腾… 四、仓库合并,独立容器 在经历了上面的尝试,在同事为啥不搞两个独立的容器提醒下,…...
SQL Error 1366, SQLState HY000
SQL错误 1366 和 SQLState HY000 通常指的是 MySQL 与字符编码或数据截断有关的问题。当尝试将数据插入具有与正在插入的数据不兼容的字符集或排序规则的列时,或者正在插入的数据对于列来说过长时,就会出现此错误。 解决方式: 检查列长度&am…...
Codeforces Round 893 (Div. 2)(VP-7,寒假加训)
VP时间 A. 关键在于按c的按钮 c&1 Alice可以多按一次c按钮 也就是a多一个(a) 之后比较a,b大小即可 !(c&1) Alice Bob操作c按钮次数一样 1.ac B.贪心 一开始会吃饼干 如果有卖饼的就吃 如果隔离一段时间到d没吃就吃(当时…...

MySQL第四战:视图以及常见面试题(上)
目录 目录: 一.视图 1.介绍什么是视图 2.视图的语法 语法讲解 实例操作 二.MySQL面试题 1.SQL脚本 2.面试题实战 三.思维导图 目录: 随着数字化时代的飞速发展,数据库技术,特别是MySQL,已经成为IT领域中不可…...

C语言程序设计——程序流程控制方法(一)
C语言关系运算符 ---等于ab!不等于a!b<、>小于和大于a>b 、a<b<、>小于等于、大于等于a>b 、a<b!非!(0)、!(NULL) 在C99之后,C语言开始支持布尔类型,头文件是stdbool.h。在文中我所演示的所有代码均是C99版。 在C语言上上述关…...
torch.backends.cudnn.benchmark
torch.backends.cudnn.benchmark 的设置对于使用 PyTorch 进行深度学习训练的性能优化至关重要。具体而言,它与 NVIDIA 的 CuDNN(CUDA Deep Neural Network library)库有关,该库是在 GPU 上加速深度神经网络计算的核心组件。 启用…...

SQL Server从0到1——写shell
xp_cmdshell 查看能否使用xpcmd_shell; select count(*) from master.dbo.sysobjects where xtype x and name xp_cmdshell 直接使用xpcmd_shell执行命令: EXEC master.dbo.xp_cmdshell whoami 发现居然无法使用 查看是否存在xp_cmdshell: EXEC…...
计算圆弧的起始角度、终止角度和矩形信息并使用drawArc绘制圆弧
Qt中常用绘制圆弧的库函数: //函数原型 void QPainter::drawArc(const QRectF &rectangle, int startAngle, int spanAngle)Qt规定1约占16个像素,比如一个完整的圆等于360度,对应的像素角度就是 5760度(16 * 360)…...
C++ Trie树模版 及模版题 || Trie字符串统计
Trie树:用来高效的存储和查找字符串集合的数据结构。 维护一个字符串集合,支持两种操作: I x 向集合中插入一个字符串 x ; Q x 询问一个字符串在集合中出现了多少次。 共有 N 个操作,所有输入的字符串总长度不超过 1…...

Linux基础命令@echo、tail、重定向符
目录 echo概念语法作用演示一演示二 反引号作用 tail概念语法作用不带选项,演示一带选项 -num,演示二带选项 -f , 持续跟踪 重定向符概念作用覆盖重定向,>演示一演示二 追加重定向,>>演示一演示二 总结 echo …...

uniapp:签字版、绘画板 插件l-signature
官方网站:LimeUi - 多端uniapp组件库 使用步骤: 1、首先从插件市场将代码下载到项目 海报画板 - DCloud 插件市场 2、下载后,在项目中的uni_modules目录(uni_modules优点:不需要import引入,还可以快捷更新…...
Python Pillow(PIL)库的用法介绍
Python的Pillow库(PIL)是一个强大的图像处理库,可以用来进行图像的读取、编辑、处理和保存等操作。下面是一些Pillow库的基本用法介绍: 安装Pillow库: 在命令行中输入以下命令即可安装Pillow库: 复制代码 p…...

uniapp 【专题详解 -- 时间】云数据库时间类型设计,时间生成、时间格式化渲染(uni-dateformat 组件的使用)
云数据表的时间类型设计 推荐使用时间戳 timestamp "createTime": {"bsonType": "timestamp","label": "创建时间:" }时间生成 获取当前时间 Date.now() .add({createTime: Date.now() })时间格式化渲染 下载安…...
k8s之flink的几种创建方式
在此之前需要部署一下私人docker仓库,教程搭建 Docker 镜像仓库 注意:每台节点的daemon.json都需要配置"insecure-registries": ["http://主机IP:8080"] 并重启 一、session 模式 Session 模式是指在 Kubernetes 上启动一个共享的…...

应用OpenCV绘制箭头
绘制箭头函数 方法:函数cv2.arrowedLine( ) 语法格式:cv2.arrowedLine(img, pt1, pt2, color[, thickness[, line_type[, shift[, tipLength]]]]) 参数说明: img:要画的直线所在的图像,也称为画布。。 pt1&#x…...
信息学奥赛一本通1032:大象喝水查
1032:大象喝水查 时间限制: 1000 ms 内存限制: 65536 KB 提交数: 104347 通过数: 64726 【题目描述】 一只大象口渴了,要喝20升水才能解渴,但现在只有一个深h厘米,底面半径为r厘米的小圆桶(h和r都是整数)。问大象至少…...
聊聊jvm的direct buffer统计
序 本文主要研究一下jvm的direct buffer统计 spring boot metrics jvm.memory.used {"name": "jvm.memory.used","description": "The amount of used memory","baseUnit": "bytes","measurements"…...

C/C++ 位段
目录 什么是位段? 位段的内存分配 位段的跨平台问题 什么是位段? 位段的声明与结构是类似的,但是有两个不同: 位段的成员必须是 int、unsigned int 或signed int 等整型家族。位段的成员名后边有一个冒号和一个数字 这是一个…...
Peter算法小课堂—树的应用
开篇先给大家讲个东西,叫vector,有老师称之为“向量”,当然与数学中的向量不一样啊,所以我要称之为“长度可变的数组” vector 头文件:#include <vector> 用法:vector<int> d; 尾部增加元素…...

FineBI:简介
1 介绍 FineBI 是帆软软件有限公司推出的一款商业智能(Business Intelligence)产品。 FineBI 是定位于自助大数据分析的 BI 工具,能够帮助企业的业务人员和数据分析师,开展以问题导向的探索式分析。 2 现阶段数据分析弊端 现阶…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...

TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...

css实现圆环展示百分比,根据值动态展示所占比例
代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...
【Java学习笔记】Arrays类
Arrays 类 1. 导入包:import java.util.Arrays 2. 常用方法一览表 方法描述Arrays.toString()返回数组的字符串形式Arrays.sort()排序(自然排序和定制排序)Arrays.binarySearch()通过二分搜索法进行查找(前提:数组是…...

遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...

Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)
概述 在 Swift 开发语言中,各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过,在涉及到多个子类派生于基类进行多态模拟的场景下,…...

高等数学(下)题型笔记(八)空间解析几何与向量代数
目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...

c#开发AI模型对话
AI模型 前面已经介绍了一般AI模型本地部署,直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型,但是目前国内可能使用不多,至少实践例子很少看见。开发训练模型就不介绍了&am…...
管理学院权限管理系统开发总结
文章目录 🎓 管理学院权限管理系统开发总结 - 现代化Web应用实践之路📝 项目概述🏗️ 技术架构设计后端技术栈前端技术栈 💡 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 🗄️ 数据库设…...

CVE-2020-17519源码分析与漏洞复现(Flink 任意文件读取)
漏洞概览 漏洞名称:Apache Flink REST API 任意文件读取漏洞CVE编号:CVE-2020-17519CVSS评分:7.5影响版本:Apache Flink 1.11.0、1.11.1、1.11.2修复版本:≥ 1.11.3 或 ≥ 1.12.0漏洞类型:路径遍历&#x…...