Integrando Struts 2 y Spring IoC

Última actualización: 08/03/2014

logo spring

Originalmente este artículo nació como un tip para la sección tips Struts 2, pero puesto que expone una integración de frameworks muy interesante y habitual he optado por tratarla con algo más de profundidad y darle más extensión de la que cabría esperar en un tip. Así pues, vamos a ver paso a paso lo fácil que es integrar el contenedor IoC de Spring con Struts 2 para poder inyectar beans en los Action e incluso definir los Actions como beans de Spring, ya sea a través de xml o mediante anotaciones.

Nota: no es necesario que los Actions sean definidos en Spring para poder inyectar las dependencias en ellos pero es la práctica habitual y lo que se mostrará en este artículo.

Ejemplo de integración

Crearemos un proyecto Maven similar a los utilizados en los tips Struts 2. Dejaremos el pom para un poco más adelante y empezaré con el código que se refiere estrictamente a la integración Struts 2 y Spring.

Para probar la inyección de dependencias en nuestro Action, definiremos el típico «bean de servicio» que pertenece a la capa de negocio de una arquitectura web clásica. Nuestro proyecto es sumamente sencillo, y para simplificar no usaré interfaces

package com.danielme.blog.demo.struts2spring.services;

import java.util.Date;

public class SpringDemoService
{
    public Date getDateNow()
    {
   	 return new Date();
    }
}

Este es el único Action de la aplicación:

package com.danielme.blog.demo.struts2spring.actions;

import java.text.SimpleDateFormat;

import com.danielme.blog.demo.struts2spring.services.SpringDemoService;
import com.opensymphony.xwork2.ActionSupport;

/**
 *
 * @author danielme.com
 *
 */
public class SpringDemoAction extends ActionSupport
{
    private static final long serialVersionUID = 1L;

    private SpringDemoService springDemoService;


    public String execute()
    {
   	 return SUCCESS;
    }

    public String getDate()
    {
   	 SimpleDateFormat sdf = new SimpleDateFormat(getText("pattern"));
   	 return sdf.format(springDemoService.getDateNow());
    }

    public String getId()
    {
   	 return this.toString();
    }

    public void setSpringDemoService(SpringDemoService springDemoService)
    {
   	 this.springDemoService = springDemoService;
    }

}

Como se puede deducir, su única funcionalidad es la de mostrar en pantalla la fecha formateada que devuelve el bean de servicio y el identificador del objeto del Action, más adelante veremos el porqué de esto último. La vista de este action es la siguiente:

<%@ page contentType="text/html; charset=UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> 
<html>
	<head>
		<%@include file="/jsp/head.jsp" %>		
	</head>       
	
	<body>
	
	<s:property value="date"/>
	</br>
	<s:property value="id"/>
			
		<%@include file="/jsp/footer.jsp"%> 		
		
	</body>
	
</html>

Y ahora, unamos las piezas paso a paso.

  1. En el pom.xml necesitaremos, además de las dependencias de Struts 2 y Spring (sólo necesitamos el módulo spring-web), el plugin que integra Struts 2 y Spring.
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    	<modelVersion>4.0.0</modelVersion>
    	<groupId>com.danielme.blog.demo.struts2spring</groupId>
    	<artifactId>Struts2-SpringIoC</artifactId>
    	<version>1.0</version>
    	<packaging>war</packaging>
    
    	<name>Struts 2 - Spring IoC Integration</name>
    	<description>Struts 2 Spring Integration</description>
    	<url>https://danielme.com/2013/11/03/integrando-struts-2-y-spring-ioc</url>
    	<inceptionYear>2013</inceptionYear>
    
    	<scm>
    		<url>https://danielme.com/2013/11/04/integrando-struts-2-y-spring-ioc/</url>
    	</scm>
    
    	<licenses>
    		<license>
    			<name>GPL 3</name>
    			<url>http://www.gnu.org/licenses/gpl-3.0.html</url>
    		</license>
    	</licenses>
    
    	<properties>
    		
    		<java.version>1.6</java.version>
    		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    	
    		<struts2.version>2.3.16.1</struts2.version>
    		<spring.version>3.2.8.RELEASE</spring.version>
    		<slf4jlog4j.version>1.7.6</slf4jlog4j.version>
    		
    	</properties>
    
    
    	<build>
    		<plugins>
    			<plugin>
    				<artifactId>maven-compiler-plugin</artifactId>
    				<version>3.1</version>
    				<configuration>
    					<encoding>${project.build.sourceEncoding}</encoding>
    					<source>${java.version}</source>
    					<target>${java.version}</target>
    				</configuration>
    			</plugin>
    
    			<plugin>
    				<groupId>org.apache.maven.plugins</groupId>
    				<artifactId>maven-war-plugin</artifactId>
    				<version>2.4</version>
    				<configuration>
    					<archive>
    						<manifestEntries>
    							<Implementation-Version>${project.version}</Implementation-Version>
    							<Implementation-Title>${project.artifactId}</Implementation-Title>
    							<Extension-Name>${project.artifactId}</Extension-Name>
    							<Built-By>danielme.com / Daniel Medina</Built-By>
    						</manifestEntries>
    					</archive>
    				</configuration>
    			</plugin>
    
    			<plugin>
    				<groupId>org.apache.maven.plugins</groupId>
    				<artifactId>maven-eclipse-plugin</artifactId>
    				<version>2.9</version>
    				<configuration>
    					<projectNameTemplate>${project.name}</projectNameTemplate>
    					<wtpmanifest>false</wtpmanifest>
    					<wtpapplicationxml>true</wtpapplicationxml>
    					<wtpversion>2.0</wtpversion>
    				</configuration>
    			</plugin>
    
    		</plugins>
    
    	</build>
    
    	<dependencies>
    
    		<dependency>
    			<groupId>org.apache.struts</groupId>
    			<artifactId>struts2-core</artifactId>
    			<version>${struts2.version}</version>
    		</dependency>
    		
    		<dependency>
    			<groupId>org.apache.struts</groupId>
    			<artifactId>struts2-spring-plugin</artifactId>
    			<version>${struts2.version}</version>
    			<exclusions>
                    <exclusion>
                        <artifactId>spring-beans</artifactId>
                        <groupId>org.springframework</groupId>
                    </exclusion>
                    <exclusion>
                        <artifactId>spring-context</artifactId>
                        <groupId>org.springframework</groupId>
                    </exclusion>
                    <exclusion>
                        <artifactId>spring-core</artifactId>
                        <groupId>org.springframework</groupId>
                    </exclusion>
                    <exclusion>
                        <artifactId>spring-web</artifactId>
                        <groupId>org.springframework</groupId>
                    </exclusion>
                </exclusions>
    		</dependency>
    
    		<dependency>
    			<groupId>org.springframework</groupId>
    			<artifactId>spring-web</artifactId>
    			<version>${spring.version}</version>
    		</dependency>
    
    		<dependency>
    			<groupId>commons-logging</groupId>
    			<artifactId>commons-logging</artifactId>
    			<version>1.1.3</version>
    		</dependency>
    		
    		<dependency>
       		 	<groupId>org.slf4j</groupId>
       		 	<artifactId>slf4j-log4j12</artifactId>
       		 	<version>${slf4jlog4j.version}</version>
       	   </dependency>
    
    
    	</dependencies>
    
    
    </project>
    

    Puesto que el plugin tiene como dependencia una versión antigüa de Spring (concretamente su versión 2.3.15.2 está enlazada con la 3.0.5.RELEASE) tendremos que excluirla para evitar conflictos con la versión que estamos usando que es la última estable de la serie 3 en el momento de redactar este articulo.

  2. Iniciar Struts 2 y el contexto de Spring en el web.xml. Se usará el fichero por defecto (/WEB-INF/applicationContext.xml) por lo que no habrá que realizar ninguna configuración adicional aunque se ha incluido como referencia. Este paso no es propio de la integración Struts2-Spring ya que así se inician Spring en una aplicación web y Struts 2.

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app id="struts_blank" version="2.4"
             xmlns="http://java.sun.com/xml/ns/j2ee" 
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
      <display-name>Struts2 - Spring Integration</display-name>
    
      <filter>
        <filter-name>struts2</filter-name>
        <filter-class>
          org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
        </filter-class>
      </filter>
    
        <context-param>
       	 <param-name>contextConfigLocation</param-name>
       	 <param-value>/WEB-INF/applicationContext.xml</param-value>
        </context-param>
      
      	<listener>
    		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    	</listener>
    	
    	 <listener>
    	    <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
        </listener>
    
      <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/*</url-pattern>
      </filter-mapping>
    
      <welcome-file-list>
        <welcome-file>init</welcome-file>
      </welcome-file-list>
      
      <security-constraint>
    		<web-resource-collection>
    			<web-resource-name>Deny Direct Access</web-resource-name>
    			<description>Deny direct access to JSPs by associating them with denied role</description>
    			<url-pattern>*.jsp</url-pattern>
    		</web-resource-collection>
    		<auth-constraint>
    			<role-name>Denied</role-name>
    		</auth-constraint>
    	</security-constraint>
    	
    	<security-role>
    		<role-name>Denied</role-name>
    	</security-role>
      
      	
    </web-app>
    
  3. Definir los beans de Spring, de momento lo haremos mediante XML. Los Action se definen como cualquier otro bean pero hay que tener mucho cuidado porque por omisión los beans de Spring son singleton, así que deberemos definir su scope como session o request, ya que si son singleton todos los usuarios de la aplicación los compartirán con consecuencias funestas :). Inyectamos también nuestro bean de servicio vía setter.
    <?xml version="1.0" encoding="UTF-8"?>
    
    <beans xmlns="http://www.springframework.org/schema/beans"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    	xmlns:context="http://www.springframework.org/schema/context"
    	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd">
    
    	 <bean id ="springDemoService" class="com.danielme.blog.demo.struts2spring.services.SpringDemoService"/> 
    	
    		<bean id ="springDemoAction" class="com.danielme.blog.demo.struts2spring.actions.SpringDemoAction" scope="session"> 
    			<property name="springDemoService" ref="springDemoService"/> 
    		</bean>
    
    </beans>
    
    
  4. En el struts.xml, definimos los action como siempre pero en el atributo class hay que poner el nombre del bean en Spring
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE struts PUBLIC
            "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
            "http://struts.apache.org/dtds/struts-2.3.dtd">
    <struts>
    	<constant name="struts.devMode" value="true" />
    	<constant name="struts.custom.i18n.resources" value="i18n" />
    	<!-- enable 'action' attribute -->
    	<constant name="struts.mapper.action.prefix.enabled" value="false" />
    	<!-- enable 'method'  attribute-->	
    	<constant name="struts.enable.DynamicMethodInvocation" value="false" />
    
    
    	<package name="default" namespace="/" extends="struts-default">
    
    		<action name="init" class="springDemoAction">
    			<result name="success">/jsp/result.jsp</result>
    		</action>
    		
    	</package>
    
    </struts>
    

Ya tenemos todo lo que necesitamos (los ficheros no relevantes para el artículo se han obviado). Yo lo que suelo hacer es generar un proyecto web para importarlo en Eclipse con el siguiente comando Maven (el pom.xml ya está configurado para ello)

mvn eclipse:eclipse

La estructura completa del proyecto es la siguiente:

eclipse

También se podría utilizar usar el plugin m2eclipse, otro IDE con soporte para Maven como Netbeans, construir directamente con Maven dede la línea de comandos… En cualquier caso si se despliega la aplicación y vamos actualizando el navegador veremos como se actualiza la hora y que la instancia del objeto Action es siempre la misma por ser un bean de sesión. Podemos comprobarlo fácilmente abriendo otro navegador a la vez y viendo que el identificador del objeto Action es distinto. Obsérvese que los textos y formato de fecha están localizados, en breve se tratará esta funcionalidad en los tips Struts 2.

Struts 2 - Spring

También se puede definir el bean del Action como “request” en cuyo caso Spring instancia un nuevo objeto cada vez que se solicite el Action (comportamiento natural del propio Struts 2).

Spring con anotaciones

Vamos a definir los beans en Spring mediante anotaciones tal y como suele hacerse hoy en día, de hecho es como se presenta el proyecto final. Nota: para hacer la configuración de Struts mediante anotaciones ver el tip #09 .

En el applicationContext.xml, «activamos» las anotaciones indicando a Spring la ruta de paquetes a partir de la que debe buscar recursivamente clases anotadas. Las definiciones de los beans ya no hacen falta se han comentado para futuras referencias.

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd">

	<context:component-scan base-package="com.danielme.blog.demo" />

	<!-- <bean id ="springDemoService" class="com.danielme.blog.demo.struts2spring.services.SpringDemoService"/> 
	
		<bean id ="springDemoAction" class="com.danielme.blog.demo.struts2spring.actions.SpringDemoAction" scope="session"> 
			<property name="springDemoService" ref="springDemoService"/> 
		</bean> -->

</beans>

El bean de servicio se anota con el estreotipo @Service. Puesto que es un Singleton no es necesaria ninguna anotación adicional.

package com.danielme.blog.demo.struts2spring.services;

import java.util.Date;

import org.springframework.stereotype.Service;

@Service
public class SpringDemoService 
{
	public Date getDateNow() 
	{
		return new Date();
	}
}

Para el Action usaremos el estereotipo @Component. Asimismo, tendremos que usar la anotación @Scope ya que no queremos que el bean sea un singleton. Por defecto, el identificador del bean en el contenedor de Spring es el nombre de la clase con la primera letra en minúscula y es ese identificador el que usaremos en el struts.xml. Si fuera necesario, se puede definir el id del bean tal y como se ha hecho en el ejemplo.

Con respecto a la inyección del servicio, simplemente usaremos la anotación @Autowired y Spring la realizará automáticamente buscando un bean de esa clase que sabemos que existe y es único. La anotación podemos aplicarla directamente al atributo, al constructor o a su setter. Lo más sencillo y legible en nuestro ejemplo particular es simplemente anotar el atributo, en cuyo caso no necesario el setter si sólo lo creamos para realizar la inyección.

package com.danielme.blog.demo.struts2spring.actions;

import java.text.SimpleDateFormat;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import com.danielme.blog.demo.struts2spring.services.SpringDemoService;
import com.opensymphony.xwork2.ActionSupport;

/**
 * 
 * @author danielme.com
 *
 */
@Component("springDemoAction")
@Scope(value="session")
public class SpringDemoAction extends ActionSupport
{			
	private static final long serialVersionUID = 1L;
	
	@Autowired
	private SpringDemoService springDemoService;
		
	public String execute() 
	{
        return SUCCESS;
    }
	
	public String getDate()
	{
		SimpleDateFormat sdf = new SimpleDateFormat(getText("pattern"));
		return sdf.format(springDemoService.getDateNow());				
	}
	
	public String getId()
	{
		return this.toString();
	}
	
//	public void setSpringDemoService(SpringDemoService springDemoService) {
//		this.springDemoService = springDemoService;
//	}
    

}

Observaciones finales

Hemos visto que como suele suceder con los plugins de Struts 2 la integración de Struts 2 y Spring se reduce simplemente a incluir el plugin en el classpath, el resto consiste en utilizar los conceptos básicos de Spring. No obstante, he querido proporcionar un ejemplo completo que pueda servir de base para aplicaciones “reales” y no se quede en una mera prueba de concepto.

Una vez que tenemos integrado Struts2 y Spring, el siguiente paso es aplicar lo visto en el artículo Persistencia en BD con Spring: Integrando JPA, c3p0, Hibernate y EHCache» y ya tendríamos un “stack” clásico y relativamente estandarizado de desarrollo web Java (y que es mi favorito), basado en Spring y no en JEE y, por lo tanto, totalmente independiente de un servidor de aplicacicones (JBoss AS, GlassFish, Geronimo…) pero desplegable en ellos.

java web stack

Código de ejemplo

Se encuentra disponible en GitHub. Para más información sobre cómo utilizar GitHub, consultar este artículo.

2 comentarios sobre “Integrando Struts 2 y Spring IoC

Deja una respuesta

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Salir /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Salir /  Cambiar )

Conectando a %s

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.