Professional Java Development with the Spring Framework
Developing the MVC layer of an application using Struts involves developing Actions. In Struts, Actions are mapped to URLs in a configuration file. Wiring an Action typically looks like this:
<action path="/showOrder" type="org.springframework.prospring.ch14.ShowOrderAction"> <forward name="success" page="/showOrder.jsp"/> </action>
As we've seen, acquiring an application context is quite easy. Developing a Struts Action using a Spring-defined resource is easy as well:
public class ShowOrderAction extends Action { private OrderService orderService; public void setServlet(ActionServlet servlet) { super.setServlet(servlet); ServletContext sContext = servlet.getServletContext(); ApplicationContext appContext = WebApplicationContextUtils.getRequiredWebApplicationContext(sContext); orderService = (OrderService)appContext.getBean("orderService"); } public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest req, HttpServletResponse res) throws Exception { String orderId = RequestUtils.getRequiredStringParameter(req, "orderId"); Order order = orderService.getOrder(orderId); req.setAttribute("order", order); return mapping.findForward("success"); } }
As you can see, we're acquiring an application context as the Action initializes. We're reusing Spring's WebApplicationContextUtils class and the action won't initialize correctly if there is no context available.
Using ActionSupport
The class shown in the previous section is a simplified version of a utility base class Spring also provides: the org.springframework.web.struts.ActionSupport class. Using this class results in the following, slightly more compact, code:
public class ShowOrderAction2 extends ActionSupport { private OrderService orderService; protected void onInit() { orderService = (OrderService)getWebApplicationContext().getBean("orderService"); } public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest req, HttpServletResponse res) throws Exception { // same as above } }
The ActionSupport class offers access to the Spring-defined MessageSources through the getMessage SourceAccessor() method. Furthermore, you can do custom initialization and destruction (acquiring and releasing of resources for example) by overriding the onInit() and onDestroy() methods.
Using DelegationRequestProcessor and DelegationActionProxy
Using the ActionSupport class, we're still not able to fully utilize Spring's Dependency Injection features in our Struts Actions. We still have to acquire resources ourselves, something you should not want to do anymore, after having played with other parts of Spring.
Fortunately there are several solutions. Using the DelegatingRequestProcessor exposes the full power of Spring's DI features in combination with Struts Actions. You will be able to wire up your actions in an application context. There are two things you need to do. First you will have to enable the ContextLoaderPlugIn, which loads a WebApplicationContext from the /WEB-INF directory (just as the Spring DispatcherServlet does). You will also have to define a simple delegating proxy action for each of the Struts actions you've defined in the Spring application context.
A second option would be to create a simple autowiring Struts baseclass that loads the Spring application context and autowires itself. This concept is also used in several other parts of the Spring Framework — for example in the AbstractDependencyInjectionSpringContextTests base unit test class. We'll first discuss the DelegationActionProxy.
Defining the Struts ActionServlet and the ContextLoaderPlugIn
A typical Struts configuration consists of an ActionServlet and a Struts configuration file, containing your actions, global forwards, and so on. The plug-in has to be defined in your struts-config.xml file as follows:
<struts-config> <!-- action mappings --> <!-- message resources --> <plug-in className="org.springframework.web.struts.ContextLoaderPlugIn"/> </struts-config>
The plug-in closely resembles the classes used to set up a Spring DispatcherServlet. In Chapter 12, we reviewed all the configuration parameters available for you to tweak the context class, the namespace, and so on. All these parameters and the concepts explained there also apply to the ContextLoaderPlugIn. In short, the plug-in loads a WebApplicationContext named after your Struts ActionServlet and takes any contexts loaded by the ContextLoaderListener (or Servlet) as its parent.
<web-app> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <servlet> <servlet-name>struts</servlet-name> <servlet-class>org.apache.struts.action.ActionServlet</servlet-class> </servlet> ... </web-app>
If the preceding code is your web.xml file, the ContextLoaderPlugIn loads a web application context for you located in the /WEB-INF directory and named struts-servlet.xml (named after your action servlet). The listener we've defined loads an additional application context from /WEB-INF/ applicationContext.xml (in our case the context containing the OrderService).
Important | The WebApplicationContext named [servlet-name]-servlet.xml is the place to define your Struts actions and other web-related beans. Keep your middle-tier beans separated from any of the beans dealing with MVC functionality. This facilitates testing and keeps the structure of your application clean. |
We're almost done setting up the environment. The last thing we need to do is define a custom Struts RequestProcessor. The request processor is what Struts uses to process requests; in other words, it's how Struts looks up your actions and calls them. Spring provides a custom processor to allow you to define your beans in a Spring application context instead of in a Struts configuration file. This way you can fully use the Spring Dependency Injection features. A custom request processor can be defined in the Struts configuration file:
<struts-config> <!-- action mappings --> <controller processor/> <!-- message resources --> <plug-in className="org.springframework.web.struts.ContextLoaderPlugIn"/> </struts-config>
The next thing we need to do is define a WebApplicationContext containing our Struts actions, wired up using Spring:
# struts-servlet.xml <bean name="/showOrder" > <property name="orderService"/> <ref bean="orderService"/> </property> </bean>
This action is implemented as follows:
public class ShowOrderAction3 extends Action { private OrderService service; public void setOrderService(OrderService service) { this.service = service; } public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest req, HttpServletResponse res) throws Exception { String id = req.getParameter("orderId"); Order order = service.getOrder(id); req.setAttribute("order", order); return new ActionForward("/showOrder.jsp"); } }
As you can see, we're fully utilizing Spring's Dependency Injection features and don't have to bother with retrieving an application context anymore.
The final thing is to declare the Struts action in a Struts configuration file. Basically we're duplicating the action here. Unfortunately, this is what the Struts integration code needs to work correctly.
Important | Remember to keep your struts-config.xml files (or whatever they are named) up-to-date and to copy the bean name as the action name. |
In the Struts configuration file, we can also mention the action forwards to use:
<action name="/showOrder"> <forward name="success" path="/showOrder.jsp"/> </action>
By doing this, we can change the return statement to the following:
return mapping.findForward("success");
There are a few things you might need to know when integrating Spring with Struts:
-
If you have your own custom Struts request processor, which conflicts with the Spring DelegatingRequestProcessor, you can also use the org.springframework.web.struts.DelegatingActionProxy. Define this in your struts-config.xml file as an ordinary action (along with forwards and so on) and it will delegate all calls to the action to a Spring-defined one. An example of how to use this can be found in the sample code with this chapter.
-
Spring's DelegatingActionProxy and DelegatingRequestProcessor are aware of Struts modules. If the ShowOrderAction would have to live in a module called "ordermodule," the only thing we'd have to do would be to name our action /ordermodule/showOrder instead of /showOrder.
-
If you need to work with Tiles, use the TilesDelegatingRequestProcessor instead of the normal delegating one. It provides the same functionality of the original Struts TilesRequestProcessor.
-
If you ever need to write your own RequestProcessor, you should definitely have a look at the org.springframework.web.struts.DelegatingActionUtils class. It provides convenience methods that will determine the Spring bean name for a given action mapping.
Using an Auto Wiring Base Action
As noted before, there are other options as well to have your actions dependency injected. An approach also used in other places in the Spring Framework is to auto wire a certain object. Auto wiring is a concept explained earlier and the only thing you need to do to auto wire an object you've created is to pass it to the BeanFactory:
ApplicationContext context; // retrieve application context; SomeClass toAutoWire; // create object somehow context.getBeanFactory().autowireBeanProperties(toAutoWire, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true);
Remember the different auto wiring types: by name, by type, using a constructor, or automatic. We usually prefer to use auto wiring by type or by name because the automatic option often is a bit too magical and not sufficiently transparent. But let's get back to what we came for: auto wiring of our Struts action!
Remember OrderAction2 from a few pages back? It used the following code to retrieve a dependency:
orderService = (OrderService)getWebApplicationContext().getBean("orderService");
The fact that in this example we're going to use auto wiring removes the need to wire the action in the Spring context as we did with DelegationRequestProcessor and DelegationActionProxy. We'll refactor our action and let it extend AutowiringActionSupport, a Struts base class that we'll have to create ourselves.
public class ShowOrderAction3 extends AutowiringActionSupport { private OrderService orderService; public void setOrderService(OrderService orderService) { this.orderService = orderService; } public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest req, HttpServletResponse res) throws Exception { // same as in previous examples } }
The new base class we're about to create will pick up the setter. It will auto wire the action by type and pass the action itself to the auto wiring facility.
Public class AutowiringActionSupport extends ActionSupport { protected void onInit() { getWebApplicationContext().getBeanFactory().autowireByType(this, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true); } }
Of course, you can extend other ActionSupport classes as well as create an auto wiring base class.