The Best of Both Worlds: Integrating JSF with Struts in Your J2EE Applications
by Craig McClanahan
Learn how to add JavaServer Faces Components to Struts Applications
|
Please rate this document:
For several years, Struts has been a popular and widely used framework for building web applications using Java. Recently, a new API that has significant overlap with Struts functionality—JavaServer Faces (JSF)—has become standard, giving rise to questions about which technology developers should use, and what they can do with existing Struts-based applications to start taking advantage of JSF's capabilities. This article briefly introduces both technologies, and discusses how to migrate the user-interface elements from Struts to JSF, providing a technique you can use to integrate the two technologies to obtain the best of both worlds.
"As the Struts framework has evolved, its developers have strongly favored API stability." |
The Struts Framework is a very popular framework for building web applications in Java. Its key features include:
-
An overall architecture based on Model-View-Controller (MVC) design principles in which all requests get funneled through a controller that exerts overall management and dispatches requests to appropriate application components as needed, based on logical identifiers that reduce coupling between tiers.
-
Form-management capabilities, such as the ActionForm JavaBean that represents the server side state of the input fields on a form, and a validation framework externaliz the configuration of the set of correctness checks to be applied to input field values, plus implement those checks on both the client side and the server side.
-
The Tiles framework for layout management, which supports creation of sophisticated layout templates that can be reused across multiple pages, and thus allows easy modifications to the overall look and feel of an application.
- A set of JSP custom tags that can simplify the process of creating the application's HTML markup for the view tier of your application, and which works in a synergistic way with the form management capabilities and the overall controller architecture.
This combination of features plus the maturity of the Struts implementation (originally created in 2000), and the substantial supporting ecosystem around Struts (documentation, books, articles, consultants, support forums, trained developers, and tools support) are some of the reasons Struts has become a de facto web application architecture for J2EE developers.
The Road Ahead for Struts
As the Struts framework has evolved, its developers (including myself) have strongly favored API stability over other goals, to ensure that porting Struts applications from earlier versions would be easy and not require developers to make significant changes to their code. The last two major feature release transitions from 1.0 to 1.1, and from 1.1 to 1.2) are evidence of that goal, which remains a primary emphasis of the current effort towards Struts version 1.3: the development focus has been on remodeling the insides of Struts to make it easier to customize and use, while retaining backwards compatible APIs for existing applications so that developers wanting to can easily transition to the newer version.
That said, down the road, future development of Struts must take into account two major factors:
-
Over the past four years, many innovations in web application frameworks have been implemented, offering elegance in some areas that Struts has not been able to match given our commitment to backwards compatibility.
- In the last year, JavaServer Faces, a standard Java API for building user interface components for web applications—essentially a framework with substantial overlap with Struts—was released.
For these reasons, beyond the ongoing work on version 1.3 of Struts, its developers have also started conversations about what a version 2.0 of Struts might look like—the major version number change indicating that we'd be willing to focus more on bringing the design of Struts up to date with current technology trends, and less on backwards compatibility.
In fact, I have made one particular proposal (code named "Shale") that envisions a Struts 2.0 that is built around JavaServer Faces, which provides value added features that JavaServer Faces by itself does not provide. (For more information about Shale, start at http://wiki.apache.org/struts/StrutsShale .) Let's look a bit at some of the benefits of JavaServer Faces before getting into how to bridge the two technologies in your existing Struts-based J2EE applications.
Benefits of JavaServer Faces
As just noted above, JavaServer Faces is the standard Java API for building user interface components in web applications. It focuses on the view tier of a Model-View-Controller architecture, while providing enough controller capability to write simple to moderately complex applications using this API alone. Its key features include:
-
Fundamental APIs for user interface components, plus a basic set of standard components that are guaranteed to exist in any implementation of JavaServer Faces.
-
Event- and listener-model for handling server side events, based on the standard JavaBeans design patterns.
-
Value-binding and method-binding-expressions based on a superset of the expression language introduced in the JSP Standard Tag Library (JSTL) 1.0, and incorporated into JavaServer Pages (JSP) 2.0. These expressions let you bind component properties to objects in your data model and or event handling methods to your business logic, without requiring the components to have any detailed knowledge of the Java classes involved.
-
Well defined request processing lifecycle that implements the Front Controller design pattern (the same pattern implemented in the Struts controller), with plug in locations for application logic that can respond to events occurring during the processing of each request.
-
Basic page navigation support, with a default implementation that chooses the next view (or page) based on three factors:
- The view that is currently processing this request.
- The action method that was invoked (typically, each action method corresponds to a submit button on a form).
- The logical "outcome" string returned by the invoked action method that describes the results of this action.
- Managed beans facility that, in the process of evaluating value binding and method binding expressions, can cause new beans to be instantiated on demand, have their properties configured, and optionally stored into some scope. The managed beans facility implements the style of Inversion of Control (IoC) configuration known as "setter injection".
The Road Ahead for JSF
Initially released in March 2004, JavaServer Faces 1.0 was soon followed by a version 1.1 maintenance release that cleaned-up some typographical errors and inconsistencies in the initial version, and also fixed several bugs in the corresponding reference implementation.
JavaServer Faces 1.2 is currently in development (under the auspices of the JSR-252 expert group). Most of the development effort is focused on improving the alignment between JavaServer Faces and JavaServer Pages (version 2.1 of which is also under development), particularly in the areas of resolving differences in the expression language syntax and semantics, and interoperability issues between JavaServer Faces components and JSP template text.
"We'd be willing to focus more on bringing the design of Struts up to date with current technology trends, and less on backwards compatibility." |
Which Technology Should I Use?
When one technology standardizes in a functional area that overlaps an existing de facto standard, as in this case, with JavaServer Faces and Struts—developers and architects naturally want advice about which of the two technologies to use—or, in fact, whether they can or should be used together. I addressed this question at length in a blog entry recently.
Leaving that question to the blogosphere, let's assume you have one or more existing applications based on Struts. You see the potential for using some of the sophisticated JavaServer Faces components becoming available to enhance the user interface of your application, but you do not have time to completely rewrite the application based on JavaServer Faces APIs. Is there any way that you can have the best of both worlds, using the new user interface components while preserving your investment in business logic, validation, and all the rest?
The answer is "yes," by using the Struts-Faces integration library created expressly for this purpose. The primary goal of this library is to let developers of existing applications migrate user-interface components from Struts to JSF, one page at a time, with minimal changes to existing struts-config.xml files and without any changes to the back-end business and persistence logic.
The library works with Struts 1.1 or the more recent Struts 1.2.x. A final release of the library is expected soon, but in the meantime, nightly builds are available.
Note that JavaServer Faces requires a platform that supports Servlet 2.3 (or later) and JSP 1.2 (or later), both of which are supported by any J2EE 1.3 (or later) platform.
Getting started with the Struts-Faces Integration Library
Let's go through a simple example. First, download the Struts-Faces Integration Library , and follow the instructions in the README.txt file to set up your build environment to incorporate the new library in your application.
To understand the migration process, let's look at an example JSP page that defines the logon screen for your application. The current Struts-based version might look something like this:
Listing 1: A simple Struts-based logon JSP
<%@ taglib uri="/tags/struts-bean" prefix="bean" %> <%@ taglib uri="/tags/struts-html" prefix="html" %> <html:html> <head> <title><bean:message key="logon.title"/></title> </head> <body> <html:errors/> <html:form action="/logon"/> <table border="0"> <tr> <td align="right"> <bean:message key="prompt.username"/> </td> <td align="left"> <html:text property="username"/> </td> </tr> <tr> <td align="right"> <bean:message key="prompt.password"/> </td> <td align="left"> <html:password property="password"/> </td> </tr> <tr> <td align="right"> <html:submit value="Log On"/> </td> <td align="left"> <html:reset/> </td> </tr> </table> </html:form> </body> </html:html>
In the next several steps, you'll see that there are several simple modifications that need to be made to use the JSF tag libraries, to change how the page handles localization for messages, and other such details.
Step 1: Change the tag library declarations Replace the tag library declarations at the top of the page (and change the <html:html> tag to a plain <html> element) with the following declarations:
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@ taglib uri="http://struts.apache.org/tags-faces" prefix="s" %> <f:view> <html>
The first two tag libraries are the standard ones provided by any JavaServer Faces implementation. The third is the integration library that contains tags specifically designed to make migration easier. See the tag library documentation included in the distribution for more details.
Be sure to also, replace the </html:html> element at the end of the page with </html> , and add the closing </f:view> tag.
Step 2: Modify declarations for localized messages The original Struts page in Listing 1 follows best practices and uses the <bean:message> tag to support localization of the page title and the field prompts. To enable localized messages in the migrated page, add the following declaration immediately below the new <f:view> element:
<f:loadBundle var="messages" basename="com.mycompany.myapp.ApplicationResources"/>
Replace the specified basename attribute with the name of the resource bundle containing your application resources for this page. The tag will then make messages available within this page, under the name messages.
Next, replace each use of the <bean:message> tag with a <h:outputText> component. For example, the page title would be specified as:
<title> <h:outputText value="#{messages['logon.title']"/> </title>
This instructs JavaServer Faces to look up a message stored under key logon.title in your application resources. The response will be localized based on the Locale selected for this user.
Step 3: Change tags for error and form components JavaServer Faces components cannot read Struts-provided ActionError and ActionMessage messages; nor can its form component automatically load an ActionForm bean (as does the standard Struts form tag). That's why the integration library provides specialized components to support these functions in the way expected by Struts application. To use these components, replace the <html:errors/> and <html:form> tags with:
<s:errors/> <s:form action="/logon">
Don't forget to change the closing </html:form> so that it reads </s:form> instead.
"In a JSF-and-Struts integrated application, JSF components handle the purely visually-oriented HTTP requests, while business transaction events go through the standard Struts-request-processing lifecycle" |
<h:inputText id="username" value="#{logonForm.username}"/> ... <h:inputSecret id="password" value="#{logonForm.password}"/>
Step 5: Change tags for submit and reset buttons The last change we need to make to the original JSP in Listing 1 is to replace the two button tags with their corresponding JavaServer Faces versions:
<h:commandButton id="submit" type="SUBMIT" value="#{messages['button.logon']"/> ... <h:commandButton id="reset" type="RESET" value="#{messages['button.reset']"/>
Note that JavaServer Faces allows us to localize the button labels using the same mechanism that we used for field prompts earlier. (Be sure to define the properties in your resource bundle for the button.logon and button.reset keys, otherwise this won't work.) Listing 2 shows the JSP with all these changes made. But there's still one more change to make to the infrastructure of your application server.
Listing 2: Example logon JSP after migration using the Struts-Faces Integration Library
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@ taglib uri="http://struts.apache.org/tags-faces" prefix="s" %> <f:view> <f:loadBundle var="messages" basename=" com.mycompany.my app.ApplicationResources"/> <html> <title><h:outputText value="#{messages['logon.title']"/></title> </head> <body> <s:errors/> <s:form action="/logon"/> <table border="0"> <tr> <td align="right"> <h:outputText value="{messages['prompt.username']"/> </td> <td align="left"> <html:text property="username"/> </td> </tr> <tr> <td align="right"> <h:outputText value="#{messages['prompt.password']"/> </td> <td align="left"> <h:inputSecret id="password" value="#{logonForm.password}"/> </td> </tr> <tr> <td align="right"> <h:commandButton id="submit" type="SUBMIT" value="#{messages['button.logon']"/> </td> <td align="left"> <h:commandButton id="reset" type="RESET" value="#{messages['button.reset']"/> </td> </tr> </table> </s:form> </body> </html> </f:view>
Step 6: Changes to the web application deployment descriptor Before your migrated page can actually work, you must modify the configuration files to refer to the faces resources rather than the JSPs. In struts-config.xml, change any logical forward pointing at /logon.jsp to /logon.faces instead, and add the JavaServer Faces servlet and servlet mapping declarations to your web.xml file (in the appropriate places):
<servlet> <servlet-name>faces</servlet-name> <servlet-class> javax.faces.webapp.FacesServlet <servlet-class> </servlet> ... <servlet-mapping> <servlet-name>faces</servlet-name> <url-pattern>*.faces</url-pattern> </servlet-mapping>
These settings tell your servlet container to forward any request URL that ends with .faces to the JavaServer Faces request processing lifecycle.
At runtime, the Struts-Faces Integration Library will customize that lifecycle (using standard extension points provided by JavaServer Faces) such that JavaServer Faces will process all user interface oriented events (such as expanding or contracting a node in a tree control), but form submits will be forwarded to the standard Struts request processor, which will perform the usual Struts functionality, such as performing server side validation, invoking the correct action, and navigating to the correct forward.
Begin Testing
None of the form beans or actions must be modified, so we can now go ahead and simply test this page for correct operation. When you are through with this page, go on to the next page. Or simply migrate and deploy only the most important pages, if you don't have time to do them all at once.
Conclusion
As you have seen from this simple example, taking advantage of the capabilities of JavaServer Faces components while maintaining your investment in the model tier and business logic functionality of existing applications is straightforward. In a JSF-and-Struts integrated application, JSF components handle the purely visually-oriented HTTP requests—for example, clicking a tree control node to expand its contents—while business transaction events go through the standard Struts-request-processing lifecycle.
In this modified application, you are not leveraging the controller capabilities provided by JavaServer Faces, since you are continuing to use those facilities in Struts. It's technically feasible to migrate the back-end processing from a Struts based architecture to a JavaServer Faces architecture as well—and this is a reasonable strategy if you want to minimize the number of different frameworks used in your application. Nonetheless, such a transition will be simpler if the view tier's pages are migrated first, as done in this article.
Next Steps
Oracle JDeveloper embraces popular open source frameworks and tools, providing built-in features for Struts, Ant, JUnit, and CVS. Such integration enables developers to use these open source tools to streamline their development process. For example, Oracle JDeveloper provides a Struts page flow modeler—a visual approach that simplifies the development of the application flow. Developers model page flows by simply dragging and dropping Struts components onto a diagram that is automatically synchronized with the source in the struts-config.xml file. As another example, Oracle ADF uses the Struts controller to manage the flow of Web applications.
Download Oracle JDeveloper 10 g (10.1.3) Developer Preview Here are some additional resources:
|
Craig McClanahan is a Senior Staff Engineer at Sun Microsystems, Inc. He is presently architect for the team that is building Sun Java Studio Creator, an IDE for graphically assembling web applications. Craig is also the original creator of Struts.
[ Back to J2EE Series Home Page ]
The Best of Both Worlds: Integrating JSF with Struts in Your J2EE Applications