miércoles, 23 de diciembre de 2015

Spring MVC - Parte 1: Configuración

En tutoriales anteriores hemos visto cómo trabajar con los módulos core de Spring, los cuales proporcionan el motor de inyección de dependencias y el contendor de beans. También tuvimos una serie completa de cómo trabajar con un framework MVC para aplicaciones web, Struts 2.

Spring tiene sus propios módulos que conforman un framework para el desarrollo de aplicaciones Java basadas en web, Este framework sigue el patrón de diseño MVC. A este conjunto de módulos se le conoce con el nombre de Spring MVC.

En esta serie de tutoriales aprenderemos a usar Spring MVC. En este primer tutorial aprenderemos cómo configurar el framework de dos formas, una a través de archivos de configuración en XML y otra a través de código Java y haremos el respectivo Hola Mundo.

Al igual que Struts 2, Spring MVC es un framework de capa de presentación, basado o dirigido por peticiones. Esto quiere decir que los programadores estructuramos el diseño de la aplicación web en términos de métodos que manejan peticiones HTTP individuales.

El framework define una serie de interfaces que siguen el patrón de diseño Strategy para todas las responsabilidades que deben ser manejadas por el framework. El objetivo de cada interface es ser simple y clara, para que sea fácil para los usuarios de Spring MVC (o sea, nosotros) crear nuestras propias implementaciones.

La pieza central de Spring MVC es un componente llamado el "DispatcherServlet", el cual sigue el patrón de diseño front controller, este envía las peticiones a los componentes designados para manejarlas.

Las interfaces más importantes definidas por Spring MVC son:
  • Controller: Se encuentra entre el Modelo y la Vista, maneja las peticiones que entran y redirige a las respuestas apropiadas. Estos serán los componentes que estaremos programando para manejar las peticiones.
  • HandlerAdapter: Es quien realiza en realidad la invocación del manejador de la petición (el DispatcherServlet delega esa tarea a este componente), esto incluye inyectar los parámetros adecuados usando reflexión.
  • HandlerInterceptor: Intercepta las peticiones de entrada. Es comparable, pero no igual, a los filtros en el API de Servlets.
  • HandlerMapping: Selecciona objetos que manejan las peticiones de entrada basado en cualquier atributo o condición interna o externa a esos atributos.
  • LocaleResolver: Resuelve y opcionalmente guarda el Locale de un usuario individual.
  • MultipartResolver: Facilita trabajar con la carga de archivos, envolviendo las peticiones de entrada.
  • View: Responsable de regresar una respuesta al cliente. Algunas peticiones pueden ir directo a la vista sin pasar por el modelo.
  • ViewResolver: Selecciona una vista basado en un nombre lógico para la vista.

Cada una de las interfaces anteriores tiene una responsabilidad importante dentro del framework, y la abstracción ofrecida por estas interfaces permite tener variaciones en sus implementaciones. Obviamente Spring MVC contiene implementaciones de casi todas estas interfaces (digo casi todas porque nosotros debemos construir nuestros propios Controllers), las cuales están construidas sobre el API de Servlets. Sin embargo, los desarrolladores somos libres de escribir nuestras propias implementaciones.

Un principio clave en Spring MVC es el principio de diseño "Abierto / Cerrado" o, por su nombre en inglés, "Open for extension, closed for modification". Algunos métodos en las clases core de Spring MVC están marcados como final para que los desarrolladores no podamos sobre-escribirlos y proporcionar nuestro propio comportamiento, de esta forma el framework garantiza que su comportamiento básico no puede ser modificado.

Antes de comenzar con el ejemplo hablaremos un poco más del "DispatcherServlet" que, como dijimos hace un instante, es la pieza central de Spring MVC. Spring MVC está, como muchos otros frameworks orientado a peticiones, diseñado alrededor de un Servlet central que despacha las peticiones a los controladores y ofrece otra funcionalidad que facilita el desarrollo de aplicaciones web. Sin embargo, el "DispatcherServlet" hace más que sólo eso, se encuentra completamente integrado con el contenedor de IoC de Spring, lo cual nos permite usar el resto de características de Spring.

El flujo del procesamiento de peticiones del "DispatcherServlet" se ilustra en el siguiente diagrama.

  1. El cliente hace una petición a la aplicación web, esta petición llega al DispatcherServlet.
  2. El DispatcherServlet determina qué componente debe atender la petición y la envía a este.
  3. El Controller implementa la lógica específica para responder la petición, para lo que puede hacer uso de cualquier recurso que esté al alcance de cualquier aplicación Java (incluyendo conexiones con servicios web o con base de datos).
  4. Una vez que el Controller termina su proceso, regresa la petición al DispatcherServlet, estableciendo los datos adecuados del modelo e indicando el nombre lógico de la vista que debe regresarse al cliente y un modelo lleno con los nombres y valores de los atributos que se usarán para generar la vista final.
  5. En base al nombre lógico regresado por el Controller, el DispatcherServlet usa un ViewResolver para determinar qué recurso debe utilizar para generar la vista final que se mostrará al usuario, este recurso puede ser una JSP, una página HTML, un template de Velocity, un archivo de Excel, un PDF, etc.
  6. El DispatcherServlet obtiene la vista que será regresada al cliente.
  7. El DispatcherServlet finalmente regresa la vista adecuada al cliente.
Como vemos, a partir del paso 5 decimos que debemos indicar qué componente y de qué forma se resolverá la vista (en este caso una página) debe regresarse en respuesta a la petición que haya hecho el usuario. Para esto Spring tiene un componente llamado "ViewResolver", el cual (como su nombre lo indica) se encarga de, en base a un nombre lógico, identificar el recurso (o página) al cual se está haciendo referencia.

La resolución de vistas en Spring es extremadamente flexible. Un Controller es típicamente responsable de preparar el modelo con datos y seleccionar el nombre de la vista que será regresada al usuario, sin embargo también podría escribir la respuesta directamente en el stream de respuesta (esto lo veremos en un tutorial posterior). La resolución del nombre de la vista es altamente configurable y se puede integrar con representación basada en tecnologías de templates como JSP, Velocity y Freemarker, o generar directamente XML, JSON, Atom, y muchos otros tipos de contenido.

Spring MVC tiene varias implementaciones de ViewResolver, como las que se muestran a continuación:

ViewResolverDescripción
AbstractCachingViewResolverEs un ViewResolver abstracto que coloca vistas en caché. Algunas veces las vistas necesitan cierta preparación antes de que puedan ser usadas; extendiendo esta clase proporciona ese caché.
XmlViewResolverImplementción de ViewResolver que acepta un archivo de configuración en XML con los mismos DTDs que los bean factories de Spring. El archivo de configuración por default es "/WEB-INF/views.xml"
ResourceBundleViewResolverImplementación de ViewResolver que usa definiciones de beans en un ResourceBundle, especificado por el nombre base del bundle. Tipicamente definimos el bundle en un archivo de propiedades, localizado en el classpath. El nombre por default es "views.properties"
UrlBasedViewResolverImplementación simple de ViewResolver que realiza la resolución directa de nombres de vistas lógicas a URLs, sin una definición explícita de mapeo. Es apropiado si nuestros nombres lógicos coinciden con los nombres de los recursos de vista de forma directa, sin la necesidad de mapeos adicionales.
InternalResourceViewResolverSubclase de UrlBasedViewResolver que soporta vistas basadas en JSPs. Tiene otras subclases útiles como JstlView y TilesView.
VelocityViewResolver / FreeMarkerViewResolverSubclase de UrlBasedViewResolver soporta vistas creadas usando templates creados usando Velocity o Freemarker, respectivamente, y sublclases propias de estos.
ContentNegotiatingViewResolverImplementación de ViewResolver que resuelve una vista basada en la petición para determinar el nombre del archivo de la vista, o en la cabecera "Accept".

Los anteriores son sólo algunos ejemplos, ya que Spring MVC tiene muchas implementaciones de ViewResolver, como por ejemplo vistas que permiten generar archivos de Excel, archivos PDF, etc.

Como ya habrán notado, el "DispatcherServlet" es una implementación del patrón de diseño "Front Controller" (esto es algo que Spring MVC comparte con muchos otros frameworks MVC). Este Servlet recibe las peticiones del usuario y dependiendo de la URL a la que vaya esta petición, el "DispatcherServlet" enviará la petición al Controller adecuado; el determinar cuál es el Controller adecuado depende de ciertas reglas, algunas de las cuales veremos a lo largo de este tutorial.

El Controller creará los objetos de modelo necesarios para la respuesta y los regresará al "DispatcherServlet", el cual nuevamente delegará la generación de la representación de la respuesta al motor de generación de vista, o "View", en base a una plantilla de la misma.

Finalmente será el mismo "DispatcherServlet" quien regresará la respuesta al usuario.

Suficiente teoría, comencemos con el ejemplo.

Lo primero que debemos hacer es descargar los jar de Spring MVC. Nosotros usaremos la última versión a la fecha que es la 4.2.3. Encontrar estos jars puede ser un poco complicado, pero podeos hacerlo desde el repositorio de Maven de Spring. Les recomiendo que descarguen el archivo (spring-framework-4.2.3.RELEASE-dist.zip).

También necesitaremos el archivo "commons-logging-api-1.2.jar" que podemos encontrar en la página de descarga de commons-logging, este lo podremos encontrar en el archivo "commons-logging-1.2-bin.zip".

En algunos servidores es posible que al ejecutar la aplicación obtengan el siguiente error:

java.lang.NoClassDefFoundError: javax/servlet/jsp/jstl/core/Config
 ...
Caused by: java.lang.ClassNotFoundException: javax.servlet.jsp.jstl.core.Config

Esto ocurre porque algunos servidores no tienen las clases de JSTL (que es lo que Spring MVC usa para generar la vista). ¿Qué podemos hacer si obtenemos este error? Afortunadamente es muy sencillo arreglarlo. Hay que agregar un jar más a nuestra librería, en este caso la librería del API de JSTL, la cual podemos descargar desde el repositorio de Maven. Es necesario descargar el archivo "javax.servlet.jsp.jstl-api-1.2.1.jar" y agregarlo a la biblioteca de "SpringMVC4.2" que crearemos en un momento.

Una vez que hayamos bajado el archivo con los jars de Spring, lo descomprimimos y veremos que tiene un subdirectorio "libs" el cual contiene todos los jars. Vamos al NetBeans y nos dirigimos al menú "Tools -> Libraries".



En la ventana que se abre presionamos el botón "New Library...":



En esta nueva ventana colocamos como nombre de la biblioteca "SpringMVC4.2" y como tipo dejamos "Class Libraries":



Ahora que tenemos nuestra biblioteca, presionamos el botón "Add Jar/Folder" para agregar los nuevos archivos que conformarán la biblioteca. Agregamos los siguientes archivos:
  • spring-aop-4.2.3.RELEASE.jar
  • spring-beans-4.2.3.RELEASE.jar
  • spring-context-4.2.3.RELEASE.jar
  • spring-core-4.2.3.RELEASE.jar
  • spring-expression-4.2.3.RELEASE.jar
  • spring-web-4.2.3.RELEASE.jar
  • spring-webmvc-4.2.3.RELEASE.jar
  • commons-logging-api-1.2.jar (que bajamos aparte)

Con estos es suficiente para hacer nuestros hola mundo iniciales ^_^. Una vez seleccionados estos archivos, nuestra biblioteca debe verse así:



Presionamos el botón "OK" y nuestra biblioteca estará lista para ser usada.

Como dijimos, en este primer tutorial de la serie veremos dos maneras de configurar Spring MVC, la primera será la tradicional forma usando archivos de configuración en XML, y la segunda será con una configuración hecha completamente en Java, con la cual no necesitaremos ni un solo archivo XML.

*Nota: Yo usaré dos proyectos, uno para usar archivos de configuración en XML y otro para configuración Java, y que el código de ambos no se mezcle.

Lo primero que haremos es crear un nuevo proyecto en NetBeans. Para esto vamos al Menú "File->New Project...". En la ventana que se abre seleccionamos la categoría "Java Web" y en el tipo de proyecto "Web Application".



Le damos una ubicación y un nombre al proyecto, en mi caso será "SpringMVCXMLConfig" para el proyecto que usa archivos de configuración XML y "SpringMVCJavaConf" para el que usa la configuración Java. Hacemos clic en el botón "Next" y se nos preguntará en qué servidor queremos desplegar nuestra aplicación, en mi caso usaré Tomcat 8 (pero debe funcionar igual en cualquier servidor de aplicaciones Java). También cambiaré el context path para tener algo más claro, en mi caso pondré "holaspringmvc":



Presionamos el botón "Finish" y veremos aparecer en el editor nuestra página "index.html"

Agregamos la biblioteca de "SpringMVC4.2" que acabamos de crear, para esto hacemos clic derecho sobre el nodo "Libraries" del proyecto, en el menú que aparece elegimos la opción "Add Library..."



En la ventana que se abre seleccionamos la biblioteca "SpringMVC4.2"



Con lo que nuestro proyecto tendrá los jars que definimos para esta librería:



Creamos un nuevo paquete en el nodo "Source Packages" del proyecto:



Y colocamos "com.javatutoriales.springmvc.configuracion.controllers" como nombre del paquete. Dentro de este nuevo paquete colocamos una clase llamada "SaludoController", que será nuestro primer controlador.

Un Controller, como ya hemos dicho, es POJO que implementa un conjunto de métodos que son invocados para manejar las peticiones HTTP.

Lo primero que debemos hacer en esta clase para indicar que es un controlador, es colocar la anotación "@Controller" a nivel de la clase, de esta forma:

@Controller
public class SaludoController {

}

Ahora creamos un método llamado "saluda", este método debe regresar un objeto de tipo "ModelAndView", que es lo que regresan los métodos que esperan dar al usuario una respuesta en base a una página web (al contrario de un XML, JSon o flujo de bytes). Este objeto encapsula tanto el nombre de la página que será usada como vist como los datos que conforman el modelo. En cuanto a los parámetros que recibe el método, en realidad Spring MVC ofrece un mundo de posibilidades en este tema, que abarcaremos en otro tutorial, por ahora dejaremos vacía esta lista de parámetros. Hasta ahora nuestro método se ve así:

public ModelAndView saluda() {        
}

Ahora debemos indicar qué vista será la que se regrese en caso de le ejecución exitosa de este método, para este ejemplo el nombre lógico de la vista que regresaremos será una página llamada "saludo" (que crearemos en un momento), para esto simplemente regresaremos un nuevo objeto ModelAndView, en cuyo constructor pasaremos el nombre lógico de la vista, o sea "saludo".

public ModelAndView saluda() {
    return new ModelAndView("saludo");
}

Para terminar con este método, debemos colocarle la anotación @RequestMapping. Esta anotación ayuda a indicar que este método será un manejador de peticiones, y también ayuda a indicar el patrón de peticiones a las que responderá este manejador específico. Spring MVC usa el valor de esta anotación a nivel método, en combinación con la anotación a nivel clase (que en este caso no hemos colocado por lo que el patrón que se usará será vacío), entre otras condiciones para decidir cuales peticiones serán las que atienda este método, esto lo explicaremos más detalladamente en el siguiente tutorial, ya que depende de varios factores dependiendo de cómo vayamos a recibir la información de la petición. En este caso no colocaremos ningún valor, lo que significa que responderá a la petición que se haga a la URL vacía (o "/"). Como tampoco hemos indicado un patrón de peticiones a nivel de clase lo que esperamos es que este método responda a las peticiones que se hagan a la URL http://localhost:8080/holaspringmvc/. Al final nuestro método "saluda" queda de la siguiente forma:

@RequestMapping
public ModelAndView saluda() {
    return new ModelAndView("saludo");
}

Y la clase SaludoController completa así:

@Controller
public class SaludoController {

    @RequestMapping
    public ModelAndView saluda() {
        return new ModelAndView("saludo");
    }
}

Ahora crearemos la página que mostrará la respuesta a esta petición. Esta página estará en el directorio "/WEB-INF/pages/" y será una JSP llamada "saludo".

Creamos un nuevo directorio, dentro de "WEB-INF", llamado "pages" y dentro de este una nueva JSP llamada "saludo.jsp":



El contenido de la página será muy sencillo y sólo tendrá la estructura básica de una página HTML y un mensaje que diga "Hola Spring MVC", de la siguiente forma:

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Spring MVC - Java Tutoriales</title>
    </head>
    <body>
        <h1>Hola Spring MVC</h1>
    </body>
</html>

Los siguientes pasos dependen del modo de configuración que hayamos decidido usar, ya sea archivos de configuración en XML o configuración basada en Java. Iniciaremos, como es costumbre, con la configuración en base a archivos XML.


Configuración con archivos XML

En este caso para configurar el "DispatcherFilter" de Spring MVC necesitamos un deployment descriptor o archivo "web.xml" de nuestro proyecto web. Para agregar este archivo hacemos clic derecho en el proyecto y seleccionamos "New -> Other...".



En la categoría elegimos "Web" y en "File Types" seleccionamos "Standard Deployment Descriptor (web.xml)"



Presionamos el botón "Finish", con lo veremos aparecer el contenido de nuestro archivo.

En este archivo configuraremos el DispatcherServlet que, como ya dijimos, es la pieza central de Spring MVC. Este Servlet procesará todas las peticiones que vayan hacia el framework, y decidirá qué componente debe atender cada una de las solicitudes.

A este Servlet podemos darle cualquier nombre que queramos, y la clase que lo implementa es "org.springframework.web.servlet.DispatcherServlet". Este Servlet debe ser el primero en iniciar cuando se cargue la aplicación, por lo que deberá ser el número 1 en el orden de inicio. La configuración del "DispatcherServlet" queda de la siguiente forma:

<servlet>
    <servlet-name>spring-web</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>

Lo siguiente es indicar qué patrón de peticiones serán las que pasen a través del DispatcherServlet, en nuestro caso queremos que todas las peticiones pasen a través de él, por lo que la configuración queda de esta forma:

<servlet-mapping>
    <servlet-name>spring-web</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

Como podemos ver, para configurar el patrón de URLs que se enviarán a este Servlet debemos indicar, primero, el nombre que habíamos indicado anteriormente, y como patrón de URLs "/".

Nuestro archivo "web.xml" completo se ve de la siguiente forma:

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

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
    
    <servlet>
        <servlet-name>spring-web</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>spring-web</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <session-config>
        <session-timeout>30</session-timeout>
    </session-config>
</web-app>

Lo siguiente es configurar el "DispatcherServlet" para indicar algunos datos particulares de cómo queremos que se comporte. El "DispatcherServlet" trae por default un conjunto de configuraciones que sirven para la mayoría de los casos. En nuestro caso la única configuración que haremos es cómo mapear los nombres lógicos de las respuestas a las páginas que conforman las vistas.

Esta configuración debe estar en un archivo XML que debe colocarse a la misma altura que el archivo "web.xml" – o sea en el directorio "WEB-INF" de la aplicación. El nombre de este archivo debe seguir una convención, y debe ser el mismo nombre que le hayamos dado al "DispatcherServlet" en la configuración del Deployment Descriptor (en nuestro caso "spring-web"), agregando "-servlet.xml", por lo que en mi caso el nombre del archivo debe ser "spring-web-servlet.xml".

¿Qué ocurre si queremos colocar el archivo de configuración de Spring MVC en otra ubicación o queremos darle otro nombre? Para esto existe una manera de sobre-escribir la ruta del archivo, a través de un parámetro que pasamos a la configuración del "DispatcherServlet". En mi caso me gusta tener estos archivos de configuración en un directorio "config" dentro del "WEB-INF". Para lograr esto agregamos un parámetro llamado "contextConfigLocation", cuyo valor será la ubicación del archivo de configuración de Spring MVC. En mi caso el nombre del archivo será "springMVCconfig.xml" y estará en el directorio "WEB-INF/config".

Modificamos la configuración del "DispatcherServlet" para incluir este parámetro, de la siguiente forma:

<servlet>
    <servlet-name>spring-web</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/config/springMVCconfig.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

Ahora, creamos un nuevo directorio llamado "config" dentro de "WEB-INF":



Y dentro de este creamos un archivo XML llamado "springMVCconfig.xml":



Inicialmente el contenido del archivo será el siguiente:

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

</beans>

Usamos el elemento "component-scan" del namespace "context" para indicar dónde se encuentran los componentes de Spring; recuerden que este elemento habilita los elementos que tengan las anotaciones @Component, @Repository, @Service, y @Controller (en nuestro caso sólo usaremos esta última). Usamos el atributo "base-package" para indicar en qué paquete están nuestros controladores, en nuestro caso "com.javatutoriales.springmvc.configuracion.controllers". Este elemento queda de la siguiente forma:

<context:component-scan base-package="com.javatutoriales.springmvc.configuracion.controllers" />

Lo siguiente es habilitar los componentes de Spring MVC con sus configuraciones por default, esto lo hacemos con el elemento "annotation-driven" del namespace "mvc", de la siguiente forma:

<mvc:annotation-driven />

Esta etiqueta adicionalmente registrará los "HandlerMapping" y "HandlerAdapter" requeridos para despachar los Controllers. Adicionalmente, aplica algunas configuraciones por default basado en lo que se encuentra en nuestro classpath. Estas configuraciones por default son:
  • Agrega soporte para formatear campos numéricos anotados con @NumberFormat
  • Agrea soporte para formateo de campos tipo Date, Calendar, y Joda Time anotados con @DateTimeFormat, si Joda Time está en el classpath
  • Agrega soporte para validar campos de entrada en @Controller con @Valid, si un proveedor JSR-303 se encuentra en el classpath
  • Agrega soporte para leer y escribir XML, si JAXB se encuentra en el classpath
  • Agrega soporte para leer y escribir JSON, si Jackson se encuentra en el classpath

Finalmente debemos indicar qué implementación de "ViewResolver" usaremos. En este caso será un "InternalResourceViewResolver", el cual como vimos permite indicar el nombre lógico de la página que representa la vista, siempre y cuando el nombre del archivo sea igual al nombre lógico de nuestra vista; esto quiere decir que si decidimos que nuestra vista serán JSP y que estarán en el directorio "WEB-INF/pages", y un Controller indica que se debe regresar una vista llamada "accesoUsuario", esto quiere decir que debe existir un archivo "/WEB-INF/pages/accesoUsuario.jsp".

Este "ViewResolver" se configura como un bean "normal" de Spring, de la siguiente forma:

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
</bean>


Para configurar el "InternalResourceViewResolver" debemos indicar cuál será el prefijo de las páginas. Este por lo general lo usamos para indicar en qué directorio se encuentran las páginas (a partir de la raíz donde se colocan las páginas web), que en nuestro caso será en el directorio "/WEB-INF/pages", por lo que hasta ahora la configuración se ve de la siguiente forma:

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix">
        <value>/WEB-INF/pages/</value>
    </property>
</bean>


La segunda propiedad que debemos indicar es el sufijo que tendrán las páginas. En esta propiedad por lo general indicamos la extensión de las mismas. En nuestro caso las páginas serán JSPs, por lo que el sufijo será ".jsp". La configuración queda de esta forma:

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix">
        <value>/WEB-INF/pages/</value>
    </property>
    <property name="suffix">
        <value>.jsp</value>
    </property>
</bean>


Y el archivo "springMVCconfig.xml" completo queda de la siguiente forma:

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

    <context:component-scan base-package="com.javatutoriales.springmvc.configuracion.controllers" />
    <mvc:annotation-driven  />
 
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix">
            <value>/WEB-INF/pages/</value>
        </property>
        <property name="suffix">
            <value>.jsp</value>
        </property>
    </bean>
</beans>


Ya tenemos todos los elementos en su lugar, por lo que el siguiente paso es ejecutar la aplicación y entrar a la siguiente dirección:

http://localhost:8080/holaspringmvc


Con lo que debemos de ver la siguiente pantalla:




Esto indica que todo está bien configurado y funciona correctamente.

Ahora veremos cómo realizar esta misma configuración sin un solo archivo XML.


Configuración Java (sin XML)

Lo primero que hay que saber es que esta configuración (al menos la que reemplaza el archivo web.xml) sólo servirá en contenedores que implementen la especificación de Servlets 3.0+, esto ya que a partir de esta versión es que se permite configurar el ServletContext de manera programática.

¿Qué ocurre si nuestro contenedor no soporta al menos la versión 3.0 de Servlets? Pues siempre podremos seguir usando el archivo web.xml para configurar el DispatcherServlet, el resto de la configuración programática puede realizarse sin problemas.

A continuación, creamos un nuevo paquete, que en este caso será "com.javatutoriales.springmvc.configuracion.config". En este nuevo paquete colocaremos las clases que nos servirán para realizar la configuración.

La especificación 3.0 de Servlet introduce la interface ServletContextInitializer, las clases que implementan esta interface son notificadas por el contenedor sobre los eventos de inicio del contenedor de Servlets. Spring hace uso de esto en su interface "WebApplicationInitializer", que asegura que la clase que la implemente es detectada y usada automáticamente para inicializar cualquier contenedor de Servlets 3. Así que es mediante esta interface que podremos configurar el "DispatcherServlet" de Spring MVC.

Dentro del nuevo paquete creamos una clase llamada "SpringWebApplicationInitializer", esta clase debe implementar la interface "WebApplicationInitializer". Esta interface sólo tiene un método: "onStartup", por lo que hasta ahora nuestra clase se ve de esta forma:

public class SpringWebApplicationInitializer implements WebApplicationInitializer {
    
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
    
    }
}

Dentro del método "onStartup" será necesario crear un ApplicationContext de Spring, también de forma programática, y agregarlo al ServletContext de la aplicación. Para la primera parte podemos crear cualquiera de los ApplicationContext que vimos en el segundo tutorial de la serie, en donde aprendimos que tenemos una clase llamada "AnnotationConfigApplicationContext", la cual nos ayuda a inicializar los beans de las clases anotadas con cualquiera de los estereotipos de Spring. Para el caso de las aplicaciones web existe una clase equivalente que es "AnnotationConfigWebApplicationContext".

public void onStartup(ServletContext servletContext) throws ServletException {
    AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
}

A esta clase debemos indicarle cuáles son las clases que cuentan con las anotaciones que debe procesar. En nuestro caso será la clase que contenga la configuración de los elementos de Spring MVC, que es una clase que aún no creamos, pero que se llamará "SpringWebConfig". Para indicarle a "AnnotationConfigWebApplicationContext" cuál es esta clase, usamos el método "register". También debemos indicarle a esta clase cual será el ServletContext que usará para la aplicación, en este caso será el mismo que el método "onStartup" recibe como parámetro. Hasta ahora la configuración se ve así:

public void onStartup(ServletContext servletContext) throws ServletException {
    AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
    context.register(SpringWebConfig.class);
    context.setServletContext(servletContext);
}

El siguiente paso es registrar el DispatcherServlet dentro de nuestro ServletContext, para esto usamos el método "addServlet" del ServleContext que recibimos como parámetro. "addServlet" recibe dos parámetros, el primero es el nombre del Servlet, similar a como lo colocamos en el elemento "servlet-name" cuando trabajamos con el archivo "web.xml". El segundo parámetro es la instancia del DispatcherServlet que recibirá las peticiones de los usuarios. Para crear la instancia de este DispatcherServlet es necesario pasar el ApplicationContext que contiene las definiciones de los controladores.

El método "addServlet" regresa una instancia de "ServletRegistration.Dynamic" que nos ayudará a terminar de configurar el DispatcherServlet. En esta instancia indicamos que este debe ser el primer Servlet en ejecutarse cuando se inicie la aplicación y que todas las peticiones que lleguen a la aplicación deben pasar a través de él:

ServletRegistration.Dynamic dispatcher = servletContext.addServlet("spring-web", new DispatcherServlet(context));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");

y la clase SpringWebApplicationInitializer completa queda de la siguiente forma:

public class SpringWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(SpringWebConfig.class);
        context.setServletContext(servletContext);

        ServletRegistration.Dynamic dispatcher = servletContext.addServlet("spring-web", new DispatcherServlet(context));
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/");
    }
}

El último paso es indicar la configuración propia de los elementos de Spring MVC, tal y como lo hicimos en el archivo "springMVCconfig.xml", sólo que en este caso lo haremos completamente mediante código Java.

Lo primero es crear una nueva clase, dentro del paquete config, que se llame "SpringWebConfig":

public class SpringWebConfig {
}

Esta clase contendrá la configuración de Spring MVC, por lo que debemos colocar la anotación @Configurable a nivel de clase. Esta anotación indica que esta clase declara uno o más métodos anotados con @Bean, los cuales deben ser procesados por el contenedor de Spring para generar definiciones de los beans y peticiones de esos beans en tiempo de ejecución.

@Configuration
public class SpringWebConfig {

}

El siguiente paso es colocar la anotación @EnableWebMvc, que es el equivalente al elemento "<mvc:annotation-driven>", la cual ya explicamos anteriormente.

@Configuration
@EnableWebMvc
public class SpringWebConfig {

}

A continuación debemos indicar en qué paquetes de nuestra aplicación se encuentran los Controllers, tal como lo hicimos antes con el elemento "<context:component-scan>", aquí también tenemos una anotación equivalente que es @ComponentScan, el cual funciona exactamente como el elemento del archivo de configuración en XML, inclusive tiene el mismo atributo "basePackages", que en este caso también colocaremos con el valor de "com.javatutoriales.springmvc.configuracion.controllers".

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.javatutoriales.springmvc.configuracion.controllers")
public class SpringWebConfig {

}

Finalmente, y para terminar con la configuración debemos crear los beans que Spring usará en nuestra aplicación, en este caso es únicamente el ViewResolver que indicará cómo se mapearán los nombres lógicos de las vistas con los recursos que implementarán la vista.

Como dijimos hace un momento, esto lo hacemos con un método al que le colocamos la anotación @Bean y que creará el objeto en cuestión. El nombre del método en este caso no importa, así que yo le pondré el nombre "viewResolver", el método hasta ahora se ve de la siguiente manera:

@Bean
public ViewResolver viewResolver() {

}

Nuevamente usaremos un "InternalResourceViewResolver" como implementación, por lo que lo creamos de forma directa:

@Bean
public ViewResolver viewResolver() {
    InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
}

Al igual que hicimos en el archivo de configuración, es necesario indicar cuál será el prefijo (o directorio en el que se encuentran las páginas que se usarán como vistas) y el sufijo (o extensión de nuestras páginas). La clase "InternalResourceViewResolver" proporciona métodos para hacer esto de forma directa:

@Bean
public ViewResolver viewResolver() {
    InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
    viewResolver.setPrefix("/WEB-INF/pages/");
    viewResolver.setSuffix(".jsp");    
}

Finalmente, el último paso es regresar este objeto:

@Bean
public ViewResolver viewResolver() {
    InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
    viewResolver.setPrefix("/WEB-INF/pages/");
    viewResolver.setSuffix(".jsp");
    
    return viewResolver;
}

La clase "SpringWebConfig" completa se ve de la siguiente forma:

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.javatutoriales.springmvc.configuracion.controllers")
public class SpringWebConfig {

    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/WEB-INF/pages/");
        viewResolver.setSuffix(".jsp");
        
        return viewResolver;
    }
}

Hemos terminado, nuestra configuración. Por fin podemos ejecutar la aplicación, entrar a la siguiente URL:

http://localhost:8080/holaspringmvc

y espera que aparezca el siguiente mensaje en pantalla:



Con lo que podemos comprobar que todo está funcionando de manera correcta ^_^.

Ahora que tenemos nuestro primer ejemplo básico agreguemos un segundo Controller.

Creamos una nueva clase que se llame "SaludosGeneralesController" dentro del paquete "controllers". A esta clase le colocamos la anotación "@Controller".

@Controller
public class SaludosGeneralesController{

}

En esta ocasión sí colocaremos la anotación "@RequestMapping" a nivel de clase, esto con el objetivo de indicar un patrón del URLs que serán enviados a los distintos métodos de esta clase. En este caso el valor que colocaremos en esta anotación será "/saludos":

@Controller
@RequestMapping("/saludos")
public class SaludosGeneralesController{

}

Ahora crearemos un nuevo método llamado "saluda" que regresará un objeto de tipo String, que será el nombre lógico de nuestra vista, y en esta ocasión sí recibirá un parámetro que será un objeto de tipo Model. Model es una abstracción de Spring MVC que representa… pues el modelo, o en este caso la información que se regresa desde la parte del negocio a la vista. En esta ocasión la usaremos para regresar un par de valores a una JSP:

String saluda(Model model)
{
        
}

Este método también lo marcaremos con "@RequestMapping" para indicar que será un manejador de peticiones, y en este caso el valor que colocaremos a esta anotación será "saludoGeneral":

@RequestMapping("/saludoGeneral")
String saluda(Model model) {

}

Esto quiere decir que esté método será invocado cuando el usuario haga una petición a nuestro servidor usando en conjunto el context path (que definimos como "holaspringmvc"), el patrón de URLs a las que responde el DispatcherServlet ("/"), el valor de la anotación "@RequestMapping" de la clase ("/saludos") y el valor de la anotación "@RequestMapping" del método ("/saludoGeneral"). Por lo tanto la URL a la que esperamos que responda esta método es:

http://localhost:8080/holaspringmvc/saludos/saludoGeneral

Dentro del método lo único que haremos será pasar dos pares de nombre-valor a nuestro modelo, que posteriormente mostraremos en una JSP: "sitio" cuyo valor será "JavaTutoriales.com" y "tutorial" cuyo valor será "Spring MVC".

Para poder agregar estos valores "Model" proporciona un método llamado "addAttribute", que recibe dos parámetros de tipo String, el primero es el nombre del atributo y el segundo su valor, de la siguiente forma:

@RequestMapping("/saludoGeneral")
String saluda(Model model) {
    model.addAttribute("sitio", "JavaTutoriales.com");
    model.addAttribute("tutorial", "Spring MVC");
}

Finalmente regresamos el nombre lógico de la vista, que en este caso será "saludoGeneral":

@RequestMapping("/saludoGeneral")
String saluda(Model model) {
    model.addAttribute("sitio", "JavaTutoriales.com");
    model.addAttribute("tutorial", "Spring MVC");

    return "saludoGeneral";
}

La clase "SaludosGeneralesController" completa queda de la siguiente forma:

@Controller
@RequestMapping("/saludos")
public class SaludosGeneralesController {

    @RequestMapping("/saludoGeneral")
    String saluda(Model model) {
        model.addAttribute("sitio", "JavaTutoriales.com");
        model.addAttribute("tutorial", "Spring MVC");

        return "saludoGeneral";
    }
}

Para terminar este tutorial crearemos una nueva JSP en el directorio "/WEB-INF/pages", que es donde estamos colocando las vistas que serán regresadas por la aplicación. Esta página se llamará "saludoGeneral.jsp" y tendrá el siguiente contenido:

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Spring MVC - JavaTutoriales.com</title>
    </head>
    <body>
        <h1>${sitio}</h1>
        <p>${tutorial}</p>
    </body>
</html>

Ejecutamos nuevamente nuestra aplicación y entramos a la siguiente dirección:

http://localhost:8080/holaspringmvc/saludos/saludoGeneral

Con lo que debemos ver la siguiente página:



Lo cual nuevamente nos indica que todo ha funcionado correctamente ^_^.

En este pequeño tutorial hemos dado sólo una breve introducción a Spring MVC, este framework (o módulo de Spring) es en realidad muy grande y tiene muchos elementos que iremos aprendiendo a lo largo de esta serie de tutoriales.

Espero que este tutorial les sea de utilidad. Si tienen alguna duda, sugerencia, comentario o aclaración, pueden dejarla en la sección de comentarios o enviar un correo a programadorjavablog@gmail.com (pueden agregarme al chat de gmail). También pueden seguir JavaTutoriales en las siguientes redes sociales:

Saludos y gracias.

Descarga los archivos de este tutorial desde aquí:



Entradas Relacionadas:

13 comentarios:

  1. Muy buenas Alex,tienes unos tuto que da gusto verlos xq dejan las cosas muy claritas(muy importante en este mundo de la programación):).
    Me gustaría saber si vas a seguir colgando las continuaciones de este primer SpringMVC y si es así si va a ser pronto.

    Un saludo amigo.

    ResponderEliminar
    Respuestas
    1. Hola Markus. Que bueno que el tutorial te ha gustado.

      Claro que sí, la intención es hacer una serie completa de tutoriales (unos 6 o 7) de Spring MVC, el segundo ya está casí listo, solo que por motivos de trabajo no puedo avanzar tan rápido como quisiera.

      Saludos y gracias.

      Eliminar
  2. Lo que haces es brillante, estoy muy agradecido..
    Te felicito!
    Seria genial si algun dia haces algun post integrador...Spring mvc + Hibernate + mysql..

    ResponderEliminar
    Respuestas
    1. Hola Marce;

      Muchas gracias por tus comentarios, en realidad me alientan a seguir haciendo tutoriales (pronto el segundo tutorial de Spring MVC).

      Claro que sí, de hecho pensaba hacerlo con Struts 2, pero ya que comenzamos con Spring MVC, avanzaré un poco en la serie para que la integración tenga sentido.

      Hay un tutorial de integración de Spring con Hibernate, en realidad no hay mucho más que decir para usar estos dos frameworks en conjunto. Aún así en un futuro no muy lejano espero poder publicar el de integración.

      Saludos.

      Eliminar
    2. Alex, hace un tiempo decidi comenzar a estudiar java por que me gusta y quiero concretar algunos proyectos que tengo en mente. A traves de tu sitio fuiste como un maestro.
      Con respecto a los Frameworks me inclino por Spring por que pareciera que es el mas utilizado y elogiado..
      Aun no llegue a leer tu tutorial de Spring con Hibernate ( demoro por que desarrollo en mi pc todos los ejemplos ) pero estoy cerca..

      Eliminar
    3. Hola Marce;

      Nuevamente muchas gracias por tus comentarios, en verdad que son un gran estímulo para continuar con este trabajo.

      Completamente de acuerdo con lo que mencionas de Spring, es el más utilizado por lo flexible que es y lo mucho que facilita un desarrollo.

      Si puedo ayudarte en algo, al final de cada post puedes encontrar mi correo.

      Saludos y nuevamente muchas gracias por tus comentarios.

      Eliminar
  3. Para cuando los proximos tutoriales de Spring MVC??? Perdon pero estamos ansiosos..

    ResponderEliminar
    Respuestas
    1. Hola Marce;

      Ya está listo, ya puedes leer la segunda parte en el siguiente enlace:

      http://www.javatutoriales.com/2016/02/spring-mvc-parte-2-seleccion-de.html

      :)

      Eliminar
  4. el mejor tutorial que he visto de Spring. Felicitaciones !!

    ResponderEliminar
  5. Saludos, muy buenos los tutoriales sobre Spring.

    ResponderEliminar
  6. No me funciono la parte en que configuras sin usar el web.xml... no tengo ningun error ni warning, pero al momento de correr la aplicación, me dice que el recurso no esta dispponible.. :(

    ResponderEliminar
    Respuestas
    1. Ya lo resolví... creo que tenía un conflicto con las librerias... estoy usando Maven, y creo que coloque algunas dependencias innecesarias...

      Eliminar