Monday, September 18, 2017

Spring Application Listener

How to use the Spring Application Listener

To set the application to use Spring Application listener, you can do the following.
1. In the application context file for Spring, declare a bean as follows

  <bean id="applicationEventMulticaster"
  class="org.springframework.context.event.SimpleApplicationEventMulticaster"/>
Actually, this may not needed at all. The following method is from Spring. By default, it will create a SimpleApplicationEventMulticaster if none is given.
   package org.springframework.context.support;

   public abstract class AbstractApplicationContext extends DefaultResourceLoader
  implements ConfigurableApplicationContext, DisposableBean {
   
   /**
  * Initialize the ApplicationEventMulticaster.
  * Uses SimpleApplicationEventMulticaster if none defined in the context.
  * @see org.springframework.context.event.SimpleApplicationEventMulticaster
  */
 protected void initApplicationEventMulticaster() {
  ConfigurableListableBeanFactory beanFactory = getBeanFactory();
  if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
   this.applicationEventMulticaster =
     beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
   if (logger.isDebugEnabled()) {
    logger.debug("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
   }
  }
  else {
   this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
   beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
   if (logger.isDebugEnabled()) {
    logger.debug("Unable to locate ApplicationEventMulticaster with name '" +
      APPLICATION_EVENT_MULTICASTER_BEAN_NAME +
      "': using default [" + this.applicationEventMulticaster + "]");
   }
  }
 }

...
}

2. Create Application listeners
import org.springframework.context.ApplicationListener;
@Component
public class MyEventListener implements ApplicationListener {
  ...
}

import org.springframework.context.ApplicationEvent;
public class MyEvent extends ApplicationEvent {
 ...
}


3. Just call the multicastEvent method of the bean applicationEventMulticaster when needed: applicationEventMulticaster.multicastEvent(event);

The method will invoke the application listeners to act on the event.

  public void multicastEvent(final ApplicationEvent event) {
  for (final ApplicationListener listener : getApplicationListeners(event)) {
   Executor executor = getTaskExecutor();
   if (executor != null) {
    executor.execute(new Runnable() {
     @SuppressWarnings("unchecked")
     public void run() {
      listener.onApplicationEvent(event);
     }
    });
   }
   else {
    listener.onApplicationEvent(event);
   }
  }
 }

How does the Application know what application listeners are listening?

At the application startup, the beans that implement the ApplicationListener interface will be automatically detected and registered.

ContextLoaderListener(ContextLoader).initWebApplicationContext(ServletContext)
->
XmlWebApplicationContext(AbstractApplicationContext).refresh();
->
DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory). applyBeanPostProcessorsAfterIniitalization
->
AbstractApplicationContext$ApplicationListenerDetector.postProcessAfterInitialization

public Object postProcessAfterInitialization(Object bean, String beanName) {
   if (bean instanceof ApplicationListener) {
    // potentially not detected as a listener by getBeanNamesForType retrieval
    Boolean flag = this.singletonNames.get(beanName);
    if (Boolean.TRUE.equals(flag)) {
     // singleton bean (top-level or inner): register on the fly
     addApplicationListener((ApplicationListener) bean);
    }
    else if (flag == null) {
     if (logger.isWarnEnabled() && !containsBean(beanName)) {
      // inner bean with other scope - can't reliably process events
      logger.warn("Inner bean '" + beanName + "' implements ApplicationListener interface " +
        "but is not reachable for event multicasting by its containing ApplicationContext " +
        "because it does not have singleton scope. Only top-level listener beans are allowed " +
        "to be of non-singleton scope.");
     }
     this.singletonNames.put(beanName, Boolean.FALSE);
    }
   }
   return bean;
  }

public void addApplicationListener(ApplicationListener listener) {
  if (this.applicationEventMulticaster != null) {
   this.applicationEventMulticaster.addApplicationListener(listener);
  }
  else {
   this.applicationListeners.add(listener);
  }
 }