Get a better handle on Struts actions, with Spring

Three ways to integrate Struts applications with Spring

Struts Recipes co-author George Franciscus is back with another great Struts integration recipe -- this time for importing Struts applications into the Spring framework. Follow along as George shows you how to revamp Struts actions so they can be managed just like Spring beans. The result is a boosted web framework that easily reaps the benefits of Spring AOP.

The Inversion of Control (IOC) design pattern has been generating buzz for long enough now that you've surely heard of it. If you've used the Spring framework in any capacity, then you've seen its principles in action. In this article, you'll learn about the power of the IOC pattern first-hand as I use its principles to inject a Struts application into the Spring framework.

The advantages of integrating a Struts application into the Spring framework are manifold. First off, Spring was explicitly designed to resolve some of the real-world problems of JEE, such as complexity, poor performance, testability, and much more. Second, the Spring framework includes an AOP implementation that lets you apply aspect-oriented techniques to normal object-oriented code. Third, some might say that the Spring framework just handles Struts better than Struts handles itself. But that's a matter of opinion, so follow along as I demonstrate three approaches to integrating Struts applications into the Spring framework, and then decide for yourself.

The approaches I demonstrate are all relatively simple to execute but they offer distinctly different advantages. So that you can fully understand each approach, I've created a separate working example for each one. See the Download section for the complete example source code. See Resources to download Struts MVC and the Spring framework.

What's so great about Spring?

Spring creator Rod Johnson took a critical eye to Java™ Enterprise software development and suggested that many enterprise issues could be resolved by the strategic use of the IOC pattern, also known as dependency injection. When Rod and a dedicated team of open-source developers put his theories into practice, the result was the Spring framework. In a nutshell, Spring is a lightweight container that makes it easy to wire objects together using an external XML configuration file. Each object can receive a reference to a dependent object by exposing a JavaBean property, leaving you with the simple task of "wiring them up" in an XML configuration file.

IOC and Spring

IOC is a design pattern that externalizes application logic so that it can be injected into client code rather than written into it. Combining IOC with the use of programming to interfaces, as the Spring framework does, yields an architecture that reduces the client's dependency on implementation-specific logic. See Resources for more on IOC and Spring.

Dependency injection is a powerful feature, but the Spring framework offers much more. Spring supports pluggable transaction managers to give you a broader range of choices for transaction handling. It integrates leading persistence frameworks while also offering a consistent exception hierarchy. Spring also provides a simple mechanism for applying aspect-oriented code against normal, object-oriented code.

Spring AOP lets you use interceptors to intercept application logic at one or more execution points. Interceptors are widely used for for logging because consolidating an application's logging logic in interceptors results in a more readable, functional code base. As you'll soon see, Spring AOP ships with its own interceptors for addressing cross-cutting concerns and also lets you write your own.


Integrating Struts and Spring

Like Struts, Spring can also function as an MVC implementation. Both frameworks have their merits and drawbacks, although most would agree that Struts is still king when it comes to MVC. Many development teams have learned to rely on Struts as the foundation for building quality software under strict deadlines. With so much momentum behind Struts, even development teams that would like to integrate features of the Spring framework don't want to switch to Spring MVC. The good news is that you don't have to. The Spring architecture allows you to connect Struts as your Web framework to Spring-based business and persistence layers. The end result is that you can have your cake and eat it too!

In the recipes that follow, you'll learn three ways to integrate Struts MVC into the Spring framework. I'll expose the cons of each recipe as well as its comparative advantages. Once you've seen all three in action, I'll show you an exciting application of the approach I like best.


Three little recipes

Each of the following integration techniques (or recipes) has its merits, as well as its own particular quirks. I'm partial to only one of them, but knowing them all will deepen your understanding of both Struts and Spring. It will also provide you with a broad range of options for dealing with various scenarios. The recipes are as follows:

  • Use Spring's ActionSupport class to integrate Struts
  • Override the Struts RequestProcessor with Spring's DelegatingRequestProcessor
  • Delegate Struts Action management to the Spring framework

Loading the application context

No matter which technique you use, you will need to use the Spring ContextLoaderPlugin to load the Spring application context for the Struts ActionServlet. Simply add the plug-in to your struts-config.xml file as you would any other plug-in, as shown here:

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

As previously mentioned, you'll find the complete source for the three fully functional example applications in the Download section. Each example presents a different approach to combining Struts and Spring for a book-search application. You can follow the basics of the examples here, but download the applications to see all the nitty-gritty details!


Recipe 1. Use Spring's ActionSupport

Creating a Spring context manually is the most intuitive way to integrate Struts with Spring. To make it even easier, Spring offers a little help. The org.springframework.web.struts.ActionSupport class provides a getWebApplicationContext() method to easily obtain a Spring context. All you need to do is extend your action from Spring's ActionSupport instead of the Struts Action class, as shown in Listing 1:

Listing 1. Using ActionSupport to integrate Struts
package ca.nexcel.books.actions;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.ActionError;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.DynaActionForm;
import org.springframework.context.ApplicationContext;
import org.springframework.web.struts.ActionSupport;

import ca.nexcel.books.beans.Book;
import ca.nexcel.books.business.BookService;

public class SearchSubmit extends ActionSupport {   |(1)


  public ActionForward execute(
    ActionMapping mapping,
    ActionForm form,
    HttpServletRequest request,
    HttpServletResponse response)
    throws IOException, ServletException {

    DynaActionForm searchForm = (DynaActionForm) form;
    String isbn = (String) searchForm.get("isbn");
		
    //the old fashion way
    //BookService bookService = new BookServiceImpl();
		
    ApplicationContext ctx = 
      getWebApplicationContext();    |(2)
    BookService bookService = 
      (BookService) ctx.getBean("bookService");   |(3)
        
  Book book = bookService.read(isbn.trim());

    if (null == book) {
      ActionErrors errors = new ActionErrors();
      errors.add(ActionErrors.GLOBAL_ERROR,new ActionError
        ("message.notfound"));
      saveErrors(request, errors);
      return mapping.findForward("failure") ;
  }

    request.setAttribute("book", book);
    return mapping.findForward("success");
  }
}

Let's quickly consider what's happening here. At (1), I create an Action by extending from the Spring ActionSupport class rather than the Struts Action class. At (2), I use the getWebApplicationContext() method to obtain an ApplicationContext. To obtain the business service, I use the context obtained at (2) to look up a Spring bean at (3).

This technique is simple and easy to understand. Unfortunately, it couples the Struts action to the Spring framework. If you ever decide to replace Spring, you would have to rewrite the code. Moreover, because the Struts action isn't under Spring's control, it can't reap the benefits of Spring AOP. This technique may be useful when using multiple independent Spring contexts, but for the most part it's not as desirable a solution as the other two choices.


Recipe 2. Override the RequestProcessor

Decoupling Spring from the Struts action is a much smarter design choice. One way to do this is to override the Struts RequestProcessor processor with the org.springframework.web.struts.DelegatingRequestProcessor class, as shown in Listing 2:

Listing 2. Integration via Spring's DelegatingRequestProcessor
<?xml version="1.0" encoding="ISO-8859-1" ?>

<!DOCTYPE struts-config PUBLIC
          "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
          "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">

<struts-config>
 <form-beans>
    <form-bean name="searchForm" 
      type="org.apache.struts.validator.DynaValidatorForm">
               <form-property name="isbn"    type="java.lang.String"/>
    </form-bean>
  
  </form-beans>

 <global-forwards type="org.apache.struts.action.ActionForward">
     <forward   name="welcome"                path="/welcome.do"/>
     <forward   name="searchEntry"            path="/searchEntry.do"/>
     <forward   name="searchSubmit"           path="/searchSubmit.do"/>
 </global-forwards>

 <action-mappings>
    <action    path="/welcome" forward="/WEB-INF/pages/welcome.htm"/>
    <action    path="/searchEntry" forward="/WEB-INF/pages/search.jsp"/>
    <action    path="/searchSubmit" 
               type="ca.nexcel.books.actions.SearchSubmit"
               input="/searchEntry.do"
               validate="true"
               name="searchForm">
              <forward name="success" path="/WEB-INF/pages/detail.jsp"/>
              <forward name="failure" path="/WEB-INF/pages/search.jsp"/>
    </action>  

 </action-mappings>

 <message-resources parameter="ApplicationResources"/>

 <controller processorClass="org.springframework.web.struts.
   DelegatingRequestProcessor"/> |(1)

 <plug-in className="org.apache.struts.validator.ValidatorPlugIn">
    <set-property property="pathnames" 
      value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml"/>
 </plug-in>


 <plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">
    <set-property property="csntextConfigLocation" value="/WEB-INF/beans.xml"/>
 </plug-in>
 
</struts-config>

Here, I've used the <controller> tag to override the default Struts RequestProcessor with the DelegatingRequestProcessor. My next step is to register the action in my Spring config file, as shown in Listing 3:

Listing 3. Registering an action in the Spring config file
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" 
  "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>
  <bean id="bookService" class="ca.nexcel.books.business.BookServiceImpl"/>

  <bean name="/searchSubmit" 
    class="ca.nexcel.books.actions.SearchSubmit"> |(1)
     <property name="bookService">
        <ref bean="bookService"/>
     </property>
  </bean>
</beans>

Note that at (1), I've registered a bean using the name attribute to match the struts-config action mapping name. The SearchSubmit action exposes a JavaBean property, allowing Spring to populate the property at run time, as shown in Listing 4:

Listing 4. A Struts action with a JavaBean property
package ca.nexcel.books.actions;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionError;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.DynaActionForm;

import ca.nexcel.books.beans.Book;
import ca.nexcel.books.business.BookService;

public class SearchSubmit extends Action {
	
  private BookService bookService;
  public BookService getBookService() {
    return bookService;
  }

  public void setBookService(BookService bookService) { | (1)
    this.bookService = bookService; 
  } 

  public ActionForward execute(
    ActionMapping mapping,
    ActionForm form,
    HttpServletRequest request,
    HttpServletResponse response)
    throws IOException, ServletException {

    DynaActionForm searchForm = (DynaActionForm) form;
    String isbn = (String) searchForm.get("isbn");
		
  Book book = getBookService().read(isbn.trim());  |(2)

    if (null == book) {
      ActionErrors errors = new ActionErrors();
      errors.add(ActionErrors.GLOBAL_ERROR,new ActionError("message.notfound"));
      saveErrors(request, errors);
      return mapping.findForward("failure") ;
  }

      request.setAttribute("book", book);
      return mapping.findForward("success");
  }

}

In Listing 4, you can see how to build the Struts action. At (1), I create a JavaBean property. This property is automatically populated by the DelegatingRequestProcessor. This design protects the Struts action from knowing it's being managed by Spring while giving you all the benefits of Spring's action management framework. Because your Struts actions are oblivious to the existence of Spring, you can swap out Spring for some other inversion of control container without refactoring your Struts code.

While the DelegatingRequestProcessor approach is definitely better than the first one, it does have some problems. If you were using a different RequestProcessor, then you would need to integrate the Spring DelegatingRequestProcessor manually. The added code would become a maintenance hassle and would also reduce your application's flexibility going forward. Moreover, there has been some talk of replacing the Struts RequestProcessor with a chain of command. Such a change would negatively impact the longevity of this solution.


Recipe 3. Delegate action management to Spring

A much better solution is to delegate Struts action management to the Spring framework. You can do this by registering a proxy in the struts-config action mapping. The proxy is responsible for looking up the Struts action in the Spring context. Because the action is under Spring's control, it populates the action's JavaBean properties and leaves the door open to applying features such as Spring's AOP interceptors.

In Listing 5, the Action class is the same as it was in Listing 4. However, the struts-config is a little different:

Listing 5. The delegation method of Spring integration
<?xml version="1.0" encoding="ISO-8859-1" ?>

<!DOCTYPE struts-config PUBLIC
          "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
          "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">

<struts-config>
 <form-beans>
    <form-bean name="searchForm" 
      type="org.apache.struts.validator.DynaValidatorForm">
               <form-property name="isbn"    type="java.lang.String"/>
    </form-bean>
  
  </form-beans>

 <global-forwards type="org.apache.struts.action.ActionForward">
     <forward   name="welcome"                path="/welcome.do"/>
     <forward   name="searchEntry"            path="/searchEntry.do"/>
     <forward   name="searchSubmit"           path="/searchSubmit.do"/>
 </global-forwards>

 <action-mappings>
    <action    path="/welcome" forward="/WEB-INF/pages/welcome.htm"/>
    <action    path="/searchEntry" forward="/WEB-INF/pages/search.jsp"/>
    <action    path="/searchSubmit" 
             type="org.springframework.web.struts.DelegatingActionProxy" |(1)
             input="/searchEntry.do"
             validate="true"
             name="searchForm">
             <forward name="success" path="/WEB-INF/pages/detail.jsp"/>
             <forward name="failure" path="/WEB-INF/pages/search.jsp"/>
    </action>  

 </action-mappings>

 <message-resources parameter="ApplicationResources"/>


 <plug-in className="org.apache.struts.validator.ValidatorPlugIn">
    <set-property 
    property="pathnames" 
    value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml"/>
 </plug-in>


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

 
</struts-config>

Listing 5 is a typical struts-config.xml file, except for one small difference. Instead of declaring the action's class name, it registers the name of Spring's proxy class, as shown at (1). The DelegatingActionProxy class uses the action mapping name to look up the action in the Spring context. This is the context that was declared with ContextLoaderPlugIn.

Registering a Struts action as a Spring bean is very straightforward, as shown in Listing 6. I simply create a bean using the name of the action mapping using the <bean> tag's name attribute (in this case, "/searchSubmit"). The action's JavaBean properties are populated like any Spring bean:

Listing 6. Register a Struts action in the Spring context
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" 
 "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>
  <bean id="bookService" class="ca.nexcel.books.business.BookServiceImpl"/>

  <bean name="/searchSubmit"   
        class="ca.nexcel.books.actions.SearchSubmit">
     <property name="bookService">
        <ref bean="bookService"/>
     </property>
  </bean>

</beans>

The benefits of action delegation

The action-delegation solution is the best of the three. The Struts action has no knowledge of Spring and could be used in non-Spring applications without changing a single line of code. It's not at the mercy of a change to the RequestProcessor, and it can take advantage of Spring's AOP features.

The benefits of action delegation don't stop there, either. Once you have your Struts action under Spring's control, you can leverage Spring to give them more pizzazz. For example, without Spring, all Struts actions must be threadsafe. If you set the <bean> tag's singleton attribute to "false," however, your application will have a newly minted action object on every request. You might not need this feature, but it's nice to know you have it in your back pocket. You can also take advantage of Spring's lifecycle methods. For example, the <bean> tag's init-method attribute is used to run a method when the Struts action is instantiated. Similarly, the destroy-method attribute executes a method just before the bean is removed from the container. These methods are a great way to manage expensive objects in much the same way as the Servlet lifecycle does.


Intercepting Struts

As previously mentioned, one of the chief advantages of combining Struts and Spring, and doing it by delegating Struts actions to the Spring framework, is that you can apply Spring's AOP interceptors to your Struts actions. By applying Spring interceptors to Struts actions, you can tackle cross-cutting concerns with minimal effort.

Spring offers a few built-in interceptors, but I'll show you how to create your own and apply it to a Struts action. To use an interceptor, you need to do three things:

  1. Create the interceptor.
  2. Register it.
  3. Declare where it will intersect the code.

This is pretty simple stuff but also very powerful. For example, in Listing 7, I create a logging interceptor for a Struts action. This interceptor prints out a statement before every method call:

Listing 7. A simple logging interceptor
package ca.nexcel.books.interceptors;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

public class LoggingInterceptor implements MethodBeforeAdvice {

   public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("logging before!");
    }
}

This interceptor is very simple. The before() method is executed before every method in its intersection. In this case, it prints out a statement, but it could do anything you like. The next step is to register the interceptor in the Spring configuration file, shown in Listing 8:

Listing 8. Registering the interceptor in the Spring config file
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" 
  "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>
  <bean id="bookService" class="ca.nexcel.books.business.BookServiceImpl"/>

  <bean name="/searchSubmit" 
        class="ca.nexcel.books.actions.SearchSubmit">
     <property name="bookService">
        <ref bean="bookService"/>
     </property>
  </bean>

  <!--  Interceptors --> 
  <bean name="logger"    
    class="ca.nexcel.books.interceptors.LoggingInterceptor"/> |(1)

  <!-- AutoProxies -->
  <bean name="loggingAutoProxy" 
        class="org.springframework.aop.framework.autoproxy.
          BeanNameAutoProxyCreator"> |(2)
    <property name="beanNames">
          <value>/searchSubmit</valuesgt; |(3)
    </property>
    <property name="interceptorNames">
        <list>
          <value>logger</value> |(4)
        </list>
    </property>
   </bean>

</beans>

As you've probably noticed, Listing 8 extends the application shown in Listing 6 to include an interceptor. Details are as follows:

  • At (1), I register the interceptor.
  • At (2), I create a bean name autoproxy describing how the interceptor is applied. There are other ways to define intersections, but this approach is common and easy to do.
  • At (3), I register the Struts action as the bean that will be intercepted. If you wanted to intersect other Struts actions, then you could simply create additional <value> tags under "beanNames."
  • At (4), when the interception occurs, I execute the name of the interceptor bean created at (1). All the interceptors listed here are applied against the "beanNames."

That's it! As this example shows, putting your Struts actions under control of the Spring framework opens up a whole new set of options for handling your Struts applications. In the case of this example, action delegation makes it easy to utilize Spring interceptors for better logging in Struts applications.


In conclusion

In this article, you learned three recipes for integrating Struts actions into the Spring framework. Using Spring's ActionSupport to integrate Struts (as I did in the first recipe) is quick and easy but couples your Struts actions to the Spring framework. If you ever needed to port the application to a different framework you would need to rewrite the code. The second solution of delegating the RequestProcessor cleverly decouples your code, but it doesn't necessarily scale well and may not last long if the Struts RequestProcessor is revised to a chain of command. The third approach is the best of the three: delegating Struts actions to the Spring framework results in decoupled code that lets you utilize Spring's features (such as logging interceptors) in your Struts applications.

Each of the three Struts-Spring integration recipes is realized as a complete working application. See the Download section to study them in detail.


Downloads

DescriptionNameSize
ActionSupport sample codej-sr2-actionsupport.zip5 MB
RequestProcessor sample codej-sr2-requestprocessor.zip5 MB
Delegate sample codej-sr2-delegate.zip5 MB

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Java technology on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology, Web development
ArticleID=95030
ArticleTitle=Get a better handle on Struts actions, with Spring
publish-date=10112005