0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看威廉希尔官方网站 视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Spring 应用合并之路(二):峰回路转,柳暗花明

京东云 来源:京东科技 李君 作者:京东科技 李君 2024-12-12 11:22 次阅读

作者:京东科技 李君

书接上文,前面在 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;

    @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 服务不能重复注册,第二次加载抛出异常,导致启动失败。

最初,怀疑是 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 necessary
    ApplicationEvent 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 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
 */
@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;

    @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 API

3.How to revert initial git commit?

审核编辑 黄宇

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • spring
    +关注

    关注

    0

    文章

    340

    浏览量

    14333
收藏 人收藏

    评论

    相关推荐

    中电信4G或将融合组网 FDD出现转机

    尽管不断有消息称中国电信在4G时代可能将获发TD-LTE牌照,但中国电信董事长王晓初近日的一番话似乎让此事峰回路转,王晓初称“4G时代,融合组网不可避免”。这似乎在暗示中国电信争取FDD LTE牌照的努力有望得到回报,将采用FDD LTE与TD-LTE融合组网的方式。
    发表于 06-24 10:55 953次阅读

    中欧争端峰回路转 光伏产业整合或提速

    中欧贸易史上金额最大的贸易争端——中欧光伏案“峰回路转”,日前达成价格承诺方案。业内人士提醒,在产能过剩没有根本改变的大背景下,国内的光伏产业还将在较长一定时期内处于加速洗牌和行业调整阶段。
    发表于 07-30 15:03 1189次阅读

    柳暗花明又一村

    “在LAYOUT里可以钻孔连线,转到ROUTER里却不可以”,纠缠了良久的问题,终于解决了,小马过河一般,自己的实践是最重要的~~~真有“山穷水复疑无路,柳暗花明又一村”的恍然之感~~
    发表于 11-28 12:31

    看完下文的晶振介绍,你定会有柳暗花明的感觉

    的地方呢,很郁闷呢!没事,听完松季电子的介绍,相信你定会有一种柳暗花明的感觉!走吧......  一、针对MHZ的晶振,我们需要清楚的知道晶振的型号(或者是体积)。如若在只知道体积的情况,我们还需要
    发表于 03-27 16:12

    GPNE拿下爱立信、三星电子、多普达后,如今又把专利对手瞄准苹果

    协议。征战多年、峰回路转华为偏软、苹果强硬“在与苹果就该专利做协商时,苹果公司态度十分强势、霸气,相反华为则表现得十分绅士,初次沟通之后约一个月时间就达成了和解协议,不过与华为达成和解协议的金额不便透露
    发表于 01-05 11:12

    三星为何不放弃PDP电视?

    三星宣布停止生产LCD电视,以后的只生产LED和PDP电视。或许伴随着3D威廉希尔官方网站 的推进,PDP电视发展会峰回路转
    发表于 03-12 09:27 1043次阅读

    雷士照明和解收场 吴长江否认策划风波

    据香港信报报道,剧情峰回路转的雷士照明(2222)股东内讧风波,由连续数月的权力争斗,发展到近日三大股东聚首和谈,集团创办人兼前任董事长吴长江重夺帅印有望。记者昨天致电
    发表于 10-10 11:31 718次阅读

    红米Note5什么时候上市?红米Note5再曝光:性价比神机来袭只要1099元

    雷军今年的销量峰回路转,又上一个高度,今年的小米5X和小米6性价比都非常高,据悉小米5X开售首天,就拿下了30万部的销量,不过红米系列在今年还没有爆发,接下来我们看看红米Note5。而且这次红米note5会大家带来更多的惊喜。而且雷军也在微博嘚瑟起来了:准确的说,也有很多外国人来买中国的小米手机。
    发表于 08-08 15:23 8253次阅读

    许家印成中国首富!万达退出房地产许家印成首富,王健林退居第四!峰回路转第一竟又是他?

    自从万达宣布退出房地产后在商界还是引起了一阵轩然大波的,近日中国首富排行版曝光许家印成为了中国首富。但是这个首富的位置应该是最短的了吧,在上面待了几个小时,峰回路转又被马云和马化腾反超,真的是不要太惊喜啊!一起来了解一下最新消息。
    发表于 09-19 09:05 3726次阅读

    iPhone面临出货危机 国产品牌厂商扩大攻势

    苹果(Apple)iPhone X市场销售表现可说是峰回路转,出货动能与各界预期落差不小。
    的头像 发表于 08-06 14:06 2876次阅读

    LoRa在业界争论不断,为何还能“倔强生长”

    看似山穷水尽,实则峰回路转。2017年底,工信部发布《微功率短距离无线电发射设备威廉希尔官方网站 要求(征求意见稿)》(简称《征求意见稿》),LoRa商用前景变得不够明朗。
    的头像 发表于 08-17 17:48 3838次阅读

    温度传感器IC可以轻松解决-55至200ºC温度范围内的大部分温度感测难题

    自进入IC设计时代,集成电路(IC)温度传感器不经意就成为器件设计的一部分。IC设计人员历经波折,试图将温度对芯片系统的影响减到最小。峰回路转,一位IC设计师突然有了一个绝妙的想法:何不积极开拓利用有源电路p-n结的温度行为,而不是局限于绞尽脑汁将其影响最小化。
    发表于 09-29 09:17 3549次阅读
    温度传感器IC可以轻松解决-55至200ºC温度范围内的大部分温度感测难题

    上海龙之队峰回路转摘夺首胜,见证历史

    在刚刚结束的《守望先锋第赛季联赛》中,中国上海龙之队以3:1的成绩击败波士顿崛起队,大比分迎来了上海龙的第一次胜利。上海龙之队冲云破雾终结连败纪录,苦尽甘来,重返胜利轨道。这场期待已久的告捷得到
    发表于 02-25 14:42 127次阅读

    峰回路转 IEEE解除对华为编辑和审稿人限制

    学者认为,IEEE“审稿门”风波落幕并不意味着美国将终止在学术界防堵华为
    的头像 发表于 06-06 16:59 3537次阅读

    华为手机销售情况峰回路转 国内高端供应链有望利好

    今年5月份以来,中美贸易摩擦对国内产业链厂商形成的影响已经开始体现;尤其是在继美系芯片厂商断供的消息传出,不久后谷歌也宣布将终止与华为的业务往来,华为及行业普遍预计,华为终端出货量将会下滑。
    的头像 发表于 07-05 16:44 2983次阅读