Flujo General de una Petición HTTP
El ciclo de vida de una solicitud en una aplicación Spring MVC sigue una secuencia estructurada que involucra al contenedor de servlets y al framework:
- El cliente emite una petición HTTP, por ejemplo:
http://localhost:8080/app/api/users.action. - El contenedor de servlets (como Tomcat) recibe la solicitud. Basándose en el
url-patterndefinido enweb.xml, sabe que las peticiones con extensión*.actiondeben ser delegadas alDispatcherServlet. - El
DispatcherServletactúa como controlador frontal. Consultando su contexto de aplicación (configurado enspring-mvc.xml), identifica que la ruta/api/usersmapea al métodolistUsers()de un controlador específico. - El
DispatcherServletdespacha la ejecución hacia el métodolistUsers()delUserController. - El framework invoca el método del controlador, el cual retorna un objeto
ModelAndView. Finalmente, este resultado se resuelve y se redirige o reenvía a la vista correspondiente, comouserList.jsp.
Análisis del Código Fuente y Ciclo de Vida del Servlet
Para comprender cómo Spring MVC inicializa y procesa las peticiones a bajo nivel, es necesario examinra el ciclo de vida del DispatcherServlet.
Inicialización del Contenedor
Cuando el servidor de aplicaciones arranca, el DispatcherServlet se instancia gracias a la directiva <load-on-startup>. Al heredar de HttpServlet, su método init() es envocado para preparar el contexto de Spring.
// Inicialización del contexto de Spring dentro del DispatcherServlet
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
// Carga el archivo de configuración XML para crear el contexto de la aplicación
WebApplicationContext mvcContext = new ClassPathXmlApplicationContext("spring-mvc.xml");
// Almacena el contexto en el ServletContext para que esté disponible globalmente
getServletContext().setAttribute("MVC_CONTEXT_ATTRIBUTE", mvcContext);
}
El propósito de esta inicialización es instanciar las clases anotadas con @Controller. Por ejemplo, se crea una instancia de UserController y se registra en el contenedor de IoC, funcionando internamente como un mapa: beanFactory.put("userController", userControllerInstance).
Procesamiento de la Solicitud
Cuando llega una petición, el contenedor invoca el método service() del servlet, que a su vez llama a doGet() o doPost(), los cuales delegan la lógica a doService(). El núcleo de Spring MVC reside en el método doDispatch():
// Flujo interno de despacho de peticiones
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 1. Determina el controlador adecuado para la petición actual
// 2. Invoca el método handler (ej. UserController.listUsers())
// 3. Maneja el ModelAndView resultante y resuelve la vista
}
El método doDispatch() es el punto central donde todas las peticiones HTTP son orquestadas, manejando el mapeo de URLs, la ejecución de interceptores y la invocación de los controladores.
Configuración de Escaneo de Anotaciones y Recursos Estáticos
En el archivo de configuración spring-mvc-servlet.xml, es fundamental habilitar el escaneo de componentes y el soporte para anotaciones.
<!-- Habilita el escaneo automático de componentes en el paquete especificado -->
<context:component-scan base-package="com.example.web.controllers"/>
<!-- Permite que el servlet por defecto del contenedor maneje recursos estáticos (CSS, JS, imágenes) -->
<mvc:default-servlet-handler/>
<!-- Activa el soporte para anotaciones de Spring MVC como @RequestMapping, @ResponseBody, etc. -->
<mvc:annotation-driven />
La etiqueta <mvc:annotation-driven /> es crucial. Registra automáticamente los mapeadores de handlers y los adaptadores necesarios para procesar las anotaciones. Sin esta configuración, las peticiones dinámicas no serían enrutadas a los controladores y caerían en el manejador de recursos estáticos por defecto.
Nota: Al utilizar <context:component-scan>, no es necesario incluir <context:annotation-config>, ya que el escaneo de componentes lo incluye implícitamente.
Resolución de Vistas con ViewResolver
El ViewResolver se encarga de traducir el nombre lógico de la vista retornado por el controlador en una ruta física real. La fórmula que utiliza es: prefijo + nombreVista + sufijo.
<!-- Configuración del resolutor de vistas para JSP -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- Directorio base donde se almacenan las vistas -->
<property name="prefix" value="/WEB-INF/pages/"/>
<!-- Extensión de los archivos de vista -->
<property name="suffix" value=".jsp"/>
</bean>
Con esta configuración, si un controlador retorna el nombre lógico "userList", el InternalResourceViewResolver lo transformará en la ruta física /WEB-INF/pages/userList.jsp.
Configuración Integral del Servlet
A continuación, se muestra la estructura base y consolidada para el archivo spring-mvc-servlet.xml:
<?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"
xmlns:mvc="http://www.springframework.org/schema/mvc"
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.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- Escaneo de controladores -->
<context:component-scan base-package="com.example.web.controllers"/>
<!-- Manejo de recursos estáticos -->
<mvc:default-servlet-handler />
<!-- Soporte para anotaciones MVC -->
<mvc:annotation-driven />
<!-- Resolutor de vistas JSP -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>