Der Weg zur Spring-Anwendungsintegration (2): Drehungen und Wendungen, helle Lichter | Technisches Team von JD Cloud

In Fortsetzung des obigen Artikels habe ich in [Spring Application Merger Road (1): Crossing the River by Feeling for Stones] mehrere erfolglose Erfahrungen vorgestellt. Lassen Sie uns weiter werfen ...

4. Lagerfusion, unabhängiger Container

Nachdem ich die oben genannten Versuche durchgegangen war und meine Kollegen mich daran erinnert hatten, warum sie nicht zwei unabhängige Container erstellt hatten, beschloss ich, die integrierte Eltern-Kind-Containerlösung von Spring Boot aufzugeben und den Eltern-Kind-Container vollständig selbst zu implementieren.

Wie lade ich webein Projekt?

Es gibt jetzt nur noch ein Problem: Wie lade ich webdas Projekt? Wie kann das Projekt nach Abschluss des Ladevorgangs weiterhin gespeichert werden web? Nachdem Sie darüber nachgedacht haben, können Sie die bootSpring Bean eines Projekts erstellen, in die der webContainer des Projekts geladen und gespeichert wird. Da Spring Bean standardmäßig ein Singleton ist und lange Zeit mit dem Spring-Container überlebt, kann es webdie Persistenz des Containers sicherstellen. In Kombination mit den Spring- Erweiterungspunkten, die in der Übersicht und Übung zu Spring-Erweiterungspunkten vorgestellt wurden, gibt es zwei Orte, die Sie verwenden können:

1. Sie können ApplicationContextAware verwenden, um die ApplicationContext-Instanz des Boot-Containers abzurufen, sodass Sie Ihren eigenen Eltern-Kind-Container implementieren können.

2. Sie können ApplicationListener verwenden, um das ContextRefreshedEvent-Ereignis abzurufen, das angibt, dass der Container die Initialisierung abgeschlossen hat und Dienste bereitstellen kann. Laden Sie den Webcontainer, nachdem Sie dieses Ereignis abgehört haben.

Nachdem die Idee festgelegt wurde, ist die Codeimplementierung sehr einfach:

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);
        }
    }
}


Das Problem der wiederholten Beladung von Containern

Der von mir dieses Mal implementierte Vater-Kind-Container überprüft, wie vorgestellt, nicht die gleichnamige Bean, was viel Ärger erspart. Wenn Sie jedoch das Protokoll beobachten, werden Sie feststellen, dass com.diguage.demo.boot.config.WebLoaderListener#onApplicationEventdie Methode zweimal ausgeführt wird, dh ContextRefreshedEventdas Ereignis wird zweimal überwacht, wodurch webder Container zweimal geladen wird. Da der RPC-Dienst des Projekts nicht wiederholt registriert werden kann, wurde beim zweiten Laden eine Ausnahme ausgelöst, die dazu führte, dass der Start fehlschlug.

Zunächst wurde vermutet, dass webder Container geladen war WebLoaderListener, doch nach der Verfolgung des Codes wurden keine zugehörigen Beans childContextim Container gefunden.WebLoaderListener

Ich habe gestern ein kleines Experiment durchgeführt, den Spring-Quellcode debuggt und das Geheimnis entdeckt. Poste einfach den Code:

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);
        }
    }
}


Wenn der übergeordnete Container am publishEventEnde der Methode nicht vorhanden ist null, werden die Container-bezogenen Ereignisse auch an den übergeordneten Container übertragen.

Wenn Sie dies sehen, ist es klar, dass es sich nicht um webden Container handelt, der WebLoaderListenerdiese Bean enthält, sondern webdass der Container das Ereignis aktiv an den übergeordneten Container sendet ContextRefreshedEvent.

Container zerstört

Zusätzlich zu den oben genannten Problemen ist noch eine weitere Frage zu berücksichtigen: Wie kann webder Container zerstört werden? Wenn der Container nicht zerstört werden kann, kommt es zu unerwarteten Problemen. Beispielsweise kann der RPC-Anbieter des Registrierungszentrums nicht rechtzeitig zerstört werden usw.

Auch hier ist die Lösung relativ einfach: Auch basierend auf der Ereignisüberwachung gibt es ContextClosedEventein Ereignis, wenn der Spring-Container zerstört wird. WebLoaderListenerHören Sie sich das Ereignis an und rufen Sie dann AbstractApplicationContext#closedie Methode auf, um die Zerstörung des Spring-Containers abzuschließen.

Laden und Zerstören von Parent-Child-Containern

Wenn man alle obigen Diskussionen zusammenfasst, sieht der vollständige Code wie folgt aus:

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. Referenzmaterialien

1. Überblick und Praxis der Spring-Erweiterungspunkte – Blog-Netzwerk „digua Brother“.

2. Kontexthierarchie mit der Spring Boot Fluent Builder API

3.Wie kann ich das anfängliche Git-Commit rückgängig machen?

Autor: Jingdong Technology Li Jun

Quelle: JD Cloud Developer Community Bitte geben Sie beim Nachdruck die Quelle an

Bilibili stürzte zweimal ab, Tencents „3.29“-Unfall erster Stufe … Bestandsaufnahme der zehn häufigsten Ausfallunfälle im Jahr 2023 Vue 3.4 „Slam Dunk“ veröffentlichte MySQL 5.7, Moqu, Li Tiaotiao … Bestandsaufnahme des „Stopps“ im Jahr 2023 Mehr ” (Open-Source-)Projekte und Websites blicken auf die IDE von vor 30 Jahren zurück: nur TUI, helle Hintergrundfarbe... Vim 9.1 wird veröffentlicht, gewidmet Bram Moolenaar, dem Vater von Redis, „Rapid Review“ LLM Programming: Omniscient und Omnipotent&& Stupid „Post-Open Source“ Die Ära ist gekommen: Die Lizenz ist abgelaufen und kann nicht mehr für die breite Öffentlichkeit bereitgestellt werden. China Unicom Broadband begrenzte plötzlich die Upload-Geschwindigkeit und eine große Anzahl von Benutzern beschwerte sich. Windows-Führungskräfte versprachen Verbesserungen: Machen Sie den Anfang Speisekarte wieder super. Niklaus Wirth, der Vater von Pascal, ist verstorben.
{{o.name}}
{{m.name}}

Supongo que te gusta

Origin my.oschina.net/u/4090830/blog/10583935
Recomendado
Clasificación