November 9th, 2010 by Lincoln Baxter III

How to: Use CDI with JSF 2 PhaseListeners – Explained

A recent thread came up on the JSF 2 forums: “Why can’t I use @Inject in a PhaseListener? This CDI stuff is so confusing.” FIrst, before I start explaining: this is possible, but there’s a little background you should probably know. The answer to the question “why can’t I use CDI in JSF PhaseListeners?” is: because JSF 2 was completed just a few short months before JSR-299 and the rest of the Java EE 6 platform specifications were finalized. In my opinion – because of the current state-of-affairs in the JCP – it was just not a safe bet to promise integration with other technologies that might not have been finalized in time. Fortunately, however, JSF goes to extra lengths in providing extension points – solving this with an extension was not too difficult. More technically speaking, however, this is because PhaseListeners in JSF 2 are not CDI-aware components. They are not created by the CDI BeanManager, rather by literal calls to PhaseListener listener = new PhaseListenerImpl();, and unfortunately, this is not scheduled to be fixed in JSF 2.1 (coming out in December if on time.) See related issue. You have a few options, the first of which is pretty easy, but not perhaps the most elegant. I think you’ll find the second option much more appealing, but the first approach requires fewer dependencies.

1. Seam Solder (formerly Weld Extensions):

The key to using CDI in places where you would normally not have access to it, is getting a reference to the BeanManager, which, for reasons best explained in a separate article, is not easy unless you use some “Solder.” However, this approach is recommended only for developers who need absolute control of how their beans are created. As background, the JSR-299 EG made the BeanManager intentionally formal because they saw how easily bean creation could be abused. They definitely channeled developers towards the more declarative approach.
public class PhaseListenerImpl implements PhaseListener
{
    public BeanManager getBeanManager()
    {
        return BeanManagerAccessor.getManager();
    }
 
    public void afterPhase(PhaseEvent event)
    {
        getBeanManager(); // and so on, you'll need to create an instance of a bean.
    }
 
    public void beforePhase(PhaseEvent event) {}
 
    public PhaseId getPhaseId() {
        return PhaseId.ANY_PHASE;
    }
}
Click here to find more information about Seam Solder. Solution two, however, I find is much simpler.

2. Use Seam 3 Faces module, or MyFaces CODI

Developed by the JBoss Seam team, the Faces module cleans up many of the integration points between CDI and JSF 2, not the least of which is providing direct access to PhaseEvent objects via CDI @Observes methods: With Seam 3 JSF integration, any method can observe phase events, and react to them without even the need to declare a PhaseListener class at all! Take a look at the example below; simplicity at work.
public class MyPhaseObserver
{
    @Inject
    private Logger log;
 
    public void before(@Observes @Before PhaseEvent event)
    {
        log.info("Observing before the [" + event.getPhaseId() + "] event.");
        // This code is executed before every phase.
    }
 
    public void after(@Observes @After @RestoreView PhaseEvent event, NavigationHandler navHandler)
    {
        log.info("Observing after the RESTORE_VIEW event.");
        // This code is executed only after the Restore View phase - notice
        // that the NavigationHandler is also available for @Inject via Seam faces.
        // All @Observes method parameters other than the event itself are
        // injected by CDI.
    }
}
Just include the Seam 3 Faces dependency in your POM file, and check out the documentation for even more cool integrations with JSF:
        <dependency>
            <groupId>org.jboss.seam.faces</groupId>
            <artifactId>seam-faces</artifactId>
            <version>${seam-faces-version}</version>
        </dependency>
Notice how simple this; by observing a standard set of events, we now have full access to the JSF lifecycle events in a CDI environment. We don’t even need to configure JSF to register a Phase Listener. So of all the options, I highly recommend this approach for simplicity. Seam 3 is designed to run anywhere that CDI runs, which means JBoss Application Server, GlassFish, and even Servlet Containers such as Tomcat or Jetty.

Wrap up & Conclusion

These are just some of the examples of simplicity at work in Java EE 6 that I think will only continue to grown in the next few months and years. Things are looking up, and if you found this article interesting, you might want to check out some of our other articles on: Migrating from Spring to Java EE 6, or PrettyFaces, a next-generation URL-rewriting framework for Servlet and Java EE. I should also note, that while these are two options for how to solve this problem, they are not the only options. You can also manage a reference to the BeanManager yourself if you really want to, but since the work’s already been done for you, why bother? 😉
Lincoln Baxter, III

About the author:

Lincoln Baxter, III is the Chief Editor of Red Hat Developers, and has worked extensively on JBoss open-source projects; most notably as creator & project lead of JBoss Forge, author of Errai UI, and Project Lead of JBoss Windup. This content represents his personal opinions, not those of his employer.

He is a founder of OCPsoft, the author of PrettyFaces and Rewrite, the leading URL-rewriting extensions for Servlet, Java EE, and Java web frameworks; he is also the author of PrettyTime, social-style date and timestamp formatting for Java. When he is not swimming, running, or playing competitive Magic: The Gathering, Lincoln is focused on promoting open-source software and making technology more accessible for everyone.

Posted in JSF2, Seam

Leave a Comment




Please note: In order to submit code or special characters, wrap it in

[code lang="xml"][/code]
(for your language) - or your tags will be eaten.

Please note: Comment moderation is enabled and may delay your comment from appearing. There is no need to resubmit your comment.