RM新时代网站-首页

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

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

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

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

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

作者:京東科技 李君

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

四、倉庫合并,獨立容器

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

如何加載 web 項目?

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

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

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

思路確定后,代碼實現(xià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 項目
     */
    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);
        }
    }
}

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

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

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

昨天做了個小實驗,又調(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 的情況下,則也會向父容器廣播容器的相關(guān)事件。

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

容器銷毀

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

這里的解決方案也比較簡單:同樣基于事件監(jiān)聽,Spring 容器銷毀會有 ContextClosedEvent 事件,在 WebLoaderListener 中監(jiān)聽該事件,然后調(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)聽的 web 項目加載器
 *
 * @author D瓜哥 · https://www.diguage.com
 */
@Component
public class WebLoaderListener implements ApplicationContextAware,
        ApplicationListener {
    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;
    }

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

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

3.?How to revert initial git commit?

審核編輯 黃宇

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

    關(guān)注

    0

    文章

    340

    瀏覽量

    14336
收藏 人收藏

    評論

    相關(guān)推薦

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

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

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

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

    柳暗花明又一村

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

    看完下文的晶振介紹,你定會有柳暗花明的感覺

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

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

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

    三星為何不放棄PDP電視?

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

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

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

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

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

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

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

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

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

    LoRa在業(yè)界爭論不斷,為何還能“倔強生長”

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

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

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

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

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

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

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

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

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