1
votes

I have a question about the nature of Spring's ApplicationListener in regards to Parent and Child Contexts. Let's say you create a Parent Context which creates a bean that is a singleton and is registered as an ApplicationListener. Then later a Child Context is created using the Parent Context. When closing the Child Context Spring will send out a ContextClosedEvent. Does this event get propagated to the Parent Context as well causing all of the Parent Context singletons who are ApplicationListeners to receive the event?

I noticed in the documentation that a [ContextClosedEvent]: (http://docs.spring.io/spring/docs/4.0.6.RELEASE/spring-framework-reference/htmlsingle/#context-functionality-events) , "Published when the ApplicationContext is closed, using the close() method on the ConfigurableApplicationContext interface. "Closed" here means that all singleton beans are destroyed. A closed context reaches its end of life; it cannot be refreshed or restarted."

Essentially what I am asking is Event Publishing confined to specific Child Contexts, or does it propagate throughout all Parent/Child Contexts?

2

2 Answers

2
votes

All listeners are invoked but the parameter, in this case a ContextClosedEvent, will point to the context that is being closed.

The following test creates a parent context, child context, starts them, closes the parent and then the child.

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

}

The test provided will output the following text:

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]

In the first close (child), both listeners are executed. But you can get which context is being closed with event.getApplicationContext().

1
votes

Yes, the Spring application contexts do propagate events from the child context to all its ancestors recursively.

The following AbstractApplicationContext code takes responsibility for this:

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

As you see in the end of the publishMethod body, if there is a parent then any event will be propagated to it too.

On the contrary, events from the parent won't be propagated to the children since the parent doesn't know about the children.

If you don't want to propagate events, you can simply do this (instead of setting a parent to your child context):

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

Or, if your child context is a GenericApplicationContext (AnnotationConfigApplicationContext, GenericGroovyApplicationContext, GenericXmlApplicationContext, StaticApplicationContext), you can even simplify more:

yourChildApplicationContext.getDefaultListableBeanFactory()
   .setParentBeanFactory(parentContext.getBeanFactory());