0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Spring 應(yīng)用合并之路(二):峰回路轉(zhuǎn),柳暗花明

京東云 ? 來(lái)源:京東科技 李君 ? 作者:京東科技 李君 ? 2024-12-12 11:22 ? 次閱讀

作者:京東科技 李君

書(shū)接上文,前面在 Spring 應(yīng)用合并之路(一):摸石頭過(guò)河 介紹了幾種不成功的經(jīng)驗(yàn),下面繼續(xù)折騰…

四、倉(cāng)庫(kù)合并,獨(dú)立容器

在經(jīng)歷了上面的嘗試,在同事為啥不搞兩個(gè)獨(dú)立的容器提醒下,決定拋開(kāi) Spring Boot 內(nèi)置的父子容器方案,完全自己實(shí)現(xiàn)父子容器。

如何加載 web 項(xiàng)目?

現(xiàn)在的難題只有一個(gè):如何加載 web 項(xiàng)目?加載完成后,如何持續(xù)持有 web 項(xiàng)目?經(jīng)過(guò)思考后,可以創(chuàng)建一個(gè) boot 項(xiàng)目的 Spring Bean,在該 Bean 中加載并持有 web 項(xiàng)目的容器。由于 Spring Bean 默認(rèn)是單例的,并且會(huì)伴隨 Spring 容器長(zhǎng)期存活,就可以保證 web 容器持久存活。結(jié)合 Spring 擴(kuò)展點(diǎn)概覽及實(shí)踐 中介紹的 Spring 擴(kuò)展點(diǎn),有兩個(gè)地方可以利用:

1.可以利用 ApplicationContextAware 獲取 boot 容器的 ApplicationContext 實(shí)例,這樣就可以實(shí)現(xiàn)自己實(shí)現(xiàn)的父子容器;

2.可以利用 ApplicationListener 獲取 ContextRefreshedEvent 事件,該事件表示容器已經(jīng)完成初始化,可以提供服務(wù)。在監(jiān)聽(tīng)到該事件后,來(lái)進(jìn)行 web 容器的加載。

思路確定后,代碼實(shí)現(xiàn)就很簡(jiǎn)單了:

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 {
    private static final Logger logger = LoggerFactory.getLogger(WebLoaderListener.class);

    /**
     * 父容器,加載 boot 項(xiàng)目
     */
    private static ApplicationContext parentContext;

    /**
     * 子容器,加載 web 項(xiàng)目
     */
    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);
        }
    }
}

容器重復(fù)加載的問(wèn)題

這次自己實(shí)現(xiàn)的父子容器,如同設(shè)想的那樣,沒(méi)有同名 Bean 的檢查,省去了很多麻煩。但是,觀察日志,會(huì)發(fā)現(xiàn) com.diguage.demo.boot.config.WebLoaderListener#onApplicationEvent 方法被兩次執(zhí)行,也就是監(jiān)聽(tīng)到了兩次 ContextRefreshedEvent 事件,導(dǎo)致 web 容器會(huì)被加載兩次。由于項(xiàng)目的 RPC 服務(wù)不能重復(fù)注冊(cè),第二次加載拋出異常,導(dǎo)致啟動(dòng)失敗。

最初,懷疑是 web 容器,加載了 WebLoaderListener,但是跟蹤代碼,沒(méi)有發(fā)現(xiàn) childContext 容器中有 WebLoaderListener 的相關(guān) Bean。

昨天做了個(gè)小實(shí)驗(yàn),又調(diào)試了一下 Spring 的源代碼,發(fā)現(xiàn)了其中的奧秘。直接貼代碼吧:

SPRING/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java

/**
 * Publish the given event to all listeners.
 * 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...
    // 如果有父容器,則也將事件發(fā)布給父容器。
    if (this.parent != null) {
        if (this.parent instanceof AbstractApplicationContext abstractApplicationContext) {
            abstractApplicationContext.publishEvent(event, typeHint);
        }
        else {
            this.parent.publishEvent(event);
        }
    }
}

在 publishEvent 方法的最后,如果父容器不為 null 的情況下,則也會(huì)向父容器廣播容器的相關(guān)事件。

看到這里就清楚了,不是 web 容器持有了 WebLoaderListener 這個(gè) Bean,而是 web 容器主動(dòng)向父容器廣播了 ContextRefreshedEvent 事件。

容器銷毀

除了上述問(wèn)題,還有一個(gè)問(wèn)題需要思考:如何銷毀 web 容器?如果不能銷毀容器,會(huì)有一些意想不到的問(wèn)題。比如,注冊(cè)中心的 RPC 提供方不能及時(shí)銷毀等等。

這里的解決方案也比較簡(jiǎn)單:同樣基于事件監(jiān)聽(tīng),Spring 容器銷毀會(huì)有 ContextClosedEvent 事件,在 WebLoaderListener 中監(jiān)聽(tīng)該事件,然后調(diào)用 AbstractApplicationContext#close 方法就可以完成 Spring 容器的銷毀工作。

父子容器加載及銷毀

結(jié)合上面的所有論述,完整的代碼如下:

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;

/**
 * 基于事件監(jiān)聽(tīng)的 web 項(xiàng)目加載器
 *
 * @author D瓜哥 · https://www.diguage.com
 */
@Component
public class WebLoaderListener implements ApplicationContextAware,
        ApplicationListener {
    private static final Logger logger = LoggerFactory.getLogger(WebLoaderListener.class);

    /**
     * 父容器,加載 boot 項(xiàng)目
     */
    private static ApplicationContext parentContext;

    /**
     * 子容器,加載 web 項(xiàng)目
     */
    private static ClassPathXmlApplicationContext childContext;

    @Override
    public void setApplicationContext(ApplicationContext ctx) throws BeansException {
        WebLoaderListener.parentContext = ctx;
    }

    /**
     * 事件監(jiān)聽(tīng)
     *
     * @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 擴(kuò)展點(diǎn)概覽及實(shí)踐 - "地瓜哥"博客網(wǎng)?

2.?Context Hierarchy with the Spring Boot Fluent Builder API?

3.?How to revert initial git commit?

審核編輯 黃宇

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • spring
    +關(guān)注

    關(guān)注

    0

    文章

    340

    瀏覽量

    14343
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    中電信4G或?qū)⑷诤辖M網(wǎng) FDD出現(xiàn)轉(zhuǎn)機(jī)

    盡管不斷有消息稱中國(guó)電信在4G時(shí)代可能將獲發(fā)TD-LTE牌照,但中國(guó)電信董事長(zhǎng)王曉初近日的一番話似乎讓此事峰回路轉(zhuǎn),王曉初稱“4G時(shí)代,融合組網(wǎng)不可避免”。這似乎在暗示中國(guó)電信爭(zhēng)取FDD LTE牌照的努力有望得到回報(bào),將采用FDD LTE與TD-LTE融合組網(wǎng)的方式。
    發(fā)表于 06-24 10:55 ?956次閱讀

    中歐爭(zhēng)端峰回路轉(zhuǎn) 光伏產(chǎn)業(yè)整合或提速

    中歐貿(mào)易史上金額最大的貿(mào)易爭(zhēng)端——中歐光伏案“峰回路轉(zhuǎn)”,日前達(dá)成價(jià)格承諾方案。業(yè)內(nèi)人士提醒,在產(chǎn)能過(guò)剩沒(méi)有根本改變的大背景下,國(guó)內(nèi)的光伏產(chǎn)業(yè)還將在較長(zhǎng)一定時(shí)期內(nèi)處于加速洗牌和行業(yè)調(diào)整階段。
    發(fā)表于 07-30 15:03 ?1193次閱讀

    柳暗花明又一村

    “在LAYOUT里可以鉆孔連線,轉(zhuǎn)到ROUTER里卻不可以”,糾纏了良久的問(wèn)題,終于解決了,小馬過(guò)河一般,自己的實(shí)踐是最重要的~~~真有“山窮水復(fù)疑無(wú)路,柳暗花明又一村”的恍然之感~~
    發(fā)表于 11-28 12:31

    看完下文的晶振介紹,你定會(huì)有柳暗花明的感覺(jué)

    的地方呢,很郁悶?zāi)?沒(méi)事,聽(tīng)完松季電子的介紹,相信你定會(huì)有一種柳暗花明的感覺(jué)!走吧......  一、針對(duì)MHZ的晶振,我們需要清楚的知道晶振的型號(hào)(或者是體積)。如若在只知道體積的情況,我們還需要
    發(fā)表于 03-27 16:12

    GPNE拿下愛(ài)立信、三星電子、多普達(dá)后,如今又把專利對(duì)手瞄準(zhǔn)蘋(píng)果

    協(xié)議。征戰(zhàn)多年、峰回路轉(zhuǎn)華為偏軟、蘋(píng)果強(qiáng)硬“在與蘋(píng)果就該專利做協(xié)商時(shí),蘋(píng)果公司態(tài)度十分強(qiáng)勢(shì)、霸氣,相反華為則表現(xiàn)得十分紳士,初次溝通之后約一個(gè)月時(shí)間就達(dá)成了和解協(xié)議,不過(guò)與華為達(dá)成和解協(xié)議的金額不便透露
    發(fā)表于 01-05 11:12

    三星為何不放棄PDP電視?

    三星宣布停止生產(chǎn)LCD電視,以后的只生產(chǎn)LED和PDP電視。或許伴隨著3D技術(shù)的推進(jìn),PDP電視發(fā)展會(huì)峰回路轉(zhuǎn)。
    發(fā)表于 03-12 09:27 ?1044次閱讀

    雷士照明和解收?qǐng)?吳長(zhǎng)江否認(rèn)策劃風(fēng)波

    據(jù)香港信報(bào)報(bào)道,劇情峰回路轉(zhuǎn)的雷士照明(2222)股東內(nèi)訌風(fēng)波,由連續(xù)數(shù)月的權(quán)力爭(zhēng)斗,發(fā)展到近日三大股東聚首和談,集團(tuán)創(chuàng)辦人兼前任董事長(zhǎng)吳長(zhǎng)江重奪帥印有望。記者昨天致電
    發(fā)表于 10-10 11:31 ?724次閱讀

    紅米Note5什么時(shí)候上市?紅米Note5再曝光:性價(jià)比神機(jī)來(lái)襲只要1099元

    雷軍今年的銷量峰回路轉(zhuǎn),又上一個(gè)高度,今年的小米5X和小米6性價(jià)比都非常高,據(jù)悉小米5X開(kāi)售首天,就拿下了30萬(wàn)部的銷量,不過(guò)紅米系列在今年還沒(méi)有爆發(fā),接下來(lái)我們看看紅米Note5。而且這次紅米note5會(huì)大家?guī)?lái)更多的驚喜。而且雷軍也在微博嘚瑟起來(lái)了:準(zhǔn)確的說(shuō),也有很多外國(guó)人來(lái)買中國(guó)的小米手機(jī)。
    發(fā)表于 08-08 15:23 ?8254次閱讀

    許家印成中國(guó)首富!萬(wàn)達(dá)退出房地產(chǎn)許家印成首富,王健林退居第四!峰回路轉(zhuǎn)第一竟又是他?

    自從萬(wàn)達(dá)宣布退出房地產(chǎn)后在商界還是引起了一陣軒然大波的,近日中國(guó)首富排行版曝光許家印成為了中國(guó)首富。但是這個(gè)首富的位置應(yīng)該是最短的了吧,在上面待了幾個(gè)小時(shí),峰回路轉(zhuǎn)又被馬云和馬化騰反超,真的是不要太驚喜??!一起來(lái)了解一下最新消息。
    發(fā)表于 09-19 09:05 ?3730次閱讀

    iPhone面臨出貨危機(jī) 國(guó)產(chǎn)品牌廠商擴(kuò)大攻勢(shì)

    蘋(píng)果(Apple)iPhone X市場(chǎng)銷售表現(xiàn)可說(shuō)是峰回路轉(zhuǎn),出貨動(dòng)能與各界預(yù)期落差不小。
    的頭像 發(fā)表于 08-06 14:06 ?2882次閱讀

    LoRa在業(yè)界爭(zhēng)論不斷,為何還能“倔強(qiáng)生長(zhǎng)”

    看似山窮水盡,實(shí)則峰回路轉(zhuǎn)。2017年底,工信部發(fā)布《微功率短距離無(wú)線電發(fā)射設(shè)備技術(shù)要求(征求意見(jiàn)稿)》(簡(jiǎn)稱《征求意見(jiàn)稿》),LoRa商用前景變得不夠明朗。
    的頭像 發(fā)表于 08-17 17:48 ?3845次閱讀

    溫度傳感器IC可以輕松解決-55至200oC溫度范圍內(nèi)的大部分溫度感測(cè)難題

    自進(jìn)入IC設(shè)計(jì)時(shí)代,集成電路(IC)溫度傳感器不經(jīng)意就成為器件設(shè)計(jì)的一部分。IC設(shè)計(jì)人員歷經(jīng)波折,試圖將溫度對(duì)芯片系統(tǒng)的影響減到最小。峰回路轉(zhuǎn),一位IC設(shè)計(jì)師突然有了一個(gè)絕妙的想法:何不積極開(kāi)拓利用有源電路p-n結(jié)的溫度行為,而不是局限于絞盡腦汁將其影響最小化。
    發(fā)表于 09-29 09:17 ?3551次閱讀
    溫度傳感器IC可以輕松解決-55至200oC溫度范圍內(nèi)的大部分溫度感測(cè)難題

    上海龍之隊(duì)峰回路轉(zhuǎn)摘奪首勝,見(jiàn)證歷史

    在剛剛結(jié)束的《守望先鋒第賽季聯(lián)賽》中,中國(guó)上海龍之隊(duì)以3:1的成績(jī)擊敗波士頓崛起隊(duì),大比分迎來(lái)了上海龍的第一次勝利。上海龍之隊(duì)沖云破霧終結(jié)連敗紀(jì)錄,苦盡甘來(lái),重返勝利軌道。這場(chǎng)期待已久的告捷得到
    發(fā)表于 02-25 14:42 ?127次閱讀

    峰回路轉(zhuǎn) IEEE解除對(duì)華為編輯和審稿人限制

    學(xué)者認(rèn)為,IEEE“審稿門”風(fēng)波落幕并不意味著美國(guó)將終止在學(xué)術(shù)界防堵華為
    的頭像 發(fā)表于 06-06 16:59 ?3544次閱讀

    華為手機(jī)銷售情況峰回路轉(zhuǎn) 國(guó)內(nèi)高端供應(yīng)鏈有望利好

    今年5月份以來(lái),中美貿(mào)易摩擦對(duì)國(guó)內(nèi)產(chǎn)業(yè)鏈廠商形成的影響已經(jīng)開(kāi)始體現(xiàn);尤其是在繼美系芯片廠商斷供的消息傳出,不久后谷歌也宣布將終止與華為的業(yè)務(wù)往來(lái),華為及行業(yè)普遍預(yù)計(jì),華為終端出貨量將會(huì)下滑。
    的頭像 發(fā)表于 07-05 16:44 ?2989次閱讀