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;
}
} |
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.
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.
}
} |
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> |
<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? 😉