A few weeks ago, Ed Burns posted a link to a blog on the JSF expert group mailing list, commenting “A nice one, but it doesn't mention JSF 2”. Ever the curmudgeon, I pointed out that it wasn't so nice that the blog's sample code used the JSF API in beans when it wasn't necessary—as does in fact a lot of sample code, even in the official Sun tutorials. Ed's response: “Cay, a blog comment by such an eminent citizen as yourself would certainly be noticed.” So, here is the curmudgeonly eminence's advice on how to stay away from the JSF API.
To set the stage, recall the basic mechanism by which JSF links the visual presentation with the application logic. JSF pages are composed of component tags that contain expressions in the oh-so-blandly named Expression Language (EL). For example,
<h:inputSecret value="{userBean.password}"/>
The class of the userBean
object must have getters and setters
for the password
property:
@Named public class UserBean { public String getPassword() { ... } public void setPassword(String newValue) { ... } ... }
Here, the property has the type String
, and that is good. There
is no coupling between the UserBean
class and the JSF API. You can
compile and run unit tests of UserBean
without having JSF around.
You can even (gasp) switch to another view technology.
So, where do programmers go wrong? Mainly in these four areas:
@ManagedBean
annotationWhen you show a list of choices, by using radio buttons, checkboxes,
listboxes, or dropdown menus, you need to provide a list of (label, value)
pairs. Since Java lacks a pair type, the JSF API provides a class
SelectItem
that lets you specify an item's label, value, and a few
other properties that aren't so useful. Your bean provides a read-only property
of type Collection<SelectItem>
for the JSF component:
<h:selectOneRadio value="#{orderBean.topping}> <f:selectItems value="#{orderBean.toppingItems}"/> </h:selectOneRadio> @Named public class OrderBean { Collection<SelectItem> getToppingItems() { ... } ... }
But to compile this OrderBean
, you need the JSF API.
This coupling is easy to avoid. You can use a Map<String,
Object>
containing the labels and values. (Use a TreeMap
if you want the labels in alphabetical order, a
Linked
HashMap
if you need some other order.)
With JSF 2.0, you can do even better. Expose a
Collection<ToppingItem>
and use a tag such as the
following:
<f:selectItems value="#{orderBean.toppingItems}" var="topping" itemLabel="#{topping.name}" itemValue="#{topping.id}" />
You design the ToppingItem
class in any way you like. The
sample above assumes that the class has getName
and
getId
methods.
You can bind a JSF data table to a Collection<RowData>
,
where RowData
is any type of your choice, or you can use the
DataModel
class in the JSF API. Why do people use a
DataModel
when it would be so much easier to just use a standard
collection? There seems to be one compelling reason—locating the
currently selected row in an action.
But you don't need the DataModel
class for that. In JSF 1.2,
you can use sPAL,
which is admittedly a bit of a pain. JSF 2.0 is much better. Simply pass the
row as a parameter to your action method.
<h:dataTable value="#{myData.rows}" var="row"> <h:column> <h:commandLink action="#{myData.doSomething(row)}"/> </h:column> ... </h:dataTable> @Named public class MyData { public String doSomething(RowData row) { do something with row return ...; } }
Some design tools (remember Java Studio
Creator?) put all JSF components of a form into a bean, sometimes called
the form's “backing bean”. This leads to very messy code, and it
encourages application logic programmers to call all sorts of inappropriate
UIComponent
methods. The UIComponent
API is difficult
for experts to understand, and it really should not be exposed to application
programmers.
One reason you may have for poking around in UIComponent
objects is multi-component validation. In that case, get busy and write a
custom validator so that you can isolate this hokus-pokus from the rest of your
application logic. In JSF 2.0, writing a custom validator isn't as bad as it
used to be because the tag handling is so much simpler.
So, you managed to get all your beans disentangled from the JSF API, you can
test them independently, and you are happy. Then you find that JSF 2.0 has
another feature that is just too good to pass up: bean annotations that free
you from the tedium of faces-config.xml
. No more
<managed-bean> <managed-bean-name>user</managed-bean-name> <managed-bean-class>com.corejsf.UserBean</managed-bean-class> <managed-bean-scope>request</managed-bean-scope> </managed-bean>
You just use an annotation:
@ManagedBean(name="user") @RequestScoped public class UserBean
Except, now you tied your bean once again to the JSF API. The annotations
are defined in the javax.faces.bean
package.
The solution is not to go back to the tedium of
faces-config.xml
, but instead to use CDI:
@Named("user") @RequestScoped public class UserBean
Ok, so why is this better? First off, @Named
is a part of JSR
330, which is tiny and easily integrated in other tools. Of course,
@RequestScoped
is in javax.enterprise.context
, which
your test framework would need to support. CDI was built around the assumption
that you will wire your beans together in different ways for testing and
deployment, and it is just a matter of time before test frameworks buy into
that. Or, if you are in a hurry and need to roll your own right now, it makes
much more sense to invest in CDI than in JSF support.
One of the key ideas behind JSF is the separation of the visual presentation and the application logic. Admittedly, older versions of JSF have not always made this separation easy or intuitive, but JSF 2.0 changes that. With JSF 2.0, you should be able to write your bean classes without using the JSF API, leaving that API for authoring components. And, if you use composite components, you may never need to see the JSF API at all!