Struts Architecture

In order that we can refamiliarize ourselves with the general flow in a struts application, take a look at this flowchart:

Click here for alternatives.

The ActionForm and the JSP page are inextricably linked. Also, the ActionForm is a convenience container: struts calls the setXXX methods while your servlet (extending Action) calls the getXXX methods, where XXX is the normalized form of the field name. When I say normalized I mean that the standard JavaBeans rules apply. A field by the name of lastName will have a mutator method of setLastName and an accessor method of getLastName.

Note that the form submit button should always send us back to the action which generated the JSP in the first place. This is important in the MVC (model-view-controller) architecture. We have to be able to manage the process flow using the servlet associated with the ActionForm. The JSP could display multiple submit buttons with different values and the navigation could be be based on those values.

Let's take a concrete example, one I've used multiple times as I'm building an application which uses these capabilities. Suppose that you've got a form for collecting information about previous work experience. Since it's part of a larger application, we should probably have three different submit buttons:

In all three cases we need to check whether the user has already entered information in the form which should be saved. Using an industry-standard approach, we utilize both client- and server-side validation. So our JavaScript checks to see whether any fields (apart from those automatically populated) contain data and pops up a dialog when the user tries to move forward or backward, asking if the data should be discarded. If so then the fields are blanked and form submission continues.

In this scenario we won't be able to use a DynaValidatorForm since there is some intelligence which needs to be applied. We need to determine whether the form is effectively blank, i.e. nothing except automatically populated fields (such as selects) contain data. If the form is blank then we don't need to generate any errors, otherwise we perform validation and return the appropriate ActionErrors.

This blank form determination also needs to be performed in the Action servlet so it makes sense to perform the check in a separate class. The reason we need to know if the form is blank is because we don't want to insert empty records into the database, which is typically the first step in Action servlet processing. Next comes the navigation selection. Page forward, back, etc. These targets are conveniently specified in the forward child elements of the action elements.

One of the strengths of the struts framework is convenient extensibility with minimal code rewriting. This is easier to achieve if you, the programmer, take advantage of the capabilities. Take a look at this abbreviated extract from the struts-config.xml file:

        <action path="/experience" ...
            <forward name="previous" path="/education">
            <forward name="next" path="/preferences">
        </action>
	<action path="/preferences" ...
            <forward name="previous" path="/experience">

Assume that the servlet code is using the framework properly, namely performing a lookup like so:

        public ActionForward execute( ActionMapping mapping,
          ActionForm actionForm, HttpServletRequest req,
          HttpServletResponse resp ) {
               ...
               return( mapping.findForward( "next" ) );
        }

Still using our résumé example, suppose that we wanted to also collect information about accreditations, professional association memberships, etc. We create a new servlet and a new ActionForm, perhaps using the DynaValidationForm (I must admit that I find this to be an incredibly useful construct; see here for examples of usage). We can then insert this page into our process flow by modifying the struts-config.xml file as follows:

        <action path="/experience" ...
            <forward name="previous" path="/education">
            <forward name="next" path="/associations">
        </action>
        <action path="/associations" ...
            <forward name="previous" path="/experience">
            <forward name="next" path="/preferences">
        </action>
	<action path="/preferences" ...
            <forward name="previous" path="/associations">

The beauty of this approach is that absolutely no other code is impacted. I don't have to modify and recompile the servlets associated with the /experience or /preferences paths.

Another important element of using the framework in the manner intended is that you can eliminate pollution of the session context. Since you always return to the servlet associated with the JSP, any data items you've temporarily stored in the session context can be removed before navigating to the next stage of processing.

Alternatives  (NEW!)

I initially wrote this document when someone on usenet asked for some clarification as to the general flow of control in Struts. Subsequent experimentation and implementation demonstrate that there is more than one approach. Part of the difficulty with the original model is that it can give rise to some duplication of effort. In order to have the correct elements display on the JSP associated with a subsequent page then you need to generate the data in the prior page. You could manage this by incorporating the required functionality in a bean which both Actions could invoke but that's not a compartmentalized approach.

What you can do is short-circuit the flow by forwarding to the Action instead of the JSP. But then that will force the population of the ActionForm with request parameters (from the previous form) which don't match the ones expected. If validate="true" has been specified for the form then the JSP could contain numerous error indications when presented to the user after validate is invoked on the ActionForm. Alternatively you could manually create the ActionForm for the subsequent page, populate it using the appropriate mutator methods, and invoke execute on the Action.

There's another problem I discovered with forwarding to the Action which appears when you use identically named fields on multiple forms. Consistent naming is actually beneficial, just like adhering to a consistent coding standard. Imagine however that you have image submit buttons on two pages, both named "back". The one page finds that the back button has been pressed and forwards to the previous page. Since the request parameters are used to populate the ActionForm, the previous page also detects that the "back" button has been pressed. This could lead to very confusing behaviour.

A solution to the above can be found in the javax.servlet.ServletRequest interface. It's as simple as invoking removeAttribute on the request. In other words, when your Action processes an element such as a "back" button, simply remove the attribute from the request before forwarding.

A solution to these problems is to specify redirect="true" in the struts-config.xml file. This actually achieves two goals: all the request parameters are silently discarded and the URL displayed in the browser is consistent with the actual location. The drawback is that the flow is not as efficient since an extra round-trip from the browser is required.

Yet another possibility presents itself, one which I used on an e-commerce site. In this case I had multiple actions forwarding to a common Action servlet. It was the credit card processing stage, requiring confirmation and display of the completed order, or offering the chance to re-enter data if an error was encountered. I used the class of the ActionForm passed to the execute method to determine the operation flow. There was a lot of duplication of processing steps within this transaction so the approach was actually quite efficient.

The topic of form scopes is also thorny. In the idealized flow shown previously, fields in a form within a JSP are typically populated from an ActionForm. When the form on the JSP is submitted, either a new ActionForm is created or an unused but still existant copy is populated by the possibly updated fields on the form. The reset method is called in either case. This is the standard "request" scope and works for the majority of cases.

The "session" scope is intended to make ActionForms available in session scope for subsequent processing. It is the responsibility of the programmer to remove these or just allow them to disappear when the session expires. A side effect is that the session is searched for the named ActionForm before any attempt is made to instantiate a new one. This permits the re-use of information when, for instance, a page has to be revisisted, perhaps to correct information.

Copyright © 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 by Phil Selby. All rights reserved.