Controladores con anotaciones en Spring MVC Framework - ChuWiki



En la versión 3 de Spring Web Framework se da prioridad a las anotaciones a la hora de hacer controladores sobre la herencia del controlador de la interfaz Controller y las clases Controladoras que ya
venían preparadas en la API de Spring Framework, como SimpleFormController. Veamos aquí algunos conceptos/ejemplos básicos sobre cómo crear controladores con anotaciones.

Configuración de web.xml y springapp-servlet.xml

Como en cualquier aplicación con Spring Web Framework, debemos
configurar nuestro web.xml para que tenga el Servlet propio de Spring
Web Framework (org.springframework.web.servlet.DispatcherServlet). Una
configuración típica puede ser



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

  <servlet-mapping>
    <servlet-name>springapp</servlet-name>
    <url-pattern>*.htm</url-pattern>
  </servlet-mapping>
  ...
</web-app>
El nombre de nuestro servlet es springapp, puede ser el nombre que
nosotros queramos. La clase Servlet es propia de Spring
org.springframework.web.servlet.DispatcherServlet y es la que se
encargará de recibir las peticiones de los navegadores web y
redirigirlas a los controladores que hagamos en nuestro código. El
apartado <servlet-mapping> indica qué peticiones del navegador se
deben redirigir a qué servlet, en concreto, cualquier petición de un
fichero .htm se redirigirá al servlet springapp (o el nombre que hayamos
decidido ponerle). Como hemos comentado, el servlet redirigirá estas
peticiones a nuestros controladores.


El servlet org.springframework.web.servlet.DispatcherServlet
también se encarga de cargar todos los beans de Spring Framework que le
indiquemos. Para ello, carga un fichero springapp-servlet.xml situado en
el directorio WEB-INF, junto a web.xml y carga los beans que se
indiquen en ese fichero. El nombre del fichero debe ser el nombre del
servlet (springapp en nuestro ejemplo) seguido de -servlet.xml.


Si queremos usar controladores con anotaciones, en este fichero
springapp-servlet.xml debemos indicar dónde están las clases que hace de
controlador y que llevan las anotaciones correspondientes. El contenido
del fichero springapp-servlet.xml puede ser como el siguiente



<?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:p="http://www.springframework.org/schema/p" 
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-2.5.xsd">

    <context:component-scan base-package="com.chuidiang.ejemplo_spring"/>
    
</beans>
Hay que fijarse que en la parte de xsi:schemaLocation de
<beans> hemos añadido dos líneas para context y
spring-context-2.5.xsd (aparte de las dos "estándar" de beans y
spring-beans). Esto nos permite usar el tag
<context:component-scan> en donde se indica el paquete java a
partir del cual buscar clases con anotación de controladores.







El controlador

Tal cual lo tenemos configurado, cuando hagamos desde el navegador
una petición de un fichero .htm, el Servlet
org.springframework.web.servlet.DispatcherServlet tratará de redirigir
la llamada a uno de nuestros controladores. Para hacer dicho
controlador, tenemos que hacer una clase java con la anotación
@Controller, ya que hemos elegido hacerlo con anotaciones en clases
dentro del paquete com.chuidiang.ejemplo_spring, como indica el fichero
springapp-servlet.xml


La clase Controlador comenzará más o menos de esta forma



package com.chuidiang.ejemplo_spring;

import org.springframework.stereotype.Controller;

@Controller
public class Controlador {
...
El siguiente paso es decidir exactamente a qué método de nuestro
controlador queremos que se llame en función del página htm pedida. Para
ello nos basta con poner un método cualquiera en la clase con la
anotacion @RequestMapping. Bueno, en realidad el método no puede ser de
cualquier forma, tiene que ser un método que admita ninguno, uno o más
de determinados parámetros y que devuelva uno de los tipos que luego
entenderá el Framework de Spring. El siguiente enlace RequestMapping
muestra un listado de todos los posibles tipos de parámetros que puede
admitir el método y los posibles tipos de resultados. No vamos a ver
aquí todas las combinaciones posibles, simplemente unos cuantos ejemplos
más o menos habituales.


Uno de los más habituales es que admita como parámetro un ModelMap y devuelva un String.



@RequestMapping("uno.htm")
 public String uno(ModelMap modelo) {
  modelo.addAttribute("uno", "1");
  return "index.jsp";
 }
La anotación @RequestMapping debe llevar entre comillas la página que
debe teclear el usuario para que se llame a este método. Si nuestra
aplicación está en un fichero aplicacion.war y lo hemos desplegado en
apache tomcat (u otro servidor de servlets), la siguiente url


http://nuestroservidor:8080/aplicacion/uno.htm


provocará la llamada a nuestro método uno() del controlador. Hay
que fijarse que en el fichero web.xml habíamos puesto que se enviaran al
servlet org.springframework.web.servlet.DispatcherServlet las
peticiones de páginas .htm. Será el servlet el que al ver que se pide
uno.htm se encargue de llamar al método uno() del controlador.


El método uno() del controlador devuelve en este caso un String
(uno de los tipos admitidos para los métodos que anotación
RequestMapping. En este String debemos indicar cual es la página jsp que
se debe mostrar. Así, el servlet, después de llamar al controlador,
llamará a la página jsp que devolvamos en el método, en nuestro caso
index.jsp ( http://nuestroservidor:8080/aplicacion/index.jsp )







Pasar datos a la vista

La gracia de usar un controlador consiste en el parámetro ModelMap.
En ese parámetro, que en el fondo es como un HashMap, podemos meter
objetos java identificados por una clave y que estarán accesibles desde
la página jsp. De esta forma, podemos visualizar unos datos u otros en
la página jsp. En el ejemplo, hemos metido dentro de ModelMap un objeto
String de valor "1" identificado por la clave "uno". Este valor estará
accesible desde index.jsp y podemos acceder a él así



<%@ page session="false"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
...
<h2>uno =  <c:out value="${uno}" /></h2>
Por supuesto, en un ejemplo más complejo el controlador podría cargar
datos de una base de datos, meter una lista (List<Dato>) dentro
del ModelMap y nuestro index.jsp podría mostrar una con todos esos
datos. Y por supuesto, no tenemos por qué meter solo un objeto java,
podemos meter todos los que queramos, cada uno con su clave propia.

Datos compartidos por todos los métodos

Es
posible que nuestro controlador tenga varios métodos, atendiendo de
esta forma a varias URLs, y es posible también que en todas ellas nos
interese pasar un mismo dato a la vista, siempre el mismo. Puesto que
sería un poco "rollo" tener que rellenar el ModelMap en todos los
métodos siempre igual, podemos hacer un método especial con la anotación
@ModelAttribute, de esta forma


@ModelAttribute("dato")
public UnBean unMetodoCualquiera() {
   UnBean bean = new UnBean();
   bean.setTexto("inicial"); // suponiendo que unBean tenga método setTexto(String)...
   return bean;
}
es decir, un método con la anotación @ModelAttribute con el
nombre que queremos que quede etiquetado el dato en cuestión ("dato" en
este caso). El método no recibe parámetros y devuelve el dato que
queremos pasar a la vista, de cualquier clase que queramos. Sólo tenemos
que rellenar el dato y hacer un return de él.


Spring llamará a todos los métodos con anotación @ModelAttribute
antes de hacer la llamada definitiva al método @RequestMapping que toque
en función de la URL. De esta forma, cualquier llamada a este
controlador pasará a la vista el "dato" con "UnBean" relleno dentro.


Datos de sesión

La
"pega" del método anterior es que siempre nos devolverá el dato
inicializado según lo inicialicemos en el método. A veces nos interesa
tener datos de sesión, que se mantengan mientras navegamos de una página
a otra del controlador y que podamos cambiar sólo si nos interesa. Para
conseguir esto, en la clase ponemos la anotación @SessionAttributes(),
indicando qué datos queremos que se mantengan en sesión


@Controller
@SessionAttributes({"datoSesion1","datoSesion2"})
public class Controlador {
   ...
En este ejemplo, los datos "datoSesion1" y "datoSesion2" serán
de sesión y mantendrán sus valores mientras navegamos por las páginas
del controlador. En principio estos datos no están definidos, pero
podemos definirlos en cualquier método


@RequestMapping("uno.htm")
public String uno(ModelMap modelo) {
   modelo.addAttribute("uno", "1");
   modelo.addAttribute("datoSesion1","uno");
   return "index.jsp";
}
Y ahí permanecerá ese dato, disponible en el modelo que se pasa
a todos los métodos del controlador, hasta que otro método del
controlador lo borre o lo modifique.


Ojo, no hay que definir datos de sesión en los métodos con
anotación @ModelAttribute, puesto que a estos métodos se los llama
siempre que hay una petición a nuestro controlador y el dato de sesión
acabará con un valor fijo, el que le de ese método. Tampoco tiene
sentido hacerlo que el dato sea de sesión si lo vamos a definir en un
método anotado con @ModelAttribute, porque siempre se llama a este
método y va a definirlo y estar disponible para todos los demás métodos,
sea o no de sesión.


En el momento que nos interese, desde un método del controlador
podemos limpiar los datos de sesión. Para ello, el método debe recibir
un atributo SessionStatus y debemos llamar a su método setComplete()


@RequestMapping("dos.htm")
public String dos(SessionStatus status, ModelMap modelo) {
   status.setComplete();
   return "index.jsp";
}
Es importante notar que este método no borra el dato de
ModelMap en el momento, cuando se llama a este método dos(), la página
index.jsp de retorno seguirá teniendo el dato disponible en ModelMap. Es
cuando salimos de esa página a la siguiente cuando el dato deja de
estar disponible ... hasta que alguien vuelva a definirlo. Podemos
borrarlo si nos interesa con modelo.remove()






@RequestMapping("dos.htm")
public String dos(SessionStatus status, ModelMap modelo) {
   status.setComplete();
   modelo.remove("datoSesion1");
   return "index.jsp";
}

Paso de parámetros request al controlador

Los
métodos del controlador pueden recibir parámetros de una petición del
navegador, bien sea por método POST o por método GET. La forma de
hacerlo sería poner en nuestro controlador un método de esta forma


@RequestMapping(value = "tres.htm")
public String tres(@RequestParam("texto") String texto,  ModelMap modelo) {
   ...      
de esta forma, recibiremos un parámetro de nombre texto si hacemos una llamada a nuestro método con esta url


http://nuestroservidor:8080/aplicacion/tres.htm?texto=hola


que sería en forma GET o bien, si enviamos desde un formulario con POST


<form method="post" action="tres.htm">
   <input type="text" name="texto"/>
   <input type="submit" value="OK"/>
</form>
Si queremos que el método sólo atienda GET o POST, podemos indicarlo en la anotación del método del controlador



@RequestMapping(value = "tres.htm", method = RequestMethod.POST)
public String tres(@RequestParam("texto") String texto,  ModelMap modelo) {
    ...