Spring 使用兼并之路(二):山穷水尽,山穷水尽
作者:京东。科技。李君。
书接上文,前面在 Spring 使用兼并之路(一):摸石头过河 介绍了几种不成功的阅历,下面继续折腾…。
四、库房兼并,独立容器。
在阅历了上面的测验,在搭档为啥不搞两个独立的容器提示下,决议抛开 Spring Boot 内置的父子容器计划,彻底自己完结父子容器。
怎么加载 web 项目?
现在的难题只要一个:怎么加载 web 项目?加载完结后,怎么继续持有 web 项目?通过考虑后,能够创立一个 boot 项目的 Spring Bean,在该 Bean 中加载并持有 web 项目的容器。因为 Spring Bean 默许是单例的,并且会随同 Spring 容器长时间存活,就能够确保 web 容器耐久存活。结合 Spring 扩展点概览及实践 中介绍的 Spring 扩展点,有两个当地能够使用:
1.能够使用 Appl。ic。ationCon。te。xtAware 获取 boot 容器的 ApplicationContext 实例,这样就能够完结自己完结的父子容器;
2.能够使用 ApplicationListener 获取 ContextRefreshedEvent 事情,该事情表明容器现已完结初始化,能够供给服务。在监听到该事情后,来进行 web 容器的加载。
思路确认后,代码完结就很简略了:
p。ac。kage com.diguage.demo.boot.config;imp。or。t org.slf4j.Logger;import org.slf4j.Logge。rF。actory;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 */Componentpublic class WebLo。ad。erListener implements ApplicationContextAware, ApplicationListener。< ApplicationEvent >{ private static final Logger logger = LoggerFactory.getLogger(WebLoaderListener.class); /** * 父容器,加载 boot 项目 */ private static ApplicationContext parentContext; /** * 子容器,加载 web 项目 */ private static ApplicationContext childContext; Override public void setApplicationContext(ApplicationContext ctx) throws BeansException { WebLoaderListener.parentContext = ctx; } Override public 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 服务不能重复注册,第2次加载抛出反常,导致发动失利。
开始,怀疑是 web 容器,加载了 WebLoaderListener,可是盯梢代码,没有发现 childContext 容器中有 WebLoaderListener 的相关 Bean。
昨日做了个小试验,又调试了一下 Spring 的源代码,发现了其间的奥妙。直接贴代码吧:
SPRING/spring-context/src/main/。java。/org/springframework/context/support/AbstractApplicationContext.java。
/** * Publish the given event to all listene。rs。. *。 < p >This is the internal delegate that all other {code publishEvent} * methods refer to. It is not meant to be cal。led。directly but rather serves * as a propaga。ti。on mechanism between application contexts in a hierarchy, * potentially overridden in。 sub。classes for a custom propagation arrangement. * pa。ram。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。 recom。mended 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 necessary ApplicationEvent applicationEvent; if (event instanceof ApplicationEvent applEvent) { applicationEvent = applEvent; eventType = typeHint; } else { ResolvableType payloadType = null; if (typeHint != null && ApplicationEvent.class.i。sAs。signableF。rom。(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 initialized if (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 */Componentpublic 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; Override public void setApplicationContext(ApplicationContext ctx) throws BeansException { WebLoaderListener.parentContext = ctx; } /** * 事情监听 * * author D瓜哥 · https://www.diguage.com */ Override public 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 A。PI。。
3.How to revert initial git commit?
审阅修改 黄宇。
内容来源:https://sh.tanphatexpress.com.vn/app-1/game co tuong hai nguoi choi,https://chatbotjud-hml.saude.mg.gov.br/app-1/ganha-tempo-barueri
(责任编辑:社会)