Tuesday, 7 June 2011

Spring's Application Context Event System

Spring provides a simple and lightweight publish/subscribe or Observer mechanism built around the ApplicationContext. This is a synchronous mechanism in that the event listener code is called during the call to the publishing code.

If you’ve seen this before, you may not know that this system has been updated in the Spring 3 release to use Generics. The change is to the ApplicationListener interface, which has become the ApplicationListener<E extends ApplicationEvent> interface with the result that messages can now be directed to one (or multiple) listeners rather than having to be sent to all listeners.

The steps required to create an Application Context Event system are:

1) Create the listener by extending ApplicationListener<E extends ApplicationEvent>. This is the message receiver or observer.

public class MyMessageEventListener implements ApplicationListener<MyMessageEvent>,
    BeanNameAware
{

 
private static final Log log = LogFactory.getLog(MyMessageEventListener.class);

 
private String name;

 
/**
   * Implementation of the ApplicationListener
   */
 
public void onApplicationEvent(MyMessageEvent event) {

   
log.info("Bean [" + name + "] Received event: " + event.getMessage());

 
}

 
public void setBeanName(String name) {

   
this.name = name;
 
}
}

2) Create your event class. This is a class that extends ApplicationEvent

public class MyMessageEvent extends ApplicationEvent {

 
private final static long serialVersionUID = 1L;

 
private final String msg;

 
public MyMessageEvent(Object source, String msg) {

   
super(source);
   
this.msg = msg;
 
}

 
public String getMessage() {

   
return msg;
 
}
}

Note that the constructor of this object's superclass needs a source argument. This is supposed to be a reference to the publisher object, but it can be anything and I have seen examples where the ApplicationContext has been used and it still works.

3) Create a publisher. This means that you should write a class that extends ApplicationEventAwarePublisher or ApplicationContextEventAware. ApplicationEventAwarePublisher provides the same functionality as ApplicationContextEventAware in this instance as the injected context has an appropriate publish(...) method. The major difference is that ApplicationContextEventAware is a general interface covering all the functionality of the ApplicationContext, whilst ApplicationEventAwarePublisher only implements event publishing.

Below are examples of both types of publisher class and you can see that they are very similar.

The ApplicationContextAware code:

public class ApplicationContextAwarePublisher implements ApplicationContextAware,
    BeanNameAware, MyPublisherInterface
{

 
private static final Log log = LogFactory.getLog(ApplicationContextAwarePublisher.class);

 
private String beanName = "Unknown Bean";

 
private ApplicationContext ctx;

 
/**
   * Implementation of the BeanNameAware interface
   */
 
public void setBeanName(String name) {

   
this.beanName = name;
    log.info
("Setting bean name " + name);
 
}

 
/**
   * Implement the ApplicationContextAware interface
   */
 
public void setApplicationContext(ApplicationContext ctx) {

   
this.ctx = ctx;
    log.info
("Bean [" + beanName + "] setting application context: " + ctx);
 
}

 
/**
   * This is the implementation of the MyPublisherInterface
   */
 
public void publish(String messageToPublish) {

   
log.info("Bean [" + beanName + "] Publishing message: " + messageToPublish);
    ctx.publishEvent
(new MyMessageEvent(this, messageToPublish));
 
}
}

The ApplicationEventAwarePublisher code:

public class ApplicationEventAwarePublisher implements ApplicationEventPublisherAware, BeanNameAware,
    MyPublisherInterface
{

 
private static final Log log = LogFactory.getLog(ApplicationEventAwarePublisher.class);

 
private String beanName = "Unknown Bean";

 
private ApplicationEventPublisher eventPublisher;

 
/**
   * Implementation of the BeanNameAware interface
   */
 
public void setBeanName(String name) {

   
this.beanName = name;
    log.info
("Setting bean name " + name);
 
}

 
/**
   * This is the implementation of the MyPublisherInterface
   */
 
public void publish(String messageToPublish) {

   
log.info("Bean [" + beanName + "] Publishing message: " + messageToPublish);
    eventPublisher.publishEvent
(new MyMessageEvent(this, messageToPublish));
 
}

 
/**
   *
@see org.springframework.context.ApplicationEventPublisherAware#setApplicationEventPublisher(org.springframework.context.ApplicationEventPublisher)
   */
 
@Override
 
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
   
this.eventPublisher = publisher;
    log.info
("Bean [" + beanName + "] setting application context: " + publisher);
 
}
}

You'll notice that both the listener and publisher classes implement BeanNameAware, which is a little superfluous in this example, but demonstrates that it's a good idea to add in some method of identifying your beans during debugging.

The XML config for this sample code is also very straight forward and contains nothing special or tricky:

<?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">
 <!-- Create a publisher bean that uses ApplicationContextAware bean -->
 <bean id="context-aware-publisher" class="example_3_spring_aware.application_context_events.ApplicationContextAwarePublisher"/>

 <!-- Create a publisher bean that uses ApplicationEventPublisherAware bean -->
 <bean id="event-publisher-aware-publisher" class="example_3_spring_aware.application_context_events.ApplicationEventAwarePublisher"/>
 
 <!-- Create the listener --> 
 <bean id="messageEventListener" class="example_3_spring_aware.application_context_events.MyMessageEventListener"/>
</beans>

Finally, the following code demonstrates how to use the Application Contet Event publish/subscribe pattern.

    ApplicationContext ctx = new FileSystemXmlApplicationContext(
       
Constants.PATH_TO_RESOURCES + "example3_ApplicationContext_Events.xml");

    MyPublisherInterface bean = ctx.getBean
("context-aware-publisher",
        MyPublisherInterface.
class);

    bean.publish
("This is my first message");
    bean.publish
("This is my second message");

    MyPublisherInterface bean2 = ctx.getBean
("event-publisher-aware-publisher",
        MyPublisherInterface.
class);
    bean2.publish
("This is a third message");

No comments: