In the previous post we saw how Spring's ApplicationContext provided for event publishing and listening. The default listener that Spring implements is synchronous. The publish method will wait until the methods of all the listeners have completed successfully. This kind of blocking behaviour may not be suitable for all scenarios.
Hence Spring provides us with an alternative strategy:
Hence Spring provides us with an alternative strategy:
- Use an implementation of the ApplicationEventMulticaster that supports asynchronous publishing.
- Create a bean of the same with id "applicationEventMulticaster"
- At start-up, if the ApplicationContext detects this bean, then it uses it for its Event publishing model.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <bean id="customEventListener" class="com.listener.CustomEventSleepListener" /> <bean id="applicationEventMulticaster" class="org.springframework.context.event.SimpleApplicationEventMulticaster"> <property name="taskExecutor" > <bean class="org.springframework.core.task.SimpleAsyncTaskExecutor"> </bean> </property> </bean> </beans>
- The ApplicationContext inherits from the AbstractApplicationContext. It includes the below method that sets up the applicationEventMulticaster
/** * 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 + "]"); } } }
- If no ApplicationEventMulticaster is specified Spring creates a SimpleApplicationEventMulticaster. The default behaviour of the class is to publish events synchronously. This is how it worked in our previous post.
- However if we specify our own ApplicationEventMulticaster bean with id "applicationEventMulticaster", the ApplicationContext uses our instance. This is what we have done above.
- In case of SimpleApplicationEventMulticaster, if we specify our own Executor, then we can make it work in an asynchronous manner. In this case I have specified SimpleAsyncTaskExecutor as the executor to be used. This is a simple thread based executor that provides us with the needed asynchronous behaviour.
203 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanF actory - Creating shared instance of singleton bean 'applicationEventMulticaster' 234 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanF actory - Creating instance of bean 'org.springframework.core.task.SimpleAsyncTa skExecutor#f99ff5' ... 250 [main] DEBUG org.springframework.beans.CachedIntrospectionResults - Found bean property 'taskExecutor' of type [java.util.concurrent.Executor] 250 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanF actory - Finished creating instance of bean 'applicationEventMulticaster'To test the asynchronous behavior I created a listener that takes it own sweet time to complete.
public class CustomEventSleepListener implements ApplicationListener<CustomMsgEvent> { @Override public void onApplicationEvent(CustomMsgEvent applicationEvent) { System.out.println("AllApplicationEventListener " + " received applicationEvent - start"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }//sleep for 3 seconds System.out.println("AllApplicationEventListener " + " received applicationEvent - done"); } }As can be seen, the thread actually sleeps for three seconds. Consider the test code:
public class TestAsync { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-async-publish.xml"); System.out.println("Application context has been started - publishing message"); CustomMsgEvent customMsgEvent = new CustomMsgEvent(applicationContext, "Test message"); applicationContext.publishEvent(customMsgEvent); //-should not block here System.out.println("The event was published successfully"); } }The output is as below:
Application context has been started - publishing message
Created a Custom event
The event was published successfully
AllApplicationEventListener received applicationEvent - start AllApplicationEventListener received applicationEvent - doneAs can be seen the publish method finished immediately without waiting for the listeners to complete execution.
You should have a look at Spring Integration which provide better event publishing and listening.
ReplyDeleteThanks Nicolas, I'll give that a look. However if ApplicationContext's mechanism satisfies requirements then Spring Integration would probably be an over kill.
DeleteCan we define the behaviour? sometimes we want to have it synchronously... Thank you for your post.
ReplyDeleteHi,
ReplyDeleteThe ApplicationContext by default supports synchronous event publication. You can directly use that - http://learningviacode.blogspot.in/2012/07/spring-and-events-and-listeners.html
Thank you for your quick response Mr. Varghese. What I would mean was that I would have both (synchronous and asynchronous events) in the same project. I have 3 events: 2 need to be synchronous and 1 asynchronous. Any recommendation? Thank you for your time.
DeleteHi Fran,
DeleteYour requirements seem to lean more towards synchronous flow then asynchronous. But I guess that could change in the future.
A simple solution would to be use the synchronous mode of publishing events. Where asynchronous response is needed, execute the code of the listeners on a separate thread.
As far as I am aware making the same applicationContext delegate in both synchronous and asynchronous manner is not supported.
Cheers
This is so interesting blog. You are best listing knowledge provide at this site. I am very excited read this nice article. You can visit my website.
ReplyDeleteProperty Development Events
Spring supports either sync or async event publishing in an ApplicationContext. Is there any way to make it support both sync and async event emitting in single context?
ReplyDeleteAs far as I am aware no :(. You can see how the application context is wired to use a single Event Publisher. What you can do is override that class.....
DeleteI think the best way to achieve both synchronous and asynchronous support would be to first chose for asynchronous and replace the default SimpleAsyncTaskExecutor by one that would dispatch task execution to threads that you could control by using a latch. In that case you would either wait for the latch to finish for synchronous behaviour and simply not wait for asynchronous behaviour.
DeleteOne other way would be to implement your own ApplicationEventMulticaster which would dispatch synchronous and asynchronous events handling to some specific handler (one would handle the message in the same thread, the other would create a new non-blocking thread)
Here is a post that shows how to do just that:
Deletehttp://www.keyup.eu/en/blog/101-synchronous-and-asynchronous-spring-events-in-one-application
This comment has been removed by a blog administrator.
ReplyDelete