当前位置: 首页 > 知识库问答 >
问题:

Spring父子上下文和应用程序侦听器

南门峰
2023-03-14

我有一个关于Spring的ApplicationListener在父上下文和子上下文方面的性质的问题。假设您创建了一个父上下文,它创建了一个bean,该bean是一个单例,并注册为ApplicationListener。然后,使用父上下文创建子上下文。关闭子上下文时,Spring将发送ContextClosedEvent。该事件是否也会传播到父上下文,从而导致作为ApplicationListener的所有父上下文单例接收该事件?

我在文档中注意到[ContextClosedEvent]:(http://docs.spring.io/spring/docs/4.0.6.RELEASE/spring-framework-reference/htmlsingle/#context-功能事件),在关闭ApplicationContext时发布,使用ConfigurableApplicationContext界面上的close()方法“Closed”在这里表示所有的单例bean都被销毁。一个封闭的上下文到达其生命周期的终点;它不能被刷新或重新启动

从本质上说,我要问的是,事件发布仅限于特定的子上下文,还是在所有父/子上下文中传播?

共有2个答案

郝原
2023-03-14

是的,Spring应用程序上下文确实会递归地将事件从子上下文传播到其所有祖先。

以下AbstractApplicationContext代码对此负责:

/**
 * Publish the given event to all listeners.
 * @param event the event to publish (may be an {@link ApplicationEvent}
 * or a payload object to be turned into a {@link PayloadApplicationEvent})
 * @param eventType the resolved event type, if known
 * @since 4.2
 */
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
    Assert.notNull(event, "Event must not be null");
    if (logger.isTraceEnabled()) {
        logger.trace("Publishing event in " + getDisplayName() + ": " + event);
    }

    // Decorate event as an ApplicationEvent if necessary
    ApplicationEvent applicationEvent;
    if (event instanceof ApplicationEvent) {
        applicationEvent = (ApplicationEvent) event;
    }
    else {
        applicationEvent = new PayloadApplicationEvent<>(this, event);
        if (eventType == null) {
            eventType = ((PayloadApplicationEvent) applicationEvent).getResolvableType();
        }
    }

    // Multicast right now if possible - or lazily once the multicaster is initialized
    if (this.earlyApplicationEvents != null) {
        this.earlyApplicationEvents.add(applicationEvent);
    }
    else {
        getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
    }

    // Publish event via parent context as well...
    if (this.parent != null) {
        if (this.parent instanceof AbstractApplicationContext) {
            ((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
        }
        else {
            this.parent.publishEvent(event);
        }
    }
}

正如您在publishMethod正文的末尾所看到的,如果有父对象,那么任何事件也会传播到它。

相反,来自父母的事件不会传播给孩子,因为父母不知道孩子的情况。

如果不想传播事件,只需执行以下操作(而不是将父级设置为子上下文):

yourChildApplicationContext.addBeanFactoryPostProcessor(beanFactory -> {
  beanFactory.setParentBeanFactory(parentContext.getBeanFactory());
});

或者,如果您的子上下文是GenericApplicationContextAnnotationConfigApplicationContextGenericGroovyApplicationContextGenericXmlApplicationContextStaticApplicationContext),您甚至可以进一步简化:

yourChildApplicationContext.getDefaultListableBeanFactory()
   .setParentBeanFactory(parentContext.getBeanFactory());
宿建本
2023-03-14

调用所有侦听器,但参数(在本例中为ContextClosedEvents)将指向正在关闭的上下文。

下面的测试创建父上下文,子上下文,启动它们,关闭父上下文,然后关闭子上下文。

public class ContextListenerTest {

    @Test
    public void contextListenerTest() {
        AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext(ParentContext.class);
        AnnotationConfigApplicationContext child = new AnnotationConfigApplicationContext(ChildContext.class);
        child.setParent(parent);
        child.start();

        System.out.println("closing child now...");
        child.close();
        System.out.println("closing parent now...");
        parent.close();
    }

    public static class ParentContext {
        @Bean
        public ApplicationListener<ContextClosedEvent> closeEvent() {
            return new ApplicationListener<ContextClosedEvent>() {
                @Override
                public void onApplicationEvent(ContextClosedEvent event) {
                    System.out.println("parent listener: " + event);
                }
            };
        }
    }

    public static class ChildContext {
        @Bean
        public ApplicationListener<ContextClosedEvent> closeEvent() {
            return new ApplicationListener<ContextClosedEvent>() {
                @Override
                public void onApplicationEvent(ContextClosedEvent event) {
                    System.out.println("child listener: " + event);
                }
            };
        }
    }

}

提供的测试将输出以下文本:

closing child now...
child listener: org.springframework.context.event.ContextClosedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@3f1b7a14: startup date [Mon Jul 21 15:25:23 BRT 2014]; parent: org.springframework.context.annotation.AnnotationConfigApplicationContext@94ac7e0]
parent listener: org.springframework.context.event.ContextClosedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@3f1b7a14: startup date [Mon Jul 21 15:25:23 BRT 2014]; parent: org.springframework.context.annotation.AnnotationConfigApplicationContext@94ac7e0]
closing parent now...
parent listener: org.springframework.context.event.ContextClosedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@94ac7e0: startup date [Mon Jul 21 15:25:22 BRT 2014]; root of context hierarchy]

在第一个关闭(子)中,两个侦听器都被执行。但是您可以通过event.getApplication ationContext()获取哪个上下文被关闭。

 类似资料:
  • 我是Spring的新手,如果我做了一些愚蠢的事情,请原谅我。我正在尝试为我的应用程序编写一个使用Spring的集成测试。 我正在创建一个上下文层次结构,如下所示 在我的测试方法中,我试图创建一个新的子上下文,它只有一个bean,它是一个应用程序侦听器,依赖于父方法中的bean。 我面临的问题是,来自子上下文的bean没有收到应用程序事件的通知,而且@Value注释也没有得到处理。 我到底做错了什么

  • 问题内容: 是否可以创建具有相同路由和相同中间件的,同时侦听HTTP和HTTPS的Express服务器? 目前,我在高速上的HTTP做到这一点,有安全通道隧道HTTPS来表达,但我更喜欢一个纯粹的节点解决方案。 我可以使用以下代码来做到这一点,但是可以使用标记为私有的方法: 问题答案: 您可以通过以下方式共享实现:

  • 我有一个使用子/父上下文关系的Spring应用程序。这样做的原因是为了确保子上下文从父上下文继承bean/资源,然后根据需要添加更多bean/资源来覆盖它们。但是,当子上下文关闭时(退出try/catch作用域),它开始对它引用的所有bean进行清理,包括父作用域中的bean。这是不可取的,因为我需要重用父上下文来创建另一个子上下文,但是现在它是垃圾,因为它包含了一堆已处理/关闭的bean。 问题

  • 我使用spring application builder从父应用程序运行子上下文应用程序(spring Cloud task)。我还将运行的父上下文传递给子应用程序的构建器——它定义了DataSource bean,我希望它也能被task使用。 我的问题是,当关闭子上下文时,所有父bean也会被销毁。我做错什么了吗? 如何将父上下文中的bean与子上下文共享,并且在子上下文退出时仍然保持它们的活

  • 我知道我需要在servlet上下文中注册用注释的类,以使webapp可访问。通常,我会按照以下方式来做: 我添加到根应用程序上下文中的所有其他配置类。下面是我的dispatcher初始值设定项通常的样子: 但是当我开始使用WebSockets时,事情变得更有趣了。要使websockets工作,必须将WebSoketConfig.class放到servlet上下文中。下面是我的WebSocketCo

  • 我多年来一直在使用Spring MVC,我试图理解与Spring Boot的一些关键区别。 你能帮我确认一下吗?或者让我明白我在这里遗漏了什么?