В продолжение приведенной выше статьи я представил несколько неудачных опытов в [Дорога слияния весенних приложений (1): Переправа через реку, нащупывая камни]. Давайте продолжим бросать...
4. Объединение складов, независимый контейнер
После описанных выше попыток и напоминаний моих коллег, почему они не создали два независимых контейнера, я решил отказаться от встроенного в Spring Boot решения для родительско-дочернего контейнера и полностью реализовать родительско-дочерний контейнер самостоятельно.
Как загрузить web
проект?
Теперь есть только одна проблема: как загрузить web
проект? Как продолжать удерживать проект после завершения загрузки web
? Подумав об этом, вы можете создать boot
Spring 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 пообещали улучшения: сделайте начало Меню снова великолепное. Скончался Никлаус Вирт, отец Паскаля.