A cada uno le pica la paja que se le mete por el culo, no el resto del pajar.
Gonzo, El Sentido De La Vida

Archivos en la categoría Spring Framework

Integrar Struts con Spring - holamundoStruts138Spring

Integrar Struts con Spring - holamundoStruts138SpringVamos al turrón, vamos a implementar lo que hemos visto a nivel teórico.

Creamos un nuevo proyecto web dinámico, holamundoStruts138Spring. Copiamos todo lo que tenemos en holamundoStruts138 sobre este nuevo proyecto para no partir de cero.

Básicamente sobreescribermos el controlador de Struts y utilizaremos la configuración de Spring para inyectar las dependencias a nuestros objetos.

LyckaRequestProcessor

package es.lycka.holamundoStruts138.action;

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.ActionMapping;

import org.apache.struts.action.ActionServlet;

import org.apache.struts.action.RequestProcessor;

import org.apache.struts.config.ModuleConfig;

import org.apache.struts.util.RequestUtils;

import org.springframework.web.context.WebApplicationContext;

import org.springframework.web.struts.DelegatingActionUtils;

public class LyckaRequestProcessor extends RequestProcessor{

      private WebApplicationContext webApplicationContext;

      public void init(ActionServlet actionServlet, ModuleConfig moduleConfig) throws ServletException {

            super.init(actionServlet, moduleConfig);

            if (actionServlet != null) {

                  this.webApplicationContext = initWebApplicationContext(actionServlet, moduleConfig);

            }

      }

      protected WebApplicationContext initWebApplicationContext(

                  ActionServlet actionServlet, ModuleConfig moduleConfig) throws IllegalStateException {

            return DelegatingActionUtils.findRequiredWebApplicationContext(actionServlet, moduleConfig);

      }

      protected final WebApplicationContext getWebApplicationContext() {

            return webApplicationContext;

      }

      /**

       * Método que sobreescribimos para utilizar beans de Spring.

       */

      protected Action processActionCreate(HttpServletRequest request,

                  HttpServletResponse response, ActionMapping mapping)

                  throws IOException {

            // Acquire the Action instance we will be using (if there is one)

            String className = mapping.getType();

            if (log.isDebugEnabled()) {

                  log.debug(" Looking for Action instance for class " + className);

            }

            // If there were a mapping property indicating whether

            // an Action were a singleton or not ([true]),

            // could we just instantiate and return a new instance here?

            Action instance;

            //Acceso sincronizado al HasMaps de actions

            synchronized (actions) {

                  // Return any existing Action instance of this class

                  instance = (Action) actions.get(className);

                  if (instance != null) {

                        if (log.isTraceEnabled()) {

                             log.trace("  Returning existing Action instance");

                        }

                        return (instance);

                  }

                  // Create and return a new Action instance

                  if (log.isTraceEnabled()) {

                        log.trace("  Creating new Action instance");

                  }

                  try {

                        //String beanName = determineActionBeanName(mapping);

                        String beanName = mapping.getType();

                        if (getWebApplicationContext().containsBean(beanName))

                             instance = (Action)getWebApplicationContext().getBean(beanName, Action.class);                           

                        else

                             instance = (Action) RequestUtils.applicationInstance(className);

                        // Maybe we should propagate this exception

                        // instead of returning null.

                  } catch (Exception e) {

                        log.error(getInternal().getMessage("actionCreate",

                                   mapping.getPath()), e);

                        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,

                                   getInternal().getMessage("actionCreate", mapping.getPath()));

                        return (null);

                  }

                  actions.put(className, instance);

            }

            if (instance.getServlet() == null) {

                  instance.setServlet(this.servlet);

            }

            return (instance);

      }

}

Para que funcione correctamente además de las librerías de Struts que ya tenemos importadas, debemos utilizar spring.jar y spring-struts.jar.

Bases, Interfaces e Implementaciones

Ya aprovechamos que vamos a utilizar Spring para desdoblar nuestras clases en una clase Base por cada capa, y para cada clase utilizaremos un interfaz y una implementación.

Para no poner todas las implementaciones, que ya bastante larga va a quedar la entrada, sólo transcribo la implementación de IdentificarBOImpl. Sólo sería necesario el set del bean identificarDAO, pero por comodidad dejo ambas. Las demás implementaciones serían similares.

package es.lycka.holamundoStruts138Spring.negocio.impl;

import es.lycka.holamundoStruts138Spring.model.Usuario;

import es.lycka.holamundoStruts138Spring.negocio.IdentificarBO;

import es.lycka.holamundoStruts138Spring.persistencia.IdentificarDAO;

public class IdentificarBOImpl extends BaseBOImpl implements IdentificarBO  {

      private IdentificarDAO identificarDAO;

      public boolean esUsuarioAutorizado (Usuario usuario) throws Exception {

            boolean esAutorizado               = false;

            try {

                  if (identificarDAO.consultarUsuario (usuario) != null ) {

                        esAutorizado = true;

                  }

            } catch (Exception e) {

                  throw e;

            }

            return esAutorizado;

      }

      public IdentificarDAO getIdentificarDAO() {

            return identificarDAO;

      }

      public void setIdentificarDAO(IdentificarDAO identificarDAO) {

            this.identificarDAO = identificarDAO;

      }

}

web.xml

Vale, ahora debemos modificar el web.xml para utilizar también Spring.

<context-param>

      <param-name>contextConfigLocation</param-name>

      <param-value>

            /WEB-INF/spring/applicationContext-*.xml

      </param-value>

</context-param>

<listener>

      <listener-class>

            org.springframework.web.context.ContextLoaderListener

      </listener-class>

</listener> 

Así, podemos crear varios archivos XML para configurar nuestra aplicación:

  • applicationContext-struts.xml, que contendrá los beans que representan a nuestras Acciones de Struts.
  • applicationContext-negocio.xml, que contendrá los beans que representan a nuestros objetos de la capa de negocio.
  • applicationContext-persistencia.xml, que contendrá los beans que representan a nuestros objetos de la capa de persistencia. Meteremos iBATIS en otro ejemplo.

applicationContext-struts.xml

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"

      "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>

      <bean name="parentAction" class="es.lycka.holamundoStruts138Spring.control.action.AppBaseAction" abstract="true" />

      <bean name="es.lycka.holamundoStruts138Spring.control.action.IdentificarAction" class="es.lycka.holamundoStruts138Spring.control.action.IdentificarAction" singleton="false" parent="parentAction">

            <property name="identificarBO"><ref bean="identificarBO" /></property>

      </bean>

</beans>

applicationContext-negocio.xml

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"

      "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>

      <bean name="baseBO" class="es.lycka.holamundoStruts138Spring.negocio.impl.BaseBOImpl" abstract="true"/>

      <bean id="identificarBO" class="es.lycka.holamundoStruts138Spring.negocio.impl.IdentificarBOImpl"  parent="baseBO">

            <property name="identificarDAO" ref="identificarDAO" />

      </bean>

</beans>

applicationContext-persistencia.xml

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"

      "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>

      <bean id="dao" class="es.lycka.holamundoStruts138Spring.persistencia.impl.BaseDAOImpl" abstract="true" />

      <bean id="identificarDAO" class="es.lycka.holamundoStruts138Spring.persistencia.impl.IdentificarDAOImpl" parent="dao"/>

</beans>

struts-config.xml

Debemos declarar como controlador nuestra nueva clase es.lycka.holamundoStruts138Spring.action.LyckaRequestProcessor

<controller>

      <set-property property="processorClass"

      value="es.lycka.holamundoStruts138Spring.action.LyckaRequestProcessor" />

</controller>

Probamos y … bene !

Integrar Struts con Spring - Teoría

Bueno, ya tenemos un proyecto con Spring - MVC (holamundo) y otro con Struts (holamundoStruts138). Cuál utilizaremos para desarrollar ? Eso lo dejo para otra discusión.

Aquí quiero señalar que existe otra solución, y es integrar Struts con Spring, es decir, utilizar Struts aprovechando sus puntos fuertes como MVC (madurez, documentación, podemos extender el framework con Struts-Validator y Tiles…) y Spring para sus puntos fuertes (como AOP, IoC, DI…).

El framework de Struts realiza una serie de operaciones transparentes para el desarrollador para facilitarle la vida. Entre ellas mantiene un HashMap que contiene referencias a objetos Action ya instanciados. Ante cada petición mira primero este HashMap para ver si contiene la clase Action solicitada. Si es así recupera el objeto a partir de la referencia almacenada en el HashMap; y si no instancia e inicializa la clase Action y guarda su referencia en el HashMap. Al manejar una única instancia de cada Action se ahorra bastante tiempo y el rendimiento de la aplicación mejora notablemente.

Este mecanismo podría ser más eficiente, ya que cada Action invoca a uno o varios objetos de negocio, y con Struts se deben instanciar e inicializar en cada llamada. La integración con Spring perfecciona este mecanismo aprovechando IoC y DI, ya que estos objetos de negocio (utilizados como singletons) ya están instanciados e inicializados en el contendor de Spring.

Si aplicamos IoC y DI también a los Action de Struts conseguimos que las instancias de los Action manejen ya tengan instanciados e inicializados los objetos de negocio, mejorando considerablemente el rendimiento de nuestra aplicación: al levantar la aplicación será un poco lenta pero después irá como un tiro.

Pues manos a la obra. Debemos declarar un bean en Spring que represente nuestro Action para que sea Spring el que le instancie y le inyecte sus dependencias (los objetos de negocio).

Donde gestiona Struts la creación de sus clases Action ? En el método processActionCreate de su controlador, RequestProcessor. Vale, pues sobre escribamos esta clase con nuestro propio controlador, AppRequestProcessor, para que pida la instancia del bean a Spring (la rama true del if) en lugar de crearla Struts (la rama del else). Lo que está en negrita es la clave, sólo un if.

Si queremos utilizar Tiles debemos extender de TilesRequestProcessor.

public class AppRequestProcessor extends RequestProcessor {

. . .

    protected Action processActionCreate(HttpServletRequest request,

            HttpServletResponse response, ActionMapping mapping)

            throws IOException {

            // Acquire the Action instance we will be using (if there is one)

            String className = mapping.getType();

            if (log.isDebugEnabled()) {

                log.debug(" Looking for Action instance for class " + className);

            }

            // If there were a mapping property indicating whether

            // an Action were a singleton or not ([true]),

            // could we just instantiate and return a new instance here?

            Action instance;

            //Acceso sincronizado al HasMaps de actions

            synchronized (actions) {

                // Return any existing Action instance of this class

                instance = (Action) actions.get(className);

                if (instance != null) {

                    if (log.isTraceEnabled()) {

                        log.trace("  Returning existing Action instance");

                    }

                    return (instance);

                }

                // Create and return a new Action instance

                if (log.isTraceEnabled()) {

                    log.trace("  Creating new Action instance");

                }

                try {

                  //String beanName = determineActionBeanName(mapping);

                  String beanName = mapping.getType();

                  if (getWebApplicationContext().containsBean(beanName))

                        instance = (Action)getWebApplicationContext().getBean(beanName, Action.class);   

                  else

                        instance = (Action) RequestUtils.applicationInstance(className);

                    // Maybe we should propagate this exception

                    // instead of returning null.

                } catch (Exception e) {

                    log.error(getInternal().getMessage("actionCreate",

                            mapping.getPath()), e);

                    response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,

                        getInternal().getMessage("actionCreate", mapping.getPath()));

                    return (null);

                }

                actions.put(className, instance);

            }

            if (instance.getServlet() == null) {

                instance.setServlet(this.servlet);

            }

            return (instance);

        }

. . .

}

Es decir, buscamos si el nombre del bean (beanName) existe en el contexto de Spring, y si es así lo obtenemos de ahí. El nombre lo podemos obtener de la ruta (path) mediante determineActionBeanName(mapping), el método comentado. O bien lo podemos obetener del "type", es decir la clase.

En el contexto de Spring debemos declarar nuestros actions. En el primer caso el nombre del bean será la ruta introducida en el navegador. Ojo, que todos los beans del mismo action deben declarar los mismos BO's porque la instancia que se almacenará será la que se instancia primero, es decir por la primera ruta a la que se acceda: