Spring Faces docs

This commit is contained in:
Jeremy Grelle
2008-04-08 21:54:45 +00:00
parent f65b830bca
commit 54ba149619

View File

@@ -0,0 +1,399 @@
<?xml version="1.0" encoding="UTF-8"?>
<chapter id="spring-faces">
<title>Using Spring Faces</title>
<sect1 id="spring-faces-introduction">
<title>Introduction</title>
<para>
Spring Faces is Spring's JSF integration module that ships with the Spring Web Flow 2.0 distribution and
aims to simplify the programming model for using JSF with Spring. It provides support for using JSF in a
Spring-centric environment using Spring MVC and Spring Web Flow for control flow and event handling.
</para>
<para>
It includes a lightweight Facelets component library that focuses on providing rich client-side behavior and
Ajax support through the use of progressive enhancement. The client-side functionality of the component
library is provided by Spring JavaScript, an integration layer that aims to be agnostic to the underlying
client-side JavaScript toolkit. Spring Faces also aims to ensure that other JSF component libraries
integrate smoothly with Spring Web Flow, for the cases where a more desktop-like UI is desired as opposed to
the lightweight approach of the Spring Faces components.
</para>
</sect1>
<sect1 id="spring-faces-integration">
<title>Spring-centric Integration Approach</title>
<para>
Spring Faces aims to emphasize the strengths of JSF (i.e, its component model and EL binding) while
providing an alternative controller model that addresses many of its traditional weaknesses. Spring Faces
requests are routed through the Spring MVC DispatcherServlet instead of the FacesServlet, and the JSF
lifecycle is executed within the context of the current flow execution. Whereas pure JSF requests are
essentially driven by the current JSF view in a pull-oriented fashion, routing through the Spring
infrastructure allows for more flexible request processing with many well-defined extension points for
initialization and manipulation of the model and event-handling mechanisms that enable a reduced dependence
on the JSF API.
</para>
<para>
Spring Faces provides a powerful supplement to a number of the standard JSF facilities, including:
<orderedlist>
<listitem>managed bean facility</listitem>
<listitem>scope management</listitem>
<listitem>event handling</listitem>
<listitem>navigation rules</listitem>
<listitem>easy modularization and packaging of views</listitem>
<listitem>cleaner URLs</listitem>
<listitem>model-level validation</listitem>
<listitem>client-side validation and UI enhancement</listitem>
<listitem>Ajax partial page updates and full navigation</listitem>
<listitem>progressive enhancement and graceful degradation</listitem>
</orderedlist>
Using these features will significantly reduce the amount of configuration required in faces-config.xml
while providing a cleaner separation between the view and controller layer and better modularization of your
application's functional responsibilities. These use of these features are outlined in the sections to
follow. As the majority of these features build on the flow definition language of Spring Web Flow, it is
assumed that you have an understanding of the foundations presented in
<link linkend="defining-flows">Defining Flows</link>
.
</para>
</sect1>
<sect1 id="spring-faces-managed-beans">
<title>Replacing the JSF Managed Bean Facility</title>
<para>
Spring Faces allows you to completely replace the JSF managed bean facility with a combination of
flow-managed variables and Spring managed beans. It gives you a good deal more control over the lifecycle of
your managed objects with well-defined hooks for initialization and execution of your domain model.
Additionally, since you are presumably already using Spring for your business layer, it reduces the
conceptual overhead of having to maintain two different managed bean models.
</para>
<para>
In doing pure JSF development, you will quickly find that request scope is not long-lived enough for storing
conversational model objects that drive complex event-driven views. The only available option is to begin
putting things into session scope, with the extra burden of needing to clean the objects up before
progressing to another view or functional area of the application. What is really needed is a managed scope
that is somewhere between request and session scope. Fortunately web flow provides such extended facilities.
</para>
<sect2 id="spring-faces-flow-variables">
<title>Using Flow Variables</title>
<para>
The easiest and most natural way to declare and manage the model is through the use of
<link linkend="flow-variables">flow variables</link>
. You can declare these variables at the beginning of the flow:
</para>
<programlisting language="xml"><![CDATA[
<var name="searchCriteria" class="com.mycompany.myapp.hotels.search.SearchCriteria"/>]]>
</programlisting>
<para>and then reference this variable in one of the flow's JSF view templates through EL:</para>
<programlisting language="xhtml"><![CDATA[
<h:inputText id="searchString" value="#{searchCriteria.searchString}"/>]]>
</programlisting>
<para>
Note that you do not need to prefix the variable with its scope when referencing it from the template
(though you can do so if you need to be more specific). As with standard JSF beans, all available scopes
will be searched for a matching variable, so you could change the scope of the variable in your flow
definition without having to modify the EL expressions that reference it.
</para>
<para>
You can also define view instance variables that are scoped to the current view and get cleaned up
automatically upon transitioning to another view. This is quite useful with JSF as views are often
constructed to handle multiple in-page events across many requests before transitioning to another view.
</para>
<para>
To define a view instance variable, you can use the
<code>var</code>
element inside a
<code>view-state</code>
definition:
</para>
<programlisting language="xml"><![CDATA[
<view-state id="enterSearchCriteria">
<var name="searchCriteria" class="com.mycompany.myapp.hotels.search.SearchCriteria"/>
</view-state>]]>
</programlisting>
</sect2>
<sect2 id="spring-faces-spring-beans">
<title>Using Scoped Spring Beans</title>
<para>
Though defining autowired flow instance variables provides nice modularization and readability,
occasions may arise where you want to utilize the other capabilities of the Spring container such as
AOP. In these cases, you can define a bean in your Spring ApplicationContext and give it a specific web
flow scope:
</para>
<programlisting language="xml"><![CDATA[
<bean id="searchCriteria" class="com.mycompany.myapp.hotels.search.SearchCriteria" scope="flow"/>]]>
</programlisting>
<para>
The major difference with this approach is that the bean will not be fully initialized until it is first
accessed via an EL expression. This sort of lazy instantiation via EL is quite similar to how JSF
managed beans are typically allocated.
</para>
</sect2>
<sect2>
<title>Manipulating The Model</title>
<para>
The need to initialize the model before view rendering (such as by loading persistent entities from a
database) is quite common, but JSF by itself does not provide any convenient hooks for such
initialization. The flow definition language provides a natural facility for this through its
<link linkend="flow-actions">Actions</link>
. Spring Faces provides some extra conveniences for converting the outcome of an action into a
JSF-specific data structure. For example:
</para>
<programlisting language="xml"><![CDATA[
<on-render>
<evaluate expression="bookingService.findBookings(currentUser.name)"
result="viewScope.bookings" result-type="dataModel" />
</on-render>]]>
</programlisting>
<para>
This will take the result of the
<code>bookingService.findBookings</code>
method an wrap it in a custom JSF DataModel so that the list can be used in a standard JSF DataTable
component:
</para>
<programlisting language="xhtml"><![CDATA[
<h:dataTable id="bookings" styleClass="summary" value="#{bookings}" var="booking" rendered="#{bookings.rowCount > 0}">
<h:column>
<f:facet name="header">Name</f:facet>
#{booking.hotel.name}
</h:column>
<h:column>
<f:facet name="header">Confirmation number</f:facet>
#{booking.id}
</h:column>
<h:column>
<f:facet name="header">Action</f:facet>
<h:commandLink id="cancel" value="Cancel" action="cancelBooking" />
</h:column>
</h:dataTable>]]>
</programlisting>
<para>
The custom DataModel provides some extra conveniences such as being serializable for storage beyond
request scope and access to the currently selected row in EL expressions. For example, on postback from
a view where the action event was fired by a component within a DataTable, you can take action on the
selected row's model instance:
</para>
<programlisting language="xml"><![CDATA[
<transition on="cancelBooking">
<evaluate expression="bookingService.cancelBooking(bookings.selectedRow)" />
</transition>]]>
</programlisting>
</sect2>
</sect1>
<sect1 id="spring-faces-event-handling">
<title>Handling JSF Events With Spring Web Flow</title>
<para>
Spring Web Flow allows you to handle JSF action events in a decoupled way, requiring no direct dependencies
in your Java code on JSF API's. In fact, these events can often be handled completely in the flow definiton
language without requiring any custom Java action code at all. This allows for a more agile development
process since the artifacts being manipulated in wiring up events (JSF view templates and SWF flow
definitions) are instantly refreshable without requiring a build and re-deploy of the whole application.
</para>
<sect2 id="spring-faces-in-page-events">
<title>Handling JSF In-page Action Events</title>
<para>
A simple but common case in JSF is the need to signal an event that causes manipulation of the model in
some way and then redisplays the same view to reflect the changed state of the model. The flow
definition language has special support for this in the
<code>transition</code>
element.
</para>
<para>
A good example of this is a table of paged list results. Suppose you want to be able to load and display
only a portion of a large result list, and allow the user to page through the results. The initial
<code>view-state</code>
definition to load and display the list would be:
</para>
<programlisting language="xml"><![CDATA[
<view-state id="reviewHotels">
<on-render>
<evaluate expression="bookingService.findHotels(searchCriteria)"
result="viewScope.hotels" result-type="dataModel" />
</on-render>
</view-state>]]>
</programlisting>
<para>
You construct a JSF DataTable that displays the current
<code>hotels</code>
list, and then place a "More Results" link below the table:
</para>
<programlisting language="xhtml"><![CDATA[
<h:commandLink id="nextPageLink" value="More Results" action="next"/>]]>
</programlisting>
<para>
This commandLink signals a "next" event from its action attribute. You can then handle the event by
adding to the
<code>view-state</code>
definition:
</para>
<programlisting language="xml"><![CDATA[
<view-state id="reviewHotels">
<on-render>
<evaluate expression="bookingService.findHotels(searchCriteria)"
result="viewScope.hotels" result-type="dataModel" />
</on-render>
<transition on="next">
<evaluate expression="searchCriteria.nextPage()" />
</transition>
</view-state>]]>
</programlisting>
<para>
Here you handle the "next" event by incrementing the page count on the searchCriteria instance. The
<code>on-render</code>
action is then called again with the updated criteria, which causes the next page of results to be
loaded into the DataModel. The same view is re-rendered since there was no
<code>to</code>
attribute on the
<code>transition</code>
element, and the changes in the model are reflected in the view.
</para>
</sect2>
<sect2 id="spring-faces-action-events">
<title>Handling JSF Action Events</title>
<para>
The next logical level beyond in-page events are events that require navigation to another view, with
some manipulation of the model along the way. Achieving this with pure JSF would require adding a
navigation rule to faces-config.xml and likely some intermediary Java code in a JSF managed bean (both
tasks requiring a re-deploy). With the flow defintion language, you can handle such a case concisely in
one place in a quite similar way to how in-page events are handled.
</para>
<para>
Continuing on with our use case of manipulating a paged list of results, suppose we want each row in the
displayed DataTable to contain a link to a detail page for that row instance. You can add a column to
the table containing the following
<code>commandLink</code>
component:
</para>
<programlisting language="xhtml"><![CDATA[
<h:commandLink id="viewHotelLink" value="View Hotel" action="select"/>]]>
</programlisting>
<para>
This raises the "select" event which you can then handle by adding another
<code>transition</code>
element to the existing
<code>view-state</code>
:
</para>
<programlisting language="xml"><![CDATA[
<view-state id="reviewHotels">
<on-render>
<evaluate expression="bookingService.findHotels(searchCriteria)"
result="viewScope.hotels" result-type="dataModel" />
</on-render>
<transition on="next">
<evaluate expression="searchCriteria.nextPage()" />
</transition>
<transition on="select" to="reviewHotel">
<set name="flowScope.hotel" value="hotels.selectedRow" />
</transition>
</view-state>]]>
</programlisting>
<para>
Here the "select" event is handled by pushing the currently selected hotel instance from the DataTable
into flow scope, so that it may be referenced by the "reviewHotel"
<code>view-state</code>
.
</para>
</sect2>
<sect2 id="spring-faces-model-validation">
<title>Performing Model Validation</title>
<para>
JSF provides useful facilities for validating input at field-level before changes are applied to the
model, but when you need to then perform more complex validation at the model-level after the updates
have been applied, you are generally left with having to add more custom code to your JSF action methods
in the managed bean. Validation of this sort is something that is generally a responsibility of the
domain model itself, but it is difficult to get any error messages propagated back to the view without
introducing an undesirable dependency on the JSF API in your domain layer.
</para>
<para>
With Spring Faces, you can utilize the generic and low-level
<code>MessageContext</code>
in your business code and any messages added there will then be available to the
<code>FacesContext</code>
at render time.
</para>
<para>
For example, suppose you have a view where the user enters the necessary details to complete a hotel
booking, and you need to ensure the Check In and Check Out dates adhere to a given set of business
rules. You can invoke such model-level validation from a
<code>transition</code>
element:
</para>
<programlisting language="xml"><![CDATA[
<view-state id="enterBookingDetails">
<transition on="proceed" to="reviewBooking">
<evaluate expression="booking.validateEnterBookingDetails(messageContext)" />
</transition>
</view-state>]]>
</programlisting>
<para>
Here the "proceed" event is handled by invoking a model-level validation method on the booking instance,
passing the generic
<code>MessageContext</code>
instance so that messages may be recorded. The messages can then be displayed along with any other JSF
messages with the
<code>h:messages</code>
component,
</para>
</sect2>
<sect2 id="spring-faces-ajax-events">
<title>Handling Ajax Events</title>
<para>
Spring Faces provides some special
<code>UICommand</code>
components that go beyond the standard JSF components by adding the ability to do Ajax-based partial
view updates. These components degrade gracefully so that the flow will still be fully functional by
falling back to full page refreshes if a user with a less capable browser views the page.
</para>
<para>
Revisiting the earlier example with the paged table, you can change the "More Results" link to use an
Ajax request by replacing the standard
<code>commandButton</code>
with the Spring Faces version (note that the Spring Faces command components use Ajax by default, but
they can alternately be forced to use a normal form submit by setting ajaxEnabled="false" on the
component):
</para>
<programlisting language="xhtml"><![CDATA[
<sf:commandLink id="nextPageLink" value="More Results" action="next" />]]>
</programlisting>
<para>
This event is handled just as in the non-Ajax case with the
<code>transition</code>
element, but now you will add a special
<code>render</code>
action that specifies which portions of the component tree need to be re-rendered:
</para>
<programlisting><![CDATA[
<view-state id="reviewHotels">
<on-render>
<evaluate expression="bookingService.findHotels(searchCriteria)"
result="viewScope.hotels" result-type="dataModel" />
</on-render>
<transition on="next">
<evaluate expression="searchCriteria.nextPage()" />
<render fragments="hotels:searchResultsFragment" />
</transition>
</view-state>]]>
</programlisting>
<para>
The
<code>fragments="hotels:searchResultsFragment"</code>
is an instruction that will be interpreted at render time, such that only the component with the JSF
clientId "hotels:searchResultsFragment" will be rendered and returned to the client. This fragment will
then be automatically replaced in the page. The
<code>fragments</code>
attribute can be a comma-delimited list of ids, with each id representing the root node of a subtree
(meaning the root node and all of its children) to be rendered. If the "next" event is fired in a
non-Ajax request (i.e., if JavaScript is disabled on the client), the
<code>render</code>
action will be ignored and the full page will be rendered as normal.
</para>
<para>
In addition to the Spring Faces
<code>commandLink</code>
component, there is a corresponding
<code>commandButton</code>
component with the same functionality. There is also a special
<code>ajaxEvent</code>
component that will raise a JSF action even in response to any client-side DOM event. See the Spring
Faces tag library docs for full details.
</para>
</sect2>
</sect1>
</chapter>