Spring アプリケーション統合への道 (2): 紆余曲折、明るい光 | JD Cloud テクニカル チーム

上記記事に引き続き、【春申請併合ロード(1):石ころを感じて川を渡る】でいくつかの失敗体験をご紹介しました。

4.倉庫統合、コンテナ独立

上記の試みを経て、なぜ 2 つの独立したコンテナを作成しなかったのか同僚に思い出された後、私は Spring Boot の組み込みの親子コンテナ ソリューションを放棄し、完全に自分で親子コンテナを実装することにしました。

webプロジェクトをロードするにはどうすればよいですか?

問題は 1 つだけです。webプロジェクトをどのようにロードするかです。ロードが完了した後、プロジェクトを保持し続けるにはどうすればよいですかweb? よく考えた後、プロジェクトのコンテナをロードして保持するbootプロジェクトの Spring Beanを作成できます。webSpring Beanはデフォルトでシングルトンであり、Springコンテナとともに長期間存続するため、webコンテナの永続性を確保できます。「 Spring 拡張ポイントの概要と実践 」で紹介した Spring 拡張ポイントと組み合わせると、次の 2 つの場所で使用できます。

1. ApplicationContextAware を使用してブート コンテナの 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をチェックしないので手間が省けます。ただし、ログを観察すると、メソッドが 2 回実行されていることがわかりますcom.diguage.demo.boot.config.WebLoaderListener#onApplicationEvent。つまり、ContextRefreshedEventイベントが 2 回監視されており、webコンテナーが 2 回読み込まれています。プロジェクトの 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 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、コンテナ関連のイベントも親コンテナにブロードキャストされます。

これを見ると、この Bean をweb保持しているのはコンテナではなく、コンテナがアクティブに親コンテナにイベントをブロードキャストしていることがわかります。WebLoaderListenerwebContextRefreshedEvent

コンテナが破壊されました

上記の問題に加えて、考慮すべきもう 1 つの問題があります。それは、webコンテナをどのように破棄するかということです。コンテナを破壊できない場合、予期せぬ問題が発生する可能性があります。たとえば、登録センターの RPC プロバイダーは時間内に破棄できないなどです。

ContextClosedEventここでの解決策も比較的単純です: これもイベント監視に基づいており、 Spring コンテナが破棄されるとイベントが発生します。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();
                    }
                }
            }
        }
    }
}


5. 参考資料

1. Spring 拡張ポイントの概要と実践 - 「ディグア兄弟」ブログネットワーク

2. Spring Boot Fluent Builder API によるコンテキスト階層

3.最初の git コミットを元に戻すにはどうすればよいですか?

著者: Jingdong Technology Li Jun

出典:JD Cloud Developer Community 転載の際は出典を明記してください

Bilibiliは2度クラッシュ、テンセントの「3.29」第1レベル事故…2023年のダウンタイム事故トップ10を振り返る Vue 3.4「スラムダンク」リリース MySQL 5.7、莫曲、李条条…2023年の「停止」を振り返る 続き” (オープンソース) プロジェクトと Web サイトが 30 年前の IDE を振り返る: TUI のみ、明るい背景色... Vim 9.1 がリリース、 Redis の父 Bram Moolenaar に捧げ、「ラピッド レビュー」LLM プログラミング: Omniscient 全能&&愚かな 「ポスト・オープンソースの時代が来た。ライセンスの有効期限が切れ、一般ユーザーにサービスを提供できなくなった。チャイナ ユニコムブロードバンドが突然アップロード速度を制限し、多くのユーザーが苦情を申し立てた。Windows 幹部は改善を約束した: Make the Start」メニューもまた素晴らしいです。 パスカルの父、ニクラス・ヴィルトが亡くなりました。
{{名前}}
{{名前}}

おすすめ

転載: my.oschina.net/u/4090830/blog/10583935
おすすめ