Search This Blog

Saturday, 14 July 2012

Spring and Events and Listeners

Spring's ApplicationContext provides the functionality to support events and listeners in code. Spring allows us to create listeners in the code. We can create beans that listen for events which are published through our ApplicationContext. This is achieved via the ApplicationEventPublisher interface.
public interface ApplicationEventPublisher {

    /**
     * Notify all listeners registered with this application of an application
     * event. Events may be framework events (such as RequestHandledEvent)
     * or application-specific events.
     * @param event the event to publish
     * @see org.springframework.web.context.support.RequestHandledEvent
     */
    void publishEvent(ApplicationEvent event);
} 
The interface exposes a single method - one that allows us to publish events. The ApplicationContext implements the above interface. 
Now to create the listeners.
The first step was to create some beans that can listen to events:
public class AllApplicationEventListener implements
        ApplicationListener<ApplicationEvent> {

    @Override
    public void onApplicationEvent(ApplicationEvent applicationEvent) {
        System.out.println(" AllApplicationEventListener (with hashcode) "
                + this.hashCode() + "\n received "
                + applicationEvent.getClass() + "\n at "
                + formatDate(applicationEvent.getTimestamp())
                + "\n with Source as "
                + applicationEvent.getSource().getClass());
    }
}
For listening to events that are published a class needs o implement the ApplicationListener interface.
The above class listens for all events generated by the ApplicationContext. The presence of generics in the Listener interface allows us to listen for very specific events. In this case our listener listens to all events of type ApplicationEvent.
Let us look at one more Listener:
public class ContextStartedEventListener implements
        ApplicationListener<ContextStartedEvent> {

    @Override
    public void onApplicationEvent(ContextStartedEvent event) {
        System.out.println(" ContextStartedEventListener received "
                + event.getClass() + "\n at "
                + formatDate(event.getTimestamp()) + "\n with Source as "
                + event.getSource().getClass() + "\n for application context "
                + event.getApplicationContext().getClass());

    }
}
The ContextStartedEvent is one of the events generated by the ApplicationContext. It extends the generic (and abstract) ApplicationEvent.
Spring is not restricted to using these predefined events. It allows us to define and publish Custom events too. I created one such custom event.
public class CustomMsgEvent extends ApplicationEvent {
    final String msg;

    public String getMsg() {
        return msg;
    }

    public CustomMsgEvent(Object source, final String msg) {
        super(source);
        this.msg = msg;
        System.out.println("Created a Custom event");
    }

    @Override
    public String toString() {
        return "CustomMsgEvent msg: " + this.msg;
    }

}
The base class defines a constructor which takes an object as a parameter.
As per the code comments this is
the component that published the event
I created a listener to listen for the custom event.
public class CustomEventListener implements ApplicationListener<CustomMsgEvent> {

    @Override
    public void onApplicationEvent(CustomMsgEvent applicationEvent) {
        System.out.println(" CustomEventListener received "
                + applicationEvent.getClass() + "\n at "
                + formatDate(applicationEvent.getTimestamp())
                + "\n with Source as "
                + applicationEvent.getSource().getClass());
        System.out.println("The message is " + applicationEvent.getMsg());
    }
}
Now to run the code.
The spring configuration file is as below:
<?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="allAppEventListener" class="com.listener.AllApplicationEventListener"/>    
    <bean id="contextStartedEventListener" class="com.listener.ContextStartedEventListener"/>    
    <bean id="customEventListener" class="com.listener.CustomEventListener"/>
</beans>
We have defined our listeners as beans in the configuration file. The test code is as below:
public class Test {
    public static void main(String[] args) {
        ApplicationContext applicationContext = 
               new ClassPathXmlApplicationContext("spring-beans.xml");
        System.out.println("Application context has been started - publishing message");
        
        CustomMsgEvent customMsgEvent = new CustomMsgEvent(applicationContext, 
                    "Test message");
        applicationContext.publishEvent(customMsgEvent); 
        //-blocking call i.e. synchronous        
        System.out.println("The custom event was published successfully");
        
        System.out.println("Starting the applicationContext");
        ((ConfigurableApplicationContext)applicationContext).start();
        
        System.out.println("Stopping the applicationContext");
        ((ConfigurableApplicationContext)applicationContext).stop();
    }
}
On running the code the logs are:
875  [main] DEBUG org.springframework.context.support.ClassPathXmlApplicationCon
text  - Unable to locate ApplicationEventMulticaster with name 'applicationEvent
Multicaster': using default [org.springframework.context.event.SimpleApplication
EventMulticaster@15a0305]
...
1000 [main] DEBUG org.springframework.context.support.ClassPathXmlApplicationContext  
- Publishing event in org.springframework.context.support.ClassPathXmlApplicationContext@b179c3: 
org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.
context.support.ClassPathXmlApplicationContext@b179c3: startup date [
Sun Dec 02 15:04:19 IST 2012]; root of context hierarchy]

 AllApplicationEventListener (with hashcode) 13238549
 received class org.springframework.context.event.ContextRefreshedEvent
 at 3:04:20 PM
 with Source as class org.springframework.context.support.ClassPathXmlApplicationContext
Application context has been started - publishing message
...
Created a Custom event
1016 [main] DEBUG org.springframework.context.support.ClassPathXmlApplicationContext
- Publishing event in org.springframework.context.support.ClassPathXmlApplicationContext@b179c3:
CustomMsgEvent msg: Test message

 AllApplicationEventListener (with hashcode) 13238549
 received class com.listener.CustomMsgEvent
 at 3:04:20 PM
 with Source as class org.springframework.context.support.ClassPathXmlApplicationContext
 CustomEventListener received class com.listener.CustomMsgEvent
 at 3:04:20 PM
 with Source as class org.springframework.context.support.ClassPathXmlApplicationContext
The message is Test message
The custom event was published successfully
...
Starting the applicationContext
1031 [main] DEBUG org.springframework.context.support.ClassPathXmlApplicationContext  
- Publishing event in org.springframework.context.support.ClassPathXmlApplicationContext@b179c3:
org.springframework.context.event.ContextStartedEvent[source=org.springframework.
context.support.ClassPathXmlApplicationContext@b179c3: startup date [
Sun Dec 02 15:04:19 IST 2012]; root of context hierarchy]
 AllApplicationEventListener (with hashcode) 13238549
 received class org.springframework.context.event.ContextStartedEvent
 at 3:04:20 PM
 with Source as class org.springframework.context.support.ClassPathXmlApplicationContext
 ContextStartedEventListener received class org.springframework.context.event.ContextStartedEvent
 at 3:04:20 PM
 with Source as class org.springframework.context.support.ClassPathXmlApplicationContext
 for application context class org.springframework.context.support.ClassPathXmlApplicationContext
...
Stopping the applicationContext
1031 [main] DEBUG org.springframework.context.support.ClassPathXmlApplicationContext  
- Publishing event in org.springframework.context.support.ClassPathXmlApplicationContext@b179c3:
org.springframework.context.event.ContextStoppedEvent[source=org.springframework.
context.support.ClassPathXmlApplicationContext@b179c3: startup date [
Sun Dec 02 15:04:19 IST 2012]; root of context hierarchy]
 AllApplicationEventListener (with hashcode) 13238549
 received class org.springframework.context.event.ContextStoppedEvent
 at 3:04:20 PM
 with Source as class org.springframework.context.support.ClassPathXmlApplicationContext
The logs in red are console statements while those in blue are Spring logs. The conclusions to be drawn from above are:
  1. When the Application context published events, they were received by the AllApplicationEventListener.
  2. On publishing the ContextStartedEvent, it was received by both the listeners.
  3. As can be seen from the logs the event publishing mechanism is synchronous. Only after the onApplicationEvent method of all the listeners had executed did the publishEvent method of the ApplicationContext complete.
  4. If there was any transactional context, then all our listener methods would have executed within the same.
  5. It may often be necessary that we need an asynchronous kind of flow. The Container publishes the event and simply moves on. It does not wait for the listener methods to complete execution. This can be achieved by the SimpleApplicationEventMulticaster class.

No comments:

Post a Comment