The Contexts and Dependency Injection (CDI) API introduced in Java EE 6 complements the JSF framework. There are good reasons to favour it over JavaServer Faces (JSF) managed bean mechanisms.
CDI doesn't do everything and there are edge cases where you might want to make use of Expression Language (EL) bindings during dependency injection. Fortunately, the gaps between these APIs are easy to fill.
Sample Beans
Consider a bean that counts the number of "foo" query parameters on a URI:
http://host/path?foo&foo&foo
The sample beans that follow implement this requirement in a number of different ways. The resultant EL expressions in a sample JSF view look like this:
#{alfa.fooParamCount} #{bravo.fooParamCount} #{charlie.fooParamCount} #{delta.fooParamCount}
JSF 2 Dependency Injection
Here is a request scoped bean that counts the number of parameters:
package beans; import javax.faces.bean.*; @ManagedBean @RequestScoped public class Alfa { @ManagedProperty("#{paramValues.foo}") private String[] foo; public String[] getFoo() { return foo; } public void setFoo(String[] foo) { this.foo = foo; } public int getFooParamCount() { return foo == null ? 0 : foo.length; } }
The parameters are injected using a managed property.
Here is another managed bean implementation that consumes the parameters from a broader scope:
package beans; import javax.faces.bean.*; import beans.faces.FacesBroker; @ManagedBean @ApplicationScoped public class Bravo { @ManagedProperty("#{facesBroker}") private FacesBroker broker; public FacesBroker getBroker() { return broker; } public void setBroker(FacesBroker broker) { this.broker = broker; } public int getFooParamCount() { String[] foo = broker.getContext() .getExternalContext() .getRequestParameterValuesMap() .get("foo"); return foo == null ? 0 : foo.length; } }
The Bravo
bean utilizes a context broker
to circumvent scope issues. The downside of this approach is that unit tests need to mock the broker,
the JSF context,
the external context
and the parameter map.
JSF Scopes and CDI
The managed properties in Alfa
and Bravo
use runtime evaluation and type casting.
Conversely, CDI provides a lot of deploy-time dependency checking and type safety.
Here is the JSF request parameter map injected into a bean:
package beans; import java.util.Map; import javax.enterprise.context.ApplicationScoped; import javax.inject.*; import beans.providers.Params; @ApplicationScoped @Named public class Charlie { @Inject @Params private Map<String, String[]> params; public int getFooParamCount() { System.out.println(params.getClass()); String[] foo = params.get("foo"); return (foo == null) ? 0 : foo.length; } }
This code relies on a producer to provide the map:
package beans.providers; import java.util.Map; import javax.enterprise.context.RequestScoped; import javax.enterprise.inject.Produces; import javax.faces.context.FacesContext; public class ParamsProducer { @Produces @Params @RequestScoped public Map<String, String[]> paramValues() { return FacesContext.getCurrentInstance() .getExternalContext() .getRequestParameterValuesMap(); } }
CDI uses the type to resolve the property, so a qualifier
must be used to distinguish it from every other Map
implementation:
package beans.providers; import static java.lang.annotation.ElementType.*; import java.lang.annotation.*; import javax.inject.Qualifier; @Qualifier @Retention(RetentionPolicy.RUNTIME) @Target({ FIELD, PARAMETER, METHOD, TYPE }) public @interface Params {}
It is safe to inject this request-scoped artefact into an application-scoped bean because the CDI framework will proxy the Map
instance.
CDI with EL
It is possible to utilize CDI to enhance managed property style expressions:
package demo.beans; import javax.enterprise.context.ApplicationScoped; import javax.inject.*; import demo.faces.defer.Property; import demo.faces.defer.el.EL; @ApplicationScoped @Named public class Delta { @Inject @EL("#{paramValues.foo}") private Property fooRef; public int getFooParamCount() { String[] foo = fooRef.get(); return (foo == null) ? 0 : foo.length; } }
This bean avoids the need for the FacesBroker
type while still avoiding scope leaks.
The Delta
bean relies on a small JSF/CDI library provided below.
The library sacrifices type and dependency checking but removes the need for a qualifier and producer
for every edge-case dependency like the parameter map. A Property
facade type is used to
hide details
of the container API.
Testing
The unit test for Delta
looks like this:
package demo.beans.test; import static demo.beans.test.ElFields.forExpressionsIn; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.*; import java.util.*; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; import demo.beans.Delta; import demo.faces.defer.Property; @RunWith(Parameterized.class) public class DeltaTest { private String[] params; public DeltaTest(String[] params) { this.params = params; } @Parameters public static Collection<Object[]> data() { String[] nothing = null; String[] zero = {}; String[] n = { "bar", "baz" }; Object[][] data = { { nothing }, { zero }, { n } }; return Arrays.asList(data); } @Test public void delta() { // mock the property Property foo = mock(Property.class); when(foo.get()).thenReturn(params); // instantiate the test bean and set mocks Delta delta = forExpressionsIn(new Delta()).set("#{paramValues.foo}", foo) .done(); // test the class int expectedCount = (params == null) ? 0 : params.length; assertEquals(expectedCount, delta.getFooParamCount()); } }
This test uses JUnit with Mockito for mocking and reflection for setting fields with no setter method.
Sample Code
- el-defer - EL library
- el-defer-sample - example code
The link to your sample code is broken.
ReplyDeleteTry:
Deletehttps://github.com/mcdiae/iae/tree/master/code/java/el-defer
https://github.com/mcdiae/iae/tree/master/code/java/el-defer-sample