Last week we released Apache Shiro 1.3, and I shared a tutorial on the new Hazelcast support. Today, I’d like to introduce you to the new EventBus
system and show you a couple different ways to use it. Shiro’s EventBus
is implemented very similar to Guava’s EventBus, if you are already familiar with that, you already know how to use it.
EventBus in Apache Shiro
An event bus allows for further decoupling of components when compared to the observable pattern. It differs in that it shifts the notification of event listeners from the observable object itself to the EventBus. We can use the common task of ordering pizza as an example. For the observable pattern, assume I am the observable, as I tend to get stuck with ordering the pizza. In this case each person in my group (the observers) comes directly to me and ‘registers’ by chipping in a few dollars.
In this case I am the PizzaObservable:
1 2 3 |
public interface PizzaObservable { void registerForPizza(PizzaPartyMember member); } |
And all my friends are a PizzaObserver:
1 2 3 |
public interface PizzaObserver { void pizzaIsReady(); } |
Eventually the pizza shows up, and I notify all of the PizzaObserver
‘s directly via the pizzaIsReady()
method.
For the EventBus, we will stretch our analogy a bit and say the pizza is for a meetup event. The event participants don’t care who puts in the order or how the pizza is delivered (just that it is free!). With a single @Subscribe
annotation we can model the interaction:
1 2 3 4 5 6 |
public class Participant { @Subscribe public void onEvent(PizzaAvailableEvent event) { // nom nom } } |
When an event coordinator, a sponsor, or a delivery person puts pizza on the table, everyone will be notified.
1 2 3 |
public void putPizzaOnTable() { eventBus.publish(new PizzaAvailableEvent(this)); } |
Each Participant
stills needs to register with the event bus, so the full example would look something like this:
1 2 3 4 5 6 7 8 9 10 11 |
EventBus eventBus = new DefaultEventBus(); MeetupCoordinator coordinator = new MeetupCoordinator(); coordinator.setEventBus(eventBus); Participant joeCoder = new Participant(); eventBus.register(joeCoder); Participant jillCoder = new Participant(); eventBus.register(jillCoder); coordinator.putPizzaOnTable(); |
In the snippet above the MeetupCoordinator
implements EventBusAware
which has a single method setEventBus()
. It is important to note that the publisher and the subscriber must be using the same EventBus instance. Shiro handles this and all of the boilerplate for any objects configured in your shiro.ini
file. The configuration ends up looking like:
1 2 3 4 |
[main] coordinator = com.stormpath.example.MeetupCoordinator joeCoder = com.stormpath.example.Participant jillCoder = com.stormpath.example.Participant |
Which just leaves us with a single line of code:
1 |
coordinator.putPizzaOnTable(); |
Any objects that publish events just need to implement the EventBusAware
interface to get access to the shared EventBus
instance. Objects that listen for events simply annotates a method with @Subscribe
, this method must only contain a single parameter of the event type you are listening for.
Further, your @Subscribe
methods will be called for any event that is an instance of the parameter, which means you can listen for a specific type of event or a range of event types (all extending a common parent).
For our example, we can introduce Jack who is a vegetarian.
1 2 3 4 5 6 |
public class VegetarianParticipant { @Subscribe public void onEvent(VegetablePizzaAvailableEvent event) { // nom nom } } |
And change our putPizzaOnTable
method to something like:
1 2 3 4 |
public void putPizzaOnTable() { eventBus.publish(new PepperoniPizzaAvailableEvent(this)); eventBus.publish(new VegetablePizzaAvailableEvent(this)); } |
Both of these events extend from PizzaAvailableEvent
, so Joe and Jill would be notified of any pizza and Jack only the vegetable.
The above code examples and a few additional ones are available on Github.
Enough with the Pizza Analogy
If you are still with me, the three things you need to remember are: implement EventBusAware
to get access to the EventBus, use EventBus.publish()
to fire an event, and use the @Subscribe
annotation for listening to events.
Already using a different event bus in your application? Take a look at this example which subscribes to all Shiro events, and republishes them to a Guava EventBus.
As of Shiro 1.3.0, SecurityManager
and SessionManager
implementations are already EventBusAware
. The events published out of the box are all related to the various life cycles of the java beans defined a shiro.ini
file, for more information checkout the Shiro documentation for Bean Events:
– Instantiated Bean Events
– Configured Bean Events
– Initialized Bean Events
– Destroyed Bean Events