【问题标题】:Spring Parent Child Context and Application ListenersSpring 父子上下文和应用程序侦听器
【发布时间】:2014-07-21 16:30:11
【问题描述】:

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

我在文档中注意到 [ContextClosedEvent]: (http://docs.spring.io/spring/docs/4.0.6.RELEASE/spring-framework-reference/htmlsingle/#context-functionality-events) ,“在 ApplicationContext 关闭时发布,使用 ConfigurableApplicationContext 接口上的 close() 方法。这里的“关闭”意味着所有单例 bean 都被销毁. 一个封闭的上下文走到了生命的尽头;它不能被刷新或重新启动。”

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

【问题讨论】:

    标签: java spring


    【解决方案1】:

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

    以下测试创建一个父上下文、子上下文,启动它们,关闭父上下文,然后关闭子上下文。

    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.getApplicationContext() 获取正在关闭的上下文。

    【讨论】:

    • 我问的原因是我遇到了一个有趣的情况。我有一个在 Spring 父上下文中创建的 Apache Camel 上下文。然后创建、使用和关闭 Spring Child Context。 ContextClosedEvent 被父上下文中的骆驼路由捕获,这会导致路由关闭。不是 CamelContext,而是各个 Routes。因此,似乎事件是从子上下文发送到父上下文的。这是我用来模拟此问题的示例项目。 github.com/pminearo/camel-shutdown-test
    • 如果先关闭子上下文会发生什么?
    • 不确定骆驼如何处理上下文关闭,但是如果您关闭子上下文,所有侦听器都会收到有关上下文关闭的通知,即使是父侦听器也会收到它。但关键是,在这种情况下 (org.springframework.context.event.ContextClosedEvent),您会收到正在关闭的上下文作为参数,从而能够区分父级或子级是否正在关闭。
    【解决方案2】:

    是的,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());
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-11-28
      • 2014-12-30
      • 1970-01-01
      • 2018-01-01
      相关资源
      最近更新 更多