Problem
In the typical communication model between components, the sender has to locate the receiver to call a method on it. In this case, the sender component must be aware of the receiver component. This kind of communication is direct and simple, but the sender and receiver components are tightly coupled.
When using an IoC container, your components can communicate by interface rather than by implementation. This communication model can help reduce coupling. However, it is only efficient when a sender component has to communicate with one receiver. When a sender needs to communicate with multiple receivers, it has to call the receivers one by one.
Solution
Spring’s application context supports event-based communication between its beans. In the event-based communication model, the sender component just publishes an event without knowing who the receiver will be. Actually, there may be more than one receiver component. Also, the receiver needn’t know who is publishing the event. It can listen to multiple events from different senders at the same time. In this way, the sender and receiver components are loosely coupled.
In Spring, all event classes must extend the ApplicationEvent class. Then any bean can publish an event by calling an application event publisher’s publishEvent() method. For a bean to listen to certain events, it must implement the ApplicationListener interface and handle the events in the onApplicationEvent() method. Actually, Spring will notify a listener of all events, so you must filter the events by yourself.
How It Works
Defining Events
The first step of enabling event-based communication is to define the event. Suppose you would like your cashier bean to publish a CheckoutEvent after the shopping cart has been checked out. This event includes two properties: the payment amount and the checkout time. In Spring, all events must extend the abstract class ApplicationEvent and pass the event source as a constructor argument.
package com.shop; … import org.springframework.context.ApplicationEvent; public class CheckoutEvent extends ApplicationEvent { private double amount; private Date time; public CheckoutEvent(Object source, double amount, Date time) { super(source); this.amount = amount; this.time = time; } public double getAmount() { return amount; } public Date getTime() { return time; } }
Publishing Events
To publish an event, you just create an event instance and make a call to the publishEvent() method of an application event publisher, which can be accessed by implementing the ApplicationEventPublisherAware interface.
package com.shop; … import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; public class Cashier implements BeanNameAware, MessageSourceAware, ApplicationEventPublisherAware, StorageConfig { … private ApplicationEventPublisher applicationEventPublisher; … public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.applicationEventPublisher = applicationEventPublisher; } public void checkout(ShoppingCart cart) throws IOException { … CheckoutEvent event = new CheckoutEvent(this, total, new Date()); applicationEventPublisher.publishEvent(event); } }
Listening to Events
Any bean defined in the application context that implements the ApplicationListener interface will be notified of all events. So in the onApplicationEvent() method, you have to filter the events that your listener wants to handle. In the following listener, suppose you would like to send an e-mail to the customer notifying them about the checkout.
package com.shop; … import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; public class CheckoutListener implements ApplicationListener { public void onApplicationEvent(ApplicationEvent event) { if (event instanceof CheckoutEvent) { double amount = ((CheckoutEvent) event).getAmount(); Date time = ((CheckoutEvent) event).getTime(); // Do anything you like with the checkout amount and time System.out.println("Checkout event [" + amount + ", " + time + "]"); } } }
Next, you have to register this listener in the application context to listen for all events. The registration is as simple as declaring a bean instance of this listener. The application context will recognize the beans that implement the ApplicationListener interface and notify them of each event.
<beans …> … <bean class="com.shop.CheckoutListener" /> … </beans>
Finally, notice that the application context itself will also publish container events such as ContextClosedEvent, ContextRefreshedEvent, and RequestHandledEvent. If any of your beans want to be notified of these events, they can implement the ApplicationListener interface.