The road to Spring application integration (2): twists and turns, bright lights | JD Cloud technical team

Continuing from the above article, I introduced several unsuccessful experiences in [Spring Application Merger Road (1): Crossing the River by Feeling for Stones]. Let’s continue to toss...

4. Warehouse merger, independent container

After going through the above attempts and being reminded by my colleagues why they didn’t create two independent containers, I decided to abandon Spring Boot’s built-in parent-child container solution and implement the parent-child container entirely by myself.

How to load webproject?

There is only one problem now: how to load webthe project? How to continue to hold the project after loading is completed web? After thinking about it, you can create a bootproject's Spring Bean in which the webproject's container is loaded and held. Since Spring Bean is a singleton by default and will survive with the Spring container for a long time, it can ensure webthe persistence of the container. Combined with the Spring extension points introduced in Spring extension point overview and practice , there are two places you can use:

1. You can use ApplicationContextAware to obtain the ApplicationContext instance of the boot container, so that you can implement your own parent-child container;

2. You can use ApplicationListener to obtain the ContextRefreshedEvent event, which indicates that the container has completed initialization and can provide services. After listening to this event, load the web container.

After the idea is determined, the code implementation is very simple:

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


The problem of repeated loading of containers

The father-child container implemented by myself this time, as imagined, does not check the Bean with the same name, which saves a lot of trouble. However, if you observe the log, you will find that com.diguage.demo.boot.config.WebLoaderListener#onApplicationEventthe method is executed twice, that is, ContextRefreshedEventthe event is monitored twice, causing webthe container to be loaded twice. Since the project's RPC service cannot be registered repeatedly, an exception was thrown during the second load, causing the startup to fail.

Initially, it was suspected that webthe container was loaded WebLoaderListener, but after tracing the code, no related beans were found childContextin the container .WebLoaderListener

I did a small experiment yesterday, debugged the Spring source code, and discovered the mystery. Just post the 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);
        }
    }
}


At publishEventthe end of the method, if the parent container is not null, the container-related events will also be broadcast to the parent container.

It is clear when you see this, it is not webthe container that holds WebLoaderListenerthis Bean, but webthe container actively broadcasts ContextRefreshedEventthe event to the parent container.

Container destroyed

In addition to the above issues, there is another question to consider: How to destroy webthe container? If the container cannot be destroyed, there will be some unexpected problems. For example, the RPC provider of the registration center cannot be destroyed in time, etc.

The solution here is also relatively simple: also based on event monitoring, there will be ContextClosedEventan event when the Spring container is destroyed. WebLoaderListenerListen to the event in and then call AbstractApplicationContext#closethe method to complete the destruction of the Spring container.

Loading and destroying parent-child containers

Combining all the above discussions, the complete code is as follows:

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. Reference materials

1. Overview and practice of Spring extension points - "digua brother" blog network

2.Context Hierarchy with the Spring Boot Fluent Builder API

3.How to revert initial git commit?

Author: Jingdong Technology Li Jun

Source: JD Cloud Developer Community Please indicate the source when reprinting

Bilibili crashed twice, Tencent’s “3.29” first-level accident... Taking stock of the top ten downtime accidents in 2023 Vue 3.4 “Slam Dunk” released MySQL 5.7, Moqu, Li Tiaotiao… Taking stock of the “stop” in 2023 More” (open source) projects and websites look back on the IDE of 30 years ago: only TUI, bright background color... Vim 9.1 is released, dedicated to Bram Moolenaar, the father of Redis, "Rapid Review" LLM Programming: Omniscient and Omnipotent&& Stupid "Post-Open Source "The era has come: the license has expired and cannot serve the general public. China Unicom Broadband suddenly limited the upload speed, and a large number of users complained. Windows executives promised improvements: Make the Start Menu great again. Niklaus Wirth, the father of Pascal, passed away.
{{o.name}}
{{m.name}}

Guess you like

Origin my.oschina.net/u/4090830/blog/10583935
Recommended