Struts Best Practices / Mejores Prácticas de Struts

Tabla de contenidos

Objetivo

Destacar las Best Practices y los Common Practices, facilitando las ventajas de utilizar las mismas y los motivos de su uso.

¿Qué es Struts?

Es un framework que se basa en el patrón MVC brindado por The Apache Software Foundation. El framework es opensource y se utiliza para construir aplicaciones web en Java.

¿Cómo funciona?

El browser genera una solicitud, que es atendida por el Controller. El mismo se encarga de analizarla, seguir la configuración que se obtiene de un XML (generalmente struts-config.xml) y llamar al Action correspondiente, pasándole los parámetros enviados. A continuación, el Action instanciará y/o utilizará los objetos de negocio para concretar la tarea. Según el resultado que retorne el Action, el Controller derivará la generación de interfaz a una o más JSPs, las cuales podrán consultar los objetos del Model, a fines de realizar su tarea. Cabe aclarar que desde el Controller se puede reenviar a otro Action.

¿Para qué se utiliza?

El objetivo es simplificar el uso del patrón MVC. El controlador ya se encuentra implementado por Struts y el workflow de la aplicación se puede configurar desde un archivo XML. Con el uso, se pueden generar ventajas de mantenimiento y de perfomance con respecto a otras aplicaciones que no lo utilicen. Separa claramente el desarrollo de interfaz del workflow y lógica de negocio, permitiendo desarrollar ambas en paralelo o con personal especializado.

Componentes Struts

Action

El objetivo de una clase Action es procesar una solicitud mediante un método, y devolver un objeto ActionForward? , que identifica dónde se debería reenviar el control (según indique el Controller). Los Action son los únicos que se pueden comunicar con otros sistemas externos a la aplicación, en los cuales se maneje la lógica de negocio.

Form

Los form son una clase java bean. Tienen getters y setters; por cada atributo de entrada de los formularios de la vista (form estáticos), la clase hereda de ActionForm?. Otra posibilidad es utilizar form dinámicos, los cuales tienen que heredar de DynaActionForm. En estas clases se puede implementar un método validate, el cual, como bien dice su nombre, valida los datos de entrada. Al haber un error de validación, se utiliza el manejo de errores y el Controller reenvía la revisión a la correspondiente página, según la configuración del struts-config.xml.

XML

Los XML se utilizan para configurar. El Controller, tiles-defs, etc. son algunos de los casos en los cuales se les da uso a los XML.

Patrón MVC

Model

El Model comprende todos los Objetos de Negocio donde se implementa la lógica de negocio (él how it's done) y donde deben soportarse todos los requisitos funcionales del Sistema, sin mezclarlo con partes correspondientes al workflow (el what to do) que corresponden al Controller. La clase utilizada es el Action, pudiendo ésta invocar a otras. Usualmente, el modelo comprende accesos a Base de Datos o a otros sistemas que funcionan independientemente de la aplicación web.

View

La View comprende toda la vista (JSP, velocity, los servlets involucrados en la generación de la interfaz de usuario o con otros Sistemas, etc.). Struts no provee una solución, sino que está libre para que el programador utilice la vista más adecuada, según la necesidad que requiera el sistema.

Controller

El Controller comprende la funcionalidad involucrada, desde que un usuario genera un estímulo (click en un link, envío de un formulario, etc.), hasta que se genera la interfaz de respuesta. Durante este proceso, llamará a los objetos de negocio del Model, para que resuelvan funcionalidad propia de la lógica de negocio y, según el resultado de la misma, ejecutará la llamada a la página correspondiente. Ésta debe generar la interfaz resultante. Struts provee su propio Controller, el cual se configura con un xml y se puede integrar con otras tecnologías para proveer el model y el view.

Best Practices

El motivo de usar las Best Practices se debe a que mejoran el desarrollo, ya que ayudan a evitar la generación de código redundante y errores habituales. Esto deriva en un código fácil de comprender, reutilizar y mantener.

Usar una BaseAction para manejar los pedidos

Para cada Action se debe heredar de org.apache.struts.action.Action. Se propone realizar una clase que herede de Action con métodos abstract, los cuales necesiten implementarse en las clases hijas. Struts recomienda los siguientes pasos:

  1. Crear una clase BaseAction, que hereda de org.apache.struts.action.Action
  2. Todo nuestros Action heredan de BaseAction
  3. Crear un método abstract en BaseAction , el cual tenga como parámetros ActionMapping, ActionForm, HttpServletRequest, HttpServletResponse
  4. Agregar en BaseAction todos los métodos genéricos que utilicen todos los Action.
  5. En BaseAction declarar un método final, el cual debe llamar al método implementado en el paso ‘3’, antes de procesar el pedido.
  6. Cada Action debe extender de BaseAction e implementar el método abstract, personalizando el método.

Ventajas: Brinda más control a la aplicación sobre las mismas tareas, realizando el comportamiento en una sola clase; de este modo, evita métodos redundantes que se encuentren en distintas clases.

No utilizar Scriptles en las páginas

Esta buena práctica no sólo es de struts, sino que debe utilizarse en toda aplicación web. El objetivo es evitar el uso de Scriplets (los trozos de código Java entre "<%" y "%>"), lo cual genera ventajas de mantenimiento y de perfomance. La idea es utilizar tags predefinidos por Struts. Para evitar el uso de Scriptles se pueden generar tags propios, los cuales son fáciles de realizar. A veces resulta imposible evitarlos; en los JSP's, por ejemplo: a un tag de struts no le podemos "insertar" otro tag de Struts:

<html:select property="<bean:write name="unBeanString"/>"  >
..

</html:select>

Esto no funciona, entonces en el 99% de las veces lo solucionamos con un <%= %>, y caemos en el uso de Scriptlets. Pero existe una solución, el Expression Language de Struts (Struts-EL). El ejemplo se puede resolver de esta manera:

<html-el:select property="${unBeanString}" >
..
</html-el:select>

Manejo Efectivo de excepciones

Convencionalmente, cuando ocurre una excepción en un Action, esta es la primera en registrarse. Luego, la clase crea un ActionError y guarda el error. A continuación, este Action forwardea el control al apropiado ActionForward.

try {
        //Codigo en la clase Action
       }
    catch (ApplicationException e) {
        //log excepcion
        ActionErrors? actionErrors = new ActionErrors();
        ActionError? actionError = new ActionError(e.getErrorCode());
        actionErrors.add(“FATAL”, actionError);
        saveErrors(request, actionErrors);

}

Se recomienda seguir estos pasos:

  1. Crear BaseAction que herede de org.apache.struts.action.Action
  2. Que todos los Action hereden de la clase creada en ‘1’
  3. En la clase BaseAction declarar una variable:

ActionErrors actionErrors = new ActionErrors()

  1. Crear un método abstract:

ActionForward *performTask*( ActionMapping mapping, ActionForm? form, HttpServletRequest? request, HttpServletResponse? response, ActionErrors? actionErrors) throws IOException, ServletException?.

  1. Declarar un método como final, en el cual se invoca al método implementado en ‘4’ (performTask() ) antes de procesar el pedido.
try     {
   //Codigo de la Clase Action
   }
   catch(ApplicationException appException) {
      //Log excepcion
      //Suma el error al ActionErrors?
      actionErrors.add(“FATAL”,
      new ActionError(appException.getErrorCode()));

}

En BaseAction, después de invocar el método performTask, se debe guardar el ActionErrors usando saveErrors(request,errors).

Ventaja: No se repite código en cada Action que maneja ActionErrors

Manejo de Errores

Es una manera efectiva de redirigir los errores, ya que se configura a qué página se debe direccional, según el error que ocurra. Ejemplo:

  1. En struts-config.xml
<global-exceptions>
    <exception type=”tuPaquete.DAOException” key=”error.DAO.conexion”  path=”/errorDAO.jsp”
handler="org.apache.struts.action.ExceptionHandler" />

</global-exceptions>

  1. Agregar en el AplicationResources.properties

error.DAO.conexion=Error al conectarse a la base de datos.

  1. Crear una página de error que tenga en el body "< html:errors/>"
  2. Se puede cambiar el handler en struts-config.xml handler=”paquete.DAOExceptionHandler”, luego crear la clase DAOExceptionHandler que extienda de org.apache.struts.action.ExceptionHandler y por último agregar código para registrar el error.

Acceder los JSP solo desde Struts

Primero, el flujo para una página debe ir a un Action, y, de allí, debe mapearse a una página (a través del struts-config.xml). Un Action puede manejar varias páginas; a su vez, una página puede ser manejada por varios Action (ejemplo una página de error). El tema de seguridad se configura en el web.xml.

<!Esta constrain sirve para negar todos los accesos>
    <security-constraint>
        <web-resource-collection>
            <web-resource-name>Default</web-resource-name>
            <url-pattern>/</url-pattern>
        </web-resource-collection>
        <auth-constraint/>
    </security-constraint>

        <!Ahora agreglo los accesos permitidos>
    <security-constraint>

        <web-resource-collection>
            <web-resource-name>acciones</web-resource-name>
            <url-pattern>*.do</url-pattern>
        </web-resource-collection>
        <web-resource-collection>
            <web-resource-name>recursos</web-resource-name>
            <url-pattern>/jsp/resources/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>*</role-name>
        </auth-constraint>

</security-constraint>

Se puede acceder a un JSP por medio de un Action directamente en el struts-config.xml, utilizando el ForwardAction? que brinda Struts. Un ejemplo de un ActionForward sería:

<action path="/home"
            parameter="/home.jsp"
            type="org.apache.struts.actions.ForwardAction"
            scope="request"
            validate="false">
</action>

Ventajas:

  • Controla fácilmente los JSPs que no se utilizan.
  • Brinda seguridad a la aplicación, ya que siempre se accede a través de un Action.

No utilizar los Objetos de Negocio en el View

Se utiliza el patrón “ValueObject” (significa crear un bean simple con todos los datos, pero sin que tenga lógica, solo getters y setters) genera una clase bean. El objetivo es separar los objetos del view de los objetos de negocio, para tener diferenciado uno del otro. Ventaja:

  • Tener separadas las capas de vista con la lógica de negocio. Si en algún momento se cambia algún atributo del objeto, no se refleja en la otra capa.

No enviar los ActionForms? a las capas de negocio

Para los casos que fueran necesarios, se realiza un clase que tenga la misma información que los Form. Utilizar DTOs para estos casos. La idea es tener separado los objetos de negocio con la capa de vista, para que, en los casos que se necesite cambiar alguna propiedad del objeto, no impacte en la otra capa.

No usar variables de instancia o estáticas en los Action

Las variables de instancia o estáticas generan problemas de sincronización; además, en los Action no debe haber nada que represente estados. Hay excepciones para las variables estáticas: el típico logger y las constantes.

Realizar Test

Hay varias herramientas de test y de diferentes tipos, como StrutsTestCase (hereda de JUnit), JMeter (aplicación Java), etc. Para realizar test en StrutsTestCase se necesita programar, debido a que se realiza una clase que hereda MockStrutsTestCase y se pueden testear tanto la validación, el traslado de datos, el forward de páginas. JMeter es una aplicación realizada en java, que sirve para probar el rendimiento y funcionalidades de la aplicación web.

No realizar la lógica del negocio en los Action

Comúnmente, se realiza la lógica de negocio en los Action, pero lo ideal es que en el Action se realicen llamadas a servicios, los cuales deben realizar la lógica. La ventaja es que en los casos en que se modifique una funcionalidad de la aplicación, sea o no de lógica de negocio, no se tenga que alterar el Action, sino solamente el servicio. Además, se obtiene un sistema más modular y legible y evitamos tener un Action que realice TODO.

Eliminar los ActionForm? de una sesión

Algunas veces es necesario cargar un Form en sesión, pero muchas veces lo dejamos en toda sesión olvidándonos de eliminarlo con un simple

session.removeAttribute(“< nombre>”)
La ventaja es el desperdicio de memoria en algo que no se está utilizando.

Chequear las precondiciones de los Form

Al ejecutar un Action se pueden obtener valores de un formulario, al que se le debe validar la entrada, para que en el Action no se lance una excepción, por falta de algún dato importante o un mal formato del mismo.

Common Practices

Utilizar Internacionalización

El objetivo es tener, en un archivo, los textos utilizados por las páginas del view, ya sea mensajes de error, textos y títulos utilizados en la aplicación. La ventaja es que la refactorización es mucho más sencilla, si se quiere realizar un cambio.

  1. Crear un archivo de texto (ej: aplicacion.properties) en el directorio, donde se encuentren las clases de la aplicación, y se contengan las claves y valores con el formato clave.subclave = texto , de los textos que pertenezcan al idioma principal. Ejemplo:
application.title= Aplicación Super Poderosa

index.encabezado = Este es el Encabezado

  1. Para cada idioma alternativo se creará un archivo nuevo que se llame igual, pero que termine en _xx.properties, siendo xx el código ISO de idioma.
  2. En struts-config.xml se debe configurar el Tag /servlet/init-param/param-name application y ponerle como param-value la localización del archivo con el idioma principal. Ejemplo:
<servlet>
    <servlet-name>action</servlet-name>
    <init-param>
      <param-name>application</param-name>
      <param-value>com.empresa.aplicacion.MiAplicacion</param-value>

</init-param>

  1. En web.xml deberemos incluir:
/WEB-INF/struts-bean.tld

/WEB-INF/struts-bean.tld

  1. En los JSPs donde utilicemos la internacionalización deberemos incluir, al comienzo:
    <web-app>
    
<taglib> <taglib-uri>/WEB-INF/struts-bean.tld</taglib-uri> <taglib-location>/WEB-INF/struts-bean.tld</taglib-location> </taglib>

</web-app> para declarar que utilizaremos la TagLibrary struts-bean con el prefijo bean y definida en

<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>

  1. Finalmente, utilizaremos el Tag

<bean:message key="clave.subclave"/>
donde clave y subclave corresponden al texto por el que se reemplazará, según el idioma del usuario. Ejemplo
<TITLE><bean:message key="application.title"/></TITLE>
Nota: Por defecto, Struts asigna a cada usuario el idioma principal de la aplicación. Si se utiliza el tag < html:html locale="true"> (reemplaza a “< html>” y debe cerrarse con “< /html:html>”) entonces se utilizará el primero de los idiomas soportados por la aplicación que se encuentre en el header “Accept-Language” enviado por su navegador. Pero si se desea proponerle al usuario una lista de idiomas, entonces éste podrá cambiarse mediante session.setAttribute( Action.LOCALE_KEY, new java.util.Locale(country, language)) , donde country y language serán el string del país e idioma a establecer.

Definir el mensaje de error

Una ventaja es utilizar niveles de errores en los Action, para determinar el grado en el cual afecta a la aplicación. Fatal, error, warning, info, etc. A su vez, se pueden utilizar mensajes, los cuales se definen en el .properties

ActionError error = new ActionError();
error.add(“info”,”error.faltaFecha.obligatorio”);

Uso de DispatchAction

En algunos casos es necesario que, en un mismo Action, se realicen diferentes acciones, entonces se debe preguntar por el valor de la variable y, así, se logra determinar qué tipo de acción realizar.

String accion = myForm.getAccion();
if ("create".equals(dispatch)) { ...
if ("save".equals(dispatch)) { ...
La mejor manera de realizar esto sería utilizando DispatchAction:

  1. El JSP tendría lo siguiente
<html:hidden property="accion" value="error"/>

function set(target) {
     document.forms0.accion.value=target;
}

<html:submit >Guardar</html:submit>
<html:submit >Guardar Como </html:submit>

<html:submit >BORRAR</html:submit>

  1. El Action tiene que heredar de DispatchAction?
  2. En el Action se implementan los métodos de las acciones requeridas.
public ActionForward create( ActionMapping mapping,
    ActionForm? form, HttpServletRequest? request,
    HttpServletResponse? response) throws IOException, ServletException? { ... }

public ActionForward save( ActionMapping mapping,
    ActionForm? form, HttpServletRequest? request,
    HttpServletResponse? response)

throws IOException, ServletException? { ... }

  1. En struts-config.xml, el mapeo tiene que tener como parámetro el nombre de la variable, el cual va a tener los distintos nombre de las acciones
<action
path="/login"
type="src.prueba.login"
name="loginForm"
scope="request"
validate="true"

parameter="accion"/>

Son varias las ventajas: Agrupar los action que están relacionados, generar un código más modular y que no haya código redundante. Cuando, por ejemplo, hay un flujo que se repite (ej: abrir, confirmar guardar), se crea un Action base del cual heredar cada vez que se presente la misma situación.

Uso del validator

Permite especificar las validaciones de las propiedades de un ActionForm (normal o dinámico) en un fichero de configuración (se puede validar, si es null o una entrada, si es numérico, entre que valores enteros o float, si es una fecha, si es un mail, etc.). En general, evita la necesidad de implementar el método validate o, al menos, lo simplifica. La validación se puede hacer tanto del lado del cliente como del lado del servidor. Para este último, en el struts-config.xml habria que setear el atributo validate=true Para el lado del cliente, Struts puede generar el javascript. Hay que poner un tag en el jsp < html:javascript> e invocar a la función en el jsp antes del submit

function submitForm(form) {
       if (validateForm(form)) {
             form.submit.value=" ..";
             return true;
       }
       else return false;

};

En el submit quedaría:
<html:form >

La configuración sería de la siguiente manera:

<plug-in className="org.apache.struts.validator.ValidatorPlugIn">
<set-property property="pathnames"
value="/WEB-INF/Struts/validator-rules.xml,
  /WEB-INF/Struts/validation.xml"/>
</plug-in>
La propiedad *pathnames *especifica dos ficheros: Fichero de especificación de validadores (comúnmente llamado *validator-rules.xml*) Fichero de validación de las propiedades de los ActionForms? (*validation.xml*)

Este último es específico a la aplicación. Por cada ActionForm se especifican los validadores que se aplican a cada uno de sus propiedades.

Fuentes y Referencias

http://wiki.apache.org/struts/StrutsCatalog?highlight=%28%28StrutsCatalogBaseAction%29%29 (external link) http://www-106.ibm.com/developerworks/web/library/wa-struts/ (external link) http://www.jaxmag.com/itr/online_artikel/psecom,id,648,nodeid,147.html (external link) http://struts.apache.org/userGuide/index.html (external link) http://strutstestcase.sourceforge.net/ (external link)

Conclusiones

El framework de Struts es altamente recomendable para el uso de aplicaciones web, porque es un proyecto maduro muy difundido entre la comunidad de programadores. Esto lleva a que prácticamente todos los problemas que pueden aparecer por su utilización, ya estén resueltos. Además, hay muchos "plugins" o similares que se basan en Struts.

Sin embargo, son muy pocas las personas que llegan a utilizar todas las buenas prácticas, ya sea por falta de conocimiento de las mismas o porque se cree que para el proyecto no es necesario.

En general, la utilización de buenas prácticas ayuda a poder generar un código legible, de buena performance, no redundante, de fácil modificación y reutilizable para proyectos futuros.

Por último, cabe aclarar que es muy útil conocer el por qué de cada una de las prácticas antes de utilizarlas, para poder sacarle provecho, ya que estas están diseñadas para evitar todo tipo de errores.