Getting Started with JSF 2.0 and CDI part 3 – Events
Last time we looked more in depth at CDI and how we can define beans and inject them into other beans. This time we are going to look at how we can use events to decouple the handling of actions in the system.
Read Part 1
Read Part 2
As a refresher, in our last part, we had an application that obtained a list of items, validated them and took a specific action when an invalid item was found. Lets say in the future we want to expand our system to handle all sorts of things happening when we find an invalid item. This could range from an email being sent, changes made to other data (i.e. an order canceled) or storing a list of rejections in a file or database table. To completely decouple the implementation we can use events. Events are raised by the event producer and subscribed to by event observers. Like most of CDI, event production and subscription is type safe and allows qualifiers to determine which events observers will observe.
Luckily we don’t have to change our application much to implement this, we just provide an implementation of the ItemErrorHandler
that raises the event when it handles the item. We will inject an instance of the Item error handler called EventErrorHandler
and create a @Notify
qualifier to select it for injection.
@Retention(RetentionPolicy.RUNTIME) @Target({FIELD,METHOD,PARAMETER,TYPE}) @Qualifier public @interface Notify {}
@Notify public class EventItemHandler implements ItemErrorHandler { @Inject private Event<Item> itemEvent; public void handleItem(Item item) { System.out.println("Firing Event"); itemEvent.fire(item); } }
In this item handler, we inject an instance of an Event where the event payload will be an Item
. The event payload is the state data passed from the event producer to the event observer which in this case passes the rejected Item. When the invalid item is handled, we fire the event and pass in the invalid item we received. This event based item handler is injected the same as any other item handler would be so we can swap it in and out whenever we need to and also can substitute it during testing. We created a @Notify
qualifier annotation to identify this error handler for injection and can use it in our item processor by adding it to the injection point.
@Inject @Notify private ItemErrorHandler itemErrorHandler;
If we deploy this now, we will see that when the item processor hits invalid items, the event based item error handler fires the event. Currently though we don’t have anything observing the event. We can fix this by creating an observer method which is the only thing needed to observe an event. We can still re-use our existing item processors by adding an event observer method to the implementations which just calls the error handler method. For example, to make our FileErrorReporter
respond to the event, we add the following observer event.
public class FileErrorReporter implements ItemErrorHandler,Serializable { public void eventFired(@Observes Item item) { handleItem(item); } .... ....
If you run the application now, you will see that the events are fired on the invalid objects, and the item information is being saved when this event is fired. You will also note that the lifecycle events are being observed.
INFO: Firing Event INFO: Creating file error reporter INFO: Saving eedemo.Item@1f84b46 [Value=34, Limit=7] to file INFO: Closing file error reporter INFO: Firing Event INFO: Creating file error reporter INFO: Saving eedemo.Item@2656da [Value=89, Limit=32] to file INFO: Closing file error reporter
Note that the file error reporter bean is created each time the event is raised which may or may not be what we want. In this case, we don’t want to create the bean new each time since we don’t want to open and close the file for each item. We still want to open the file at the start of the process, and then close it once the process it completed. In this case, we need to consider the scope of the FileErrorReporter
bean which doesn’t have a scope defined. When no scope is defined, CDI defaults it to the default pseudo dependent scope. What this means in practice is that the bean is created and destroyed over a very short space of time, typically over a method call. In our case here, the bean is created and destroyed for the duration of the event being fired. To fix this, we need to lengthen the scope of the bean by manually adding a scope annotation. We will make this bean @RequestScoped
so once the bean is created the first time the event is fired, it will remain created for the duration of the request. Also, for any injection points that this bean is qualified to be injected to, the same bean instance will be injected. Here is the log when we make our scope change to the bean. Note that the bean is still only created when the event is fired.
INFO: Firing Event INFO: Creating file error reporter INFO: Saving eedemo.Item@1380c08 [Value=34, Limit=7] to file INFO: Firing Event INFO: Saving eedemo.Item@1b44f96 [Value=89, Limit=32] to file INFO: Closing file error reporter
Let’s take the events example a little further. Right now we are observing any event for an item but chances are, there may be different types of events in the system for the items. We want to be specific in which observers subscribe to which events. Luckily CDI provides for this in a similar manner to how we determine suitable beans for injection points by using typing and qualifiers.
First, lets create a problem for ourselves by firing events off for each item we validate by adding the following code to the item processor.
@Inject private Event<Item> processorEvent; public void execute() { List<Item> items = itemDao.fetchItems(); for (Item item : items) { processorEvent.fire(item); if (!itemValidator.isValid(item)) { itemErrorHandler.handleItem(item); } } }
This will fire an event for each item we process, but we don’t want the invalid item handler to subscribe to each of those events. If we do, we will see output like the following :
INFO: Creating eedemo.EventItemHandler_$$_javassist_748 INFO: Creating file error reporter INFO: Saving eedemo.Item@6d0baf [Value=34, Limit=7] to file INFO: Creating eedemo.EventItemHandler INFO: Firing Event INFO: Saving eedemo.Item@6d0baf [Value=34, Limit=7] to file INFO: Saving eedemo.Item@1087300 [Value=4, Limit=37] to file INFO: Saving eedemo.Item@11674c9 [Value=24, Limit=19] to file INFO: Saving eedemo.Item@de163a [Value=89, Limit=32] to file INFO: Firing Event INFO: Saving eedemo.Item@de163a [Value=89, Limit=32] to file INFO: Closing file error reporter
For each item, the file item handler observer is being called for each item because we aren’t distinguishing the kind of events fired when the item is processed and the kind of event fired when an invalid item is found. To differentiate between them we will create an @Invalidate
qualifier to qualify the event for invalid items. This annotation will be placed on the event injection point and also on the observation point method.
@Notify @RequestScoped public class EventItemHandler implements ItemErrorHandler { @Inject @Invalidated private Event<Item> itemEvent; public void handleItem(Item item) { System.out.println("Firing Event"); itemEvent.fire(item); } }
@Save @RequestScoped public class FileErrorReporter implements ItemErrorHandler,Serializable { public void eventFired(@Observes @Invalidated Item item) { handleItem(item); } .... }
If you run the code now, you will see that we no longer call the file save handler for each item, even though we are firing the event for each item.
INFO: Firing Event INFO: Creating file error reporter INFO: Saving eedemo.Item@1e3aa22 [Value=34, Limit=7] to file INFO: Firing Event INFO: Saving eedemo.Item@172940f [Value=89, Limit=32] to file INFO: Closing file error reporter
You can add an event observer to the item processor just to see the events are still being raised and can be observed.
@Named("itemProcessor") @RequestScoped public class ItemProcessor { .... public void observeItemEvent(@Observes Item item) { System.out.println("Item event observed for item "+item); } }
This will print a message whenever an event is fired so you can see that there is a difference between the two event subscribers and also that the more general version of the observer sees all events related to the item.
Events are a great way to decouple parts of the system in a modular fashion as you can add pieces that will subscribe to events with the event producer unaware of the observer as opposed to the even producer having to call the observer manually when events are not used. For example, if someone updates an order status, you could add events to email the sales rep, or notify an account manager if a tech support issue is open for more than a week. These kinds of rules can be implemented without events, but events make it easier to decouple the business logic. Additionally, there is no compile or build time dependency and you can just add modules to your application and they will automatically start observing and producing events. Additionally, observers and producers know nothing about each other, nor do they require any configuration for them to do so.
Scheduling the Processing
One last tidbit before we move on to creating CDI driven JSF apps is that for now we are running our code from a button in a JSF page but typically this is something that would get fired off on a regular basis. With the power of the EJB 3.1 scheduler we can do just that in a new class with few lines of code. We’ll create a new stateless EJB into which we’ll inject our ItemProcessor
and add a method to call the execute()
method which will be called at a scheduled time.
@Stateless public class ScheduledProcessor { @Inject private ItemProcessor itemProcessor; @Schedule(hour="07",minute = "00") public void execute() { System.out.println("executing scheduled job!"); itemProcessor.execute(); } }
This makes use of the new EJB 3.1 @Schedule
annotation and will schedule this method to be called every morning at 7am. You could make it on the hour every hour, or you could use the EJB timer service to implement your own timeout. We inject the same ItemProcessor
implementation we have been using all along and call the execute()
method. That is a pretty powerful transformation letting us re-use our existing code and easily incorporating EJB services with POJO managed beans into an EJB needing only a few lines of code.
Note : If you actually implement this and run it, it won’t work because of a Weld 1.0 bug whereby the request scope isn’t active during calls to EJB timeouts (see here for details) so you’ll have to wait until Weld 1.01 which should be out fairly soon.
14 thoughts on “Getting Started with JSF 2.0 and CDI part 3 – Events”
Comments are closed.
Hi Andy,
thank you very much for your awesome CDI examples. You are really doing a great job!
I am sure CDI will play a very important role in JavaEE 6. However, has anyone thought of how to test CDI code with simple Junit Tests? Do you have any idea?
Thank Daniel,
I’m currently working on a larger tutorial application for JSF/CDI and I hope to make testing a part of that.
Off the top of my head, CDI code can run in Java SE so you should be able to bootstrap CDI and execute methods in CDI beans.
If you use EJBs it gets a little more tricky because then you also need to involve an EJB container such as the JBoss micro-container.
The JBoss Arquillian project may be of interest to you as it lets you test your code inside the container. You are right though, some of this stuff is a little unclear. I’ll make a note to make sure I cover it in the future,
Thanks again,
Cheers,
Andy Gibson
my Junit test that tests fireing CDI events fails with pure JavaSE. But maybe I did something wrong. Thanks for the Arquillian hint, I will check this out.
Daniel
Hi
Can you also provide an example Using jboss seam framework which uses CDI, JPA ,Facelets, Validator and EJB 3.1
That would be a tall order. Getting Seam running with JPA, Facelets, Validator and EJB 3.1 should be doable just by deploying a Seam app on the latest version of glassfish (AFAIK JBoss 6 m1 doesn’t support EJB 3.1) . Getting CDI incorporated would be difficult as well as ill defined (are you trying to use CDI conversations and injection?). Seam 3 will be built on CDI and all that good stuff and come in a more standards friendly manner.
Cheers,
Andy
@Save ant @Invalidated are unknown…
What library I need?
Hey Cornelius, the save and invalidated annotations are your own qualifiers, something like :
@Retention(RetentionPolicy.RUNTIME)
@Target({FIELD,METHOD,PARAMETER,TYPE})
@Qualifier
public @interface Invalidated {}
Looking at the code, I don’t believe the @save annotation is needed, it probably got pulled into the source as I was looking at another kind of example that never made it into the final document (possibly, some mechanism to inject the file error handler by marking the injection point with the save annotation).
Cheers,
Andy
Hey Andy,
Thanks for the great example.
On the ScheduledProcessor part I was getting some errors when the scheduled job was executing. My idea was to comment out the @RequestScoped annotations, as it is not web based anymore and it seems it works fine.
Was this the right/best thing to do?
Thanks,
Gabriel
Well that would work, but that would make the bean Dependent scoped which is probably the wrong thing to do, especially if they are used in other placed in the app where they might need request or conversation scopes.
Here’s the Jira issue for it :
https://jira.jboss.org/browse/WELD-15
It seems it is fixed, however, if you are using Glassfish, last I looked they are still using the old versions of Weld so its a matter of waiting for them to upgrade it (there was a change in the API so it means that it is a bit more involved that just building and replacing the osgi bundle).
After finding and finishing your 3-part tutorial, let me add my thanks for a great piece of work. Now both annotations and bean management have snapped into focus.
Using your work, I was inspired to check out the Weld – JSR-299 Reference Implementation document and use it’s Chapter 3 example to cobble together a NB project which mostly worked (once I remembered the beans.xml keystone).
One JSF/Weld element didn’t work: proper EL resolution from for “currentUser” that was set up with a @Produces method in the “login” session bean. That identifier is not on the JSF EL-resolver’s radar when rendering as Weld suggests it will:
“Or we can reference the current user in a JSF view:”
signed in as #{currentUser.username}
Were the currentUser reference was produced by:
@Produces @LoggedIn User getCurrentUser() {
return user;
}
I wish you luck with next opus with more elaborate JSF/Weld cooperation. Thanks again.
Thanks Bob, glad you liked it,
If you want to look at more Weld/JSF samples take a look at the Knappsack maven archetypes (they are available in the central maven repo)
There are two demo apps there, one that is servlet based with Jetty and another for Java EE 6 application servers.
See http://www.andygibson.net/blog/news/knappsack-archetypes-are-now-in-the-maven-central-repository/ for more details
Cheers,
Andy Gibson
Nice 3 parts … Learning by example and trying out the code is only way to get a firm grip on some of this stuff. Will there be a part 4, part 5, etc?
Thank you so much your awesome tutorial. You saved me a lot of frustration!! Seriously, thank you!!
Thank you very much Andy for this amazing article about CDI
i just read about it in netbeans site
i think it’s the first perfect tutorial which describe the CDI features
i just linked it in my Java EE Professional Articles group on facebook
thankx