Platinum Partner
java,framework,spring

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 org.springframework.context.ApplicationListener, which defined one method named onApplicationEvent. 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"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

<!-- Adding the listener class to Spring Container automatically registers it for events -->
<bean class="com.yohanliyanage.blog.springevents.MyEventListener" />

</beans>

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 META-INF/spring/application-context.xml, 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 ApplicationEvent.

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 ApplicationEventPublisher. This can be easily done by implementing the ApplicationEventPublisherAware, 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 ApplicationEventPublisherAware interface.

<bean class="com.yohanliyanage.blog.springevents.MyEventPublisher" />

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 ApplicationListener, 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 onApplicationEvent method parameter uses generic type E. With this, we can implement our listener as follows.

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

}

This listener implementation’s onApplicationEvent method will be called only for MyCustomEvent 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 ApplicationEventMulticaster implementation that would make use of a TaskExecutor.


 

 

 

 

 

 

 

 

 

 

 

 

Published at DZone with permission of {{ articles[0].authors[0].realName }}, DZone MVB. (source)

Opinions expressed by DZone contributors are their own.

{{ tag }}, {{tag}},

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}
{{ parent.authors[0].realName || parent.author}}

{{ parent.authors[0].tagline || parent.tagline }}

{{ parent.views }} ViewsClicks
Tweet

{{parent.nComments}}