Eventing with Spring Framework

Spring Framework, since it’s inception, included an eventing mechanism which can be used for application-wide eventing. This eventing mechanism was developed to be used internally by Spring Framework for eventing, such as notification of context being refreshed, etc, but it can be used for application specific custom events as well. This eventing API is based on  an interface named {java}org.springframework.context.ApplicationListener{/java}, which defined one method named {java}onApplicationEvent{/java}. Below code snippet shows a simple events listener which just logs the event information.

package com.yohanliyanage.blog.springevents;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;

public class MyEventListener implements ApplicationListener {

	private static final Log LOG = LogFactory.getLog(MyEventListener.class);
	
	public void onApplicationEvent(ApplicationEvent event) {
		LOG.info("Event Occurred : " + event);
	}
}

To register this event listener, all that we have to do is to add it as a Spring managed bean. If we just add it as a bean in Spring Bean Configuration XML, or if we have annotation scanning enabled, adding an annotation such as @Component, would ensure that our listener will receive events via Spring. Below XML block shows the simple bean definition in XML for registering this listener.

< ?xml version="1.0" encoding="UTF-8"?>


	
	
	

Now, to test this code, let’s write up a main method which creates the Spring Application Context. In this code, it’s assumed that the Spring bean definition file is located at {java}META-INF/spring/application-context.xml{/java}, which is in class path. You can download the sample code here.

public class Main {

	public static void main(String[] args) throws InterruptedException {
		ApplicationContext context = 
			new ClassPathXmlApplicationContext("classpath:META-INF/spring/application-context.xml");
	}
}

When we run this, we get the following output.

18:45:00 INFO Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@7a982589: startup date [Sun Sep 30 18:45:00 IST 2012]; root of context hierarchy
18:45:00 INFO Loading XML bean definitions from class path resource [META-INF/spring/application-context.xml]
18:45:00 INFO Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@33e228bc: defining beans [com.yohanliyanage.blog.springevents.MyEventListener#0]; root of factory hierarchy
18:45:00 INFO Event Occurred : org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.support.ClassPathXmlApplicationContext@7a982589: startup date [Sun Sep 30 18:45:00 IST 2012]; root of context hierarchy]

As highlighted above, our listener gets notified by the framework when Spring Context is refreshed during initialization. This is a framework event, and of course, there’s no magic to it. But how can we generate application specific, custom events? As you will see in the blow code block, this is also very simple and straight-forward.

First, let’s create our own event implementation. For this, we just have to write a class that extends from {java}ApplicationEvent{/java}.

package com.yohanliyanage.blog.springevents;

import org.springframework.context.ApplicationEvent;

public class MyCustomEvent extends ApplicationEvent {

	private static final long serialVersionUID = -5308299518665062983L;

	public MyCustomEvent(Object source) {
		super(source);
	}
}

Next, we have to write a class which does the event publishing. In order to publish an event, we need to get a reference to {java}ApplicationEventPublisher{/java}. This can be easily done by implementing the {java}ApplicationEventPublisherAware{/java}, as shown in the code block below.

package com.yohanliyanage.blog.springevents;

import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;

public class MyEventPublisher implements ApplicationEventPublisherAware {

	private ApplicationEventPublisher publisher;
	
	public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
		this.publisher = publisher;
	}

	public void publish() {
		this.publisher.publishEvent(new MyCustomEvent(this));
	}
}

Now, this bean also should be added to the Spring bean configuration, as follows. Note that you do not have to write a separate publisher class in your application. Your existing Spring beans can easily to this by just implementing the {java}ApplicationEventPublisherAware{/java} interface.


Now, let’s slightly modify the Main class to invoke the publish() method in our event publisher.

	public static void main(String[] args) throws InterruptedException {
		ApplicationContext context = new ClassPathXmlApplicationContext("classpath:META-INF/spring/application-context.xml");
		
		MyEventPublisher publisher = context.getBean(MyEventPublisher.class);
		publisher.publish();
	}

When we run the application now, we get the following output.

19:21:18 INFO Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@7a982589: startup date [Sun Sep 30 19:21:18 IST 2012]; root of context hierarchy
19:21:18 INFO Loading XML bean definitions from class path resource [META-INF/spring/application-context.xml]
19:21:19 INFO Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@2565a3c2: defining beans [com.yohanliyanage.blog.springevents.MyEventListener#0,com.yohanliyanage.blog.springevents.MyEventPublisher#0]; root of factory hierarchy
19:21:19 INFO Event Occurred : org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.support.ClassPathXmlApplicationContext@7a982589: startup date [Sun Sep 30 19:21:18 IST 2012]; root of context hierarchy]
19:21:19 INFO Event Occurred : com.yohanliyanage.blog.springevents.MyCustomEvent[source=com.yohanliyanage.blog.springevents.MyEventPublisher@5a676437]

As highlighted above, our custom event has been triggered, and our listener was able to handle that event.

So far, so good. But if you have noticed, our listener gets invoked for all of the events that occurs in the application, including framework events. But in most of the cases, this is not desirable. The listener will be interested in one or more specific events. Up until Spring 3.0, this eventing mechanism did not had support for filtering events. That is, if you implement an {java}ApplicationListener{/java}, you would end up receiving all events that occurs in the application, and you had to manually look into the ApplicationEvent object that gets passed in to your listener to identify and discard events that you are not interested in. This of course, was a hassle, and probably due to this, Spring Eventing did not get attention of most of the developers.

With Spring 3.0, this API was enhanced with Generics support, to provide filtering of events. Now, the ApplicationListener interface is parameterized as follows.

public interface ApplicationListener < E extends ApplicationEvent > extends EventListener

The {java}onApplicationEvent{/java} method parameter uses generic type E. With this, we can implement our listener as follows.

[java]
package com.yohanliyanage.blog.springevents;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationListener;

public class MyEventListener implements ApplicationListener < MyCustomEvent > {

private static final Log LOG = LogFactory.getLog(MyEventListener.class);

public void onApplicationEvent(MyCustomEvent event) {
LOG.info(“Event Occurred : ” + event);
}

}
[/java]

This listener implementation’s {java}onApplicationEvent{/java} method will be called only for {java}MyCustomEvent{/java} based events. This in turn provides the necessary event filtering, where we don’t have to write boilerplate code to discard unnecessary events. The log output is as follows.

19:29:31 INFO Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@7a982589: startup date [Sun Sep 30 19:29:31 IST 2012]; root of context hierarchy
19:29:31 INFO Loading XML bean definitions from class path resource [META-INF/spring/application-context.xml]
19:29:31 INFO Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@2565a3c2: defining beans [com.yohanliyanage.blog.springevents.MyEventListener#0,com.yohanliyanage.blog.springevents.MyEventPublisher#0]; root of factory hierarchy
19:29:31 INFO Event Occurred : com.yohanliyanage.blog.springevents.MyCustomEvent[source=com.yohanliyanage.blog.springevents.MyEventPublisher@5a676437]

As seen above in the output, we no longer receive the unwanted framework specific events, or any other events that we are not interested in.

So in conclusion, Spring does provide a decent eventing mechanism which is quite useful for implementing eventing support in applications. While this has been around since the early days of Spring, it did not see wide adoption primarily due to it’s incapability of filtering out specific events for a listener. But with Spring 3.0, things are improved, and now it has reached a state where we can leverage it to broadcast events in our applications with ease. One thing to note is that by default, Spring Eventing is synchronous. But this can be made asynchronous by providing a custom {java}ApplicationEventMulticaster{/java} implementation that would make use of a {java}TaskExecutor{/java}.


5 comments

  1. Hi Yohan,
    Nice post. A follow up on the performance benchmark with a few 000′s msgs per second processing and how Spring events handle such a load would be beneficial if you get the time.

    Again thx for Sharing… Very useful

    Cheers

    Dinuka

  2. Enjoy your post.

    Is there a way to have asynchronous and synchronous event listeners in the same application context? If the default synchronous events are changed to asynchronous ones, what kind of impact does it have to a big web app with hundreds of beans configured?

    Thanks!

    1. Hi,

      Spring allows you to have only one ApplicationEventMulticaster. But you could write your own ApplicationEventMulticaster implementation which would dispatch certain events synchronously, and other events asynchronously (or vice versa). For example, if you want to dispatch all events of type ‘Foo’ synchronously, and rest of the events asynchronously, then you can write your implementation to analyze the event type, and call the event listeners in a single thread (synchronous) or multiple threads (asynchronous).

      Regarding the second question, it depends on your implementation of the multicaster, and also your event listener implementation. For example, you could have a multicaster which uses only 10 threads to dispatch (thus at most 10 event listeners will get called concurrently). But if you write a multicaster which creates unlimited number of threads, then definitely your performance will go down after a certain point. This depends on how you implement your event listeners as well. For example, if your event listeners are heavy, then invoking a large number of such event listeners will also result in degradation of performance. So in a nutshell, you should use a thread pool (such as a TaskExecutor) which uses a limited number of threads to dispatch events asynchronously. The exact number of threads you should use depends on your situation, so probably you will have to analyze and find out the ideal number.

      On a side note, if you are going to use asynchronous events with Spring, I would suggest looking at the Spring’s SimpleApplicationEventMulticaster class. Also, one thing to note is that in an Application Server, creating your own threads is not recommended because the threads will not be managed by the container. In such a situation, you should obtain threads from the container’s managed thread pool, but that of course is dependent on the container.

  3. This was very helpful.
    Simple step by step explanation. I was able to write a spring event based code and get it to work.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>