Путь к интеграции приложений Spring (2): перипетии, яркие огни | Техническая команда JD Cloud

В продолжение приведенной выше статьи я представил несколько неудачных опытов в [Дорога слияния весенних приложений (1): Переправа через реку, нащупывая камни]. Давайте продолжим бросать...

4. Объединение складов, независимый контейнер

После описанных выше попыток и напоминаний моих коллег, почему они не создали два независимых контейнера, я решил отказаться от встроенного в Spring Boot решения для родительско-дочернего контейнера и полностью реализовать родительско-дочерний контейнер самостоятельно.

Как загрузить webпроект?

Теперь есть только одна проблема: как загрузить webпроект? Как продолжать удерживать проект после завершения загрузки web? Подумав об этом, вы можете создать bootSpring Bean проекта, в который webзагружается и хранится контейнер проекта. Поскольку Spring Bean по умолчанию является одноэлементным и может работать с контейнером Spring в течение длительного времени, он может обеспечить webпостоянство контейнера. В сочетании с точками расширения Spring, представленными в разделе «Обзор и практика точек расширения Spring» , вы можете использовать два места:

1. Вы можете использовать ApplicationContextAware для получения экземпляра ApplicationContext загрузочного контейнера, чтобы можно было реализовать собственный родительско-дочерний контейнер;

2. Вы можете использовать ApplicationListener для получения события ContextRefreshedEvent, которое указывает, что контейнер завершил инициализацию и может предоставлять услуги. Прослушав это событие, загрузите веб-контейнер.

После того, как идея определена, реализация кода очень проста:

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 с тем же именем, что избавляет от многих хлопот. Однако, если вы посмотрите журнал, вы обнаружите, что com.diguage.demo.boot.config.WebLoaderListener#onApplicationEventметод выполняется дважды, то есть ContextRefreshedEventсобытие отслеживается дважды, что приводит webк двойной загрузке контейнера. Поскольку службу RPC проекта нельзя зарегистрировать повторно, во время второй загрузки было выдано исключение, что привело к сбою запуска.

Изначально предполагалось, что webконтейнер был загружен WebLoaderListener, но после трассировки кода childContextв контейнере не было обнаружено WebLoaderListenerсвязанных с ним bean-компонентов .

Вчера я провел небольшой эксперимент, отладил исходный код Spring и обнаружил тайну. Просто опубликуйте код:

ВЕСНА/весна-контекст/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, события, связанные с контейнером, также будут транслироваться в родительский контейнер.

Когда вы это видите, становится ясно, что не webконтейнер содержит WebLoaderListenerэтот Bean, а webконтейнер активно транслирует ContextRefreshedEventсобытие родительскому контейнеру.

Контейнер уничтожен

Помимо вышеперечисленных вопросов, следует рассмотреть еще один вопрос: как уничтожить 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 — сеть блогов "digua Brother"

2.Иерархия контекста с помощью Spring Boot Fluent Builder API

3.Как отменить первоначальную фиксацию git?

Автор: Jingdong Technology Ли Цзюнь

Источник: Сообщество разработчиков JD Cloud. При перепечатке указывайте источник.

Дважды произошел сбой Bilibili, авария первого уровня Tencent «3.29»… Подведение итогов десяти крупнейших аварий с простоями в 2023 году. Vue 3.4 «Slam Dunk» выпустил MySQL 5.7, Moqu, Li Tiaotiao… Подведение итогов «остановки» в 2023 году Подробнее (с открытым исходным кодом) проекты и веб-сайты оглядываются на IDE 30-летней давности: только TUI, яркий цвет фона... Выпущен Vim 9.1, посвященный Брэму Муленаару, отцу Redis, "Rapid Review" LLM Programming: Omniscient и Всемогущий&& Глупый «Пост-открытый исходный код». Наступила эра: срок действия лицензии истек, и она не может обслуживать широкую публику. China Unicom Broadband внезапно ограничила скорость загрузки, и большое количество пользователей пожаловались. Руководители Windows пообещали улучшения: сделайте начало Меню снова великолепное. Скончался Никлаус Вирт, отец Паскаля.
{{o.name}}
{{м.имя}}

рекомендация

отmy.oschina.net/u/4090830/blog/10583935