17.4 Apache Struts 1.x and 2.x

Struts is the de facto web framework for Java applications, mainly because it was one of the first to be released (June 2001). Invented by Craig McClanahan, Struts is an open source project hosted by the Apache Software Foundation. At the time, it greatly simplified the JSP/Servlet programming paradigm and won over many developers who were using proprietary frameworks. It simplified the programming model, it was open source (and thus free as in beer), and it had a large community, which allowed the project to grow and become popular among Java web developers.

[Note]Note

The following section discusses Struts 1 a.k.a. "Struts Classic".

Struts 2 is effectively a different product - a successor of WebWork 2.2 (as discussed in Section 17.5, “WebWork 2.x”), carrying the Struts brand now. Check out the Struts 2 Spring Plugin for the built-in Spring integration shipped with Struts 2. In general, Struts 2 is closer to WebWork 2.2 than to Struts 1 in terms of its Spring integration implications.

To integrate your Struts 1.x application with Spring, you have two options:

17.4.1 ContextLoaderPlugin

The ContextLoaderPlugin is a Struts 1.1+ plug-in that loads a Spring context file for the Struts ActionServlet. This context refers to the root WebApplicationContext (loaded by the ContextLoaderListener) as its parent. The default name of the context file is the name of the mapped servlet, plus -servlet.xml. If ActionServlet is defined in web.xml as <servlet-name>action</servlet-name>, the default is /WEB-INF/action-servlet.xml.

To configure this plug-in, add the following XML to the plug-ins section near the bottom of your struts-config.xml file:

<plug-in className="org.springframework.web.struts.ContextLoaderPlugIn"/>

The location of the context configuration files can be customized using the 'contextConfigLocation' property.

<plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">
  <set-property property="contextConfigLocation"
      value="/WEB-INF/action-servlet.xml,/WEB-INF/applicationContext.xml"/>
</plug-in>

It is possible to use this plugin to load all your context files, which can be useful when using testing tools like StrutsTestCase. StrutsTestCase's MockStrutsTestCase won't initialize Listeners on startup so putting all your context files in the plugin is a workaround. (A bug has been filed for this issue, but has been closed as 'Wont Fix').

After configuring this plug-in in struts-config.xml, you can configure your Action to be managed by Spring. Spring (1.1.3+) provides two ways to do this:

  • Override Struts' default RequestProcessor with Spring's DelegatingRequestProcessor.

  • Use the DelegatingActionProxy class in the type attribute of your <action-mapping>.

Both of these methods allow you to manage your Actions and their dependencies in the action-servlet.xml file. The bridge between the Action in struts-config.xml and action-servlet.xml is built with the action-mapping's "path" and the bean's "name". If you have the following in your struts-config.xml file:

<action path="/users" .../>

You must define that Action's bean with the "/users" name in action-servlet.xml:

<bean name="/users" .../>

17.4.1.1 DelegatingRequestProcessor

To configure the DelegatingRequestProcessor in your struts-config.xml file, override the "processorClass" property in the <controller> element. These lines follow the <action-mapping> element.

<controller>
  <set-property property="processorClass"
      value="org.springframework.web.struts.DelegatingRequestProcessor"/>
</controller>

After adding this setting, your Action will automatically be looked up in Spring's context file, no matter what the type. In fact, you don't even need to specify a type. Both of the following snippets will work:

<action path="/user" type="com.whatever.struts.UserAction"/>
<action path="/user"/>

If you're using Struts' modules feature, your bean names must contain the module prefix. For example, an action defined as <action path="/user"/> with module prefix "admin" requires a bean name with <bean name="/admin/user"/>.

[Note]Note

If you are using Tiles in your Struts application, you must configure your <controller> with the DelegatingTilesRequestProcessor instead.

17.4.1.2 DelegatingActionProxy

If you have a custom RequestProcessor and can't use the DelegatingRequestProcessor or DelegatingTilesRequestProcessor approaches, you can use the DelegatingActionProxy as the type in your action-mapping.

<action path="/user" type="org.springframework.web.struts.DelegatingActionProxy"
    name="userForm" scope="request" validate="false" parameter="method">
  <forward name="list" path="/userList.jsp"/>
  <forward name="edit" path="/userForm.jsp"/>
</action>

The bean definition in action-servlet.xml remains the same, whether you use a custom RequestProcessor or the DelegatingActionProxy.

If you define your Action in a context file, the full feature set of Spring's bean container will be available for it: dependency injection as well as the option to instantiate a new Action instance for each request. To activate the latter, add scope="prototype" to your Action's bean definition.

<bean name="/user" scope="prototype" autowire="byName"
    class="org.example.web.UserAction"/>

17.4.2 ActionSupport Classes

As previously mentioned, you can retrieve the WebApplicationContext from the ServletContext using the WebApplicationContextUtils class. An easier way is to extend Spring's Action classes for Struts. For example, instead of subclassing Struts' Action class, you can subclass Spring's ActionSupport class.

The ActionSupport class provides additional convenience methods, like getWebApplicationContext(). Below is an example of how you might use this in an Action:

public class UserAction extends DispatchActionSupport {

    public ActionForward execute(ActionMapping mapping,
                                 ActionForm form,
                                 HttpServletRequest request,
                                 HttpServletResponse response) throws Exception {
        if (log.isDebugEnabled()) {
            log.debug("entering 'delete' method...");
        }
        WebApplicationContext ctx = getWebApplicationContext();
        UserManager mgr = (UserManager) ctx.getBean("userManager");
        // talk to manager for business logic
        return mapping.findForward("success");
    }
}

Spring includes subclasses for all of the standard Struts Actions - the Spring versions merely have Support appended to the name:

The recommended strategy is to use the approach that best suits your project. Subclassing makes your code more readable, and you know exactly how your dependencies are resolved. However, using the ContextLoaderPlugin allow you to easily add new dependencies in your context XML file. Either way, Spring provides some nice options for integrating the two frameworks.