Spring autowiring Struts 1 Actions
That’s right. Struts 1. Not hip. Not happenin’. But in the real world you might have bills to pay and a pair-programmer to feed.
The docs are a bit sketchy with Spring 2.5 stuff.
First you set up your service layer in web.xml
1 2 3 4 5 6 7 8 9 |
<context-param> <description>Used by ContextLoaderListener</description> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring-service-layer.xml</param-value> </context-param> <listener> <description>spring. looks for /WEB-INF/applicationContext.xml unless you set contextConfigLocation (see above)</description> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> |
Inside my spring-service-layer.xml file I tell spring to look for classes annotated with one of the 2.5 @Component stereotypes – which include the @Service annotation.
1 2 3 4 |
<context:annotation-config /> <context:component-scan base-package="com.rockhoppertech.example" scoped-proxy="targetClass"> </context:component-scan> |
Here’s a simple service:
1 2 3 4 5 6 7 8 9 |
@Service("HelloService") public class DefaultHelloService implements HelloService { /* * @see com.rockhoppertech.example.service.HelloService#getMessage() */ public String getMessage() { return "Yo"; } } |
Spring will create a root web context in application scope accessible with the attribute name org.springframework.web.context.WebApplicationContext.ROOT. My service appears here.
1 2 3 4 5 |
org.springframework.web.context.support.XmlWebApplicationContext@74907490: display name [Root WebApplicationContext] org.springframework.context.annotation.internalCommonAnnotationProcessor org.springframework.context.annotation.internalAutowiredAnnotationProcessor org.springframework.context.annotation.internalRequiredAnnotationProcessor HelloService |
The Spring docs say one approach is to configure Spring to manage your Actions as beans, using the ContextLoaderPlugin which will read the appropriately specified spring config files like this:
1 2 3 4 |
<plug-in className="org.springframework.web.struts.ContextLoaderPlugIn"> <set-property property="contextConfigLocation" value="/WEB-INF/action-servlet.xml"/> </plug-in> |
Then in action-servlet.xml for example you configure your actions as beans.
1 2 |
<bean name="/hello" class="com.rockhoppertech.example.struts.action.HelloAction"> </bean> |
Then in struts-config.xml you specify a special Spring controller:
1 2 3 4 |
<controller> <set-property property="processorClass" value="org.springframework.web.struts.DelegatingRequestProcessor"/> </controller> |
Also in struts-config.xml you have to change your actions definitions to use a Spring delegate. Notice
that the path matches the bean name specified in action-servlet.xml.
1 2 |
<action name="HelloForm" path="/hello" type="org.springframework.web.struts.DelegatingActionProxy"> |
Ok, that works fine. Not too dry though since I have to specify the bean twice.
Let’s try a newer Spring class.
In struts-config.xml I change the controller to this.
1 2 3 |
<controller> <set-property property="processorClass"value="org.springframework.web.struts.AutowiringRequestProcessor" /></controller> |
Since it’s named AutowiringRequestProcessor I’ve used the @Autowire annotation in my Action to get the service injected (which will NOT work if you annotate the instance variable and not a setter method).
The docs say that you specify just the usual action tag in struts-config.xml.
But how will the action beans then be created?
I simply annotated HelloAction with @Component. The scan for the service found the action too. The thing is, they live in the parent web context not in the child context created by the contextLoader plugin:
1 2 |
(accessible as an application scoped attribute named org.springframework.web.struts.ContextLoaderPlugIn.CONTEXT) org.springframework.web.context.support.XmlWebApplicationContext@4e8a4e8a: display name [WebApplicationContext for namespace 'action-servlet'] |
The beans you specify in your plugin config file live here. But since we’re doing the component scan chacha they aren’t living there anymore. Do you think that’s a bad thing?
Here’s my annotated Action:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
@Component public class HelloAction extends Action { private Logger logger = Logger.getLogger(HelloAction.class); private HelloService helloService; /** * @param helloService * the helloService to set */ @Autowired(required = true) public void setHelloService(HelloService helloService) { this.helloService = helloService; if (logger.isDebugEnabled()) { logger.debug("Service was set"); } } @Override public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { ActionMessages errors = new ActionMessages(); ActionForward forward = null; if (helloService == null) { errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage( "helloservice.null")); if (logger.isDebugEnabled()) { logger.debug("Hello service is null"); } } else { request.setAttribute("Greeting", helloService.getMessage()); if (logger.isDebugEnabled()) { logger.debug("Set request attribute Greeting"); } } if (!errors.isEmpty()) { saveErrors(request, errors); forward = mapping.findForward("failure"); } else { forward = mapping.findForward("success"); } return forward; } |
Here is a mavenized project with all the sources for this post.
Hello Gene,
Great photo of you and your “pair programmer”! I have three of my own. And you’re right, there’s nothing sexy about Struts 1, but the reality is that some of us still make a living with these outdated technologies.
Keep blogging!
Tina
That was an inspiring post,
These are some great instructions, thanks for helping out,
Thanks for writing, most people don’t bother.
Thanks, Gene, very helpful! Exactly what I was looking for.
A special thank-you for the test setup in the sample project download! That was my next step, and it was a pleasant surprise to find tests in the sample.