I. Resumen
Spring Web MVC es el marco web original construido sobre la API Servlet, incluido en el marco Spring desde el principio. El nombre oficial "SpringWebMVC" proviene del nombre de su módulo fuente (spring-webmvc), pero su nombre más común es "SpringMVC".
Paralelamente a Spring Web MVC, Spring Framework 5.0 presenta un marco web de pila reactiva cuyo nombre "Spring WebFlux" también se basa en su módulo fuente (Spring-WebFlux).
Dos, DispatcherServlet
Spring MVC, como muchos otros marcos web, está diseñado en torno al patrón Front Controller, donde uno central Servlet
, DispatcherServlet , proporciona un algoritmo compartido para el procesamiento de solicitudes, mientras que el trabajo real lo realizan componentes delegados configurables. Este modo es flexible y admite una variedad de flujos de trabajo.
DispatcherServlet, como cualquier Servlet, debe declararse y asignarse de acuerdo con la especificación de Servlet utilizando la configuración de Java o web.xml. A su vez, DispatcherServlet usa la configuración de Spring para descubrir los componentes de delegado necesarios para el mapeo de solicitudes, la resolución de vistas, el manejo de excepciones, etc.
El siguiente ejemplo de configuración de Java registra e inicializa el DispatcherServlet, que es detectado automáticamente por el contenedor de Servlet
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) {
// Load Spring web application configuration
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(AppConfig.class);
// Create and register the DispatcherServlet
DispatcherServlet servlet = new DispatcherServlet(context);
ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
registration.setLoadOnStartup(1);
registration.addMapping("/app/*");
}
}
1. Jerarquía de contexto
DispatcherServlet
Se espera que tenga una WebApplicationContext
( ApplicationContext
extensión simple) para su propia configuración. WebApplicationContext
Hay un enlace ServletContext
ay relacionado con él Servlet
. También está vinculado para ServletContext
que las aplicaciones puedan usar RequestContextUtils
los métodos estáticos para consultar WebApplicationContext
si necesitan acceder a él.
Para muchas aplicaciones, tener uno solo WebApplicationContext
es simple y suficiente. También es posible tener una jerarquía de contexto en la que varias (u otras ) instancias WebApplicationContext
comparten una sola raíz , cada una con su propia subconfiguración.DispatcherServlet
Servlet
WebApplicationContext
La raíz WebApplicationContext
generalmente contiene beans de infraestructura, como Servlet
repositorios de datos y servicios comerciales que deben compartirse entre varias instancias. Estos beans se heredan de manera efectiva y se pueden anular (es decir, volver a declarar) en Servlet
un hijo específico , que generalmente contiene un bean local determinado. La siguiente imagen muestra esta relación:WebApplicationContext
WebApplicationContext
Servlet
El siguiente ejemplo configura una WebApplicationContext
jerarquía:
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] { RootConfig.class };
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { App1Config.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { "/app1/*" };
}
}
El siguiente ejemplo muestra el equivalente web.xml:
<web-app>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/root-context.xml</param-value>
</context-param>
<servlet>
<servlet-name>app1</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app1-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app1</servlet-name>
<url-pattern>/app1/*</url-pattern>
</servlet-mapping>
</web-app>
2. Tipos especiales de frijoles
DispatcherServlet
Delega a un bean especial para manejar la solicitud y dar la respuesta adecuada. Por "frijoles especiales" nos referimos Object
a instancias administradas por Spring que implementan contratos marco. Estas instancias suelen tener convenciones integradas, pero puede personalizar sus propiedades y ampliarlas o reemplazarlas.
La siguiente tabla enumera DispatcherServlet
las clases especiales de beans detectadas por:
tipo de frijol | ilustrar |
---|---|
|
Asigna una solicitud a un controlador junto con una lista de interceptores para el procesamiento previo y posterior. La asignación se basa en algún estándar, cuyos detalles Las dos implementaciones principales |
|
Ayuda |
VerResolver | Una estrategia para resolver excepciones, posiblemente asignándolas a controladores, vistas de errores HTML u otros objetivos. |
VerResolver | Resuelve el nombre de la vista lógica en función de lo devuelto por el controlador |
LocaleResolver, LocaleContextResolver | Analice la |
Resolución de temas | Resuelve temas que su aplicación web puede usar, por ejemplo, para proporcionar un diseño personalizado. |
MultipartResolver | Una abstracción para analizar una solicitud de varias partes (por ejemplo, la carga de un archivo de formulario del navegador) con la ayuda de alguna biblioteca de análisis de varias partes. |
FlashMapManager | Almacene y recupere "entrada" y "salida" |
3. Configuración MVC de la página web
Las aplicaciones pueden declarar los beans de infraestructura necesarios para procesar las solicitudes enumeradas en los tipos de beans especiales. DispatcherServlet comprueba cada bean especial en WebApplicationContext. Si no hay ningún tipo de bean coincidente, se recurrirá al tipo predeterminado que se muestra en DispatcherServlet.properties. En la mayoría de los casos, la configuración de MVC es el mejor lugar para comenzar. Declara el Bean requerido en Java o XML, y proporciona una API de devolución de llamada (devolución de llamada) de configuración de nivel superior para personalizarlo.
4. Configuración de servlets
En un entorno de Servlet, puede optar por configurar el contenedor de Servlet mediante programación, como una opción o junto web.xml
con la documentación. El siguiente ejemplo registra uno DispatcherServlet
:
import org.springframework.web.WebApplicationInitializer;
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext container) {
XmlWebApplicationContext appContext = new XmlWebApplicationContext();
appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
ServletRegistration.Dynamic registration = container.addServlet("dispatcher", new DispatcherServlet(appContext));
registration.setLoadOnStartup(1);
registration.addMapping("/");
}
}
WebApplicationInitializer
es una interfaz proporcionada por Spring MVC que asegura que su implementación sea detectada y utilizada automáticamente para inicializar cualquier contenedor Servlet 3. WebApplicationInitializer
Una implementación de clase base abstracta llamada , que facilita el registro AbstractDispatcherServletInitializer
al anular métodos para especificar Servlet
asignaciones y DispatcherServlet
ubicaciones de configuración .DispatcherServlet
Esto se recomienda para aplicaciones que utilizan una configuración de Spring basada en Java, como se muestra en el siguiente ejemplo:
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { MyWebConfig.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
Si usa una configuración de Spring basada en XML, debe comenzar directamente desde AbstractDispatcherServletInitializer
la extensión, como se muestra en el siguiente ejemplo:
public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {
@Override
protected WebApplicationContext createRootApplicationContext() {
return null;
}
@Override
protected WebApplicationContext createServletApplicationContext() {
XmlWebApplicationContext cxt = new XmlWebApplicationContext();
cxt.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
return cxt;
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
AbstractDispatcherServletInitializer
También proporciona un método conveniente para agregar Filter
instancias y hacer que se asignen automáticamente a DispatcherServlet
, como muestra el siguiente ejemplo:
public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {
// ...
@Override
protected Filter[] getServletFilters() {
return new Filter[] {
new HiddenHttpMethodFilter(), new CharacterEncodingFilter() };
}
}
Cada filtro se agrega con un nombre predeterminado (nombre) basado en su tipo concreto y se asigna automáticamente a DispatcherServlet
.
AbstractDispatcherServletInitializer
El isAsyncSupported
método protegido proporciona un lugar único para habilitar DispatcherServlet
la compatibilidad asincrónica y todos los filtros asignados a él. De forma predeterminada, esta bandera se establece en true
.
DispatcherServlet
Finalmente, puede anular createDispatcherServlet
los métodos si necesita personalizarse aún más .
5. Proceso
DispatcherServlet
La solicitud se maneja de la siguiente manera:
WebApplicationContext
Los beans declarados en se HandlerExceptionResolver
utilizan para resolver las excepciones lanzadas durante el procesamiento de solicitudes. Estos solucionadores de excepciones permiten una lógica de manejo de excepciones personalizada
WebRequest
Para la compatibilidad con el almacenamiento en caché de HTTP, los métodos están disponibles para los controladores checkNotModified
. Puede personalizar instancias individuales web.xml
agregando parámetros de inicialización de Servlet (elementos) a la declaración de Servlet en el archivo . La siguiente tabla enumera los parámetros admitidos:init-param
DispatcherServlet
-
WebApplicationContext
Se busca y vincula como un atributo en la solicitud, disponible para los controladores y otros elementos en el proceso. Está enlazado bajoDispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE
clave por defecto. -
Un solucionador de configuración regional está vinculado a la solicitud para que los elementos del proceso resuelvan la configuración regional que se usará al procesar la solicitud (presentación de vistas, preparación de datos, etc.). Si no necesita un análisis de configuración regional, no necesita una resolución de configuración regional.
-
Un solucionador de temas está vinculado a la solicitud para permitir que elementos como las vistas decidan qué tema usar. Si no usa un tema, puede ignorarlo.
-
Si especifica un analizador de archivos de varias partes, la solicitud se comprobará como un archivo de varias partes. Si se encuentra una multiparte, la solicitud se envolverá en una
MultipartHttpServletRequest
para que otros elementos del proceso la procesen más. -
Se busca un controlador adecuado. Si se encuentra un controlador, la cadena de ejecución (preprocesador, posprocesador y controlador) asociada con ese controlador se ejecuta para preparar el modelo para la representación. Además, para los controladores anotados, la respuesta se puede representar (dentro
HandlerAdapter
) en lugar de devolver una vista. -
Si se devuelve un modelo, se renderizará la vista. Si no se devuelve ningún modelo (quizás debido a que un preprocesador o posprocesador interceptó la solicitud, quizás por razones de seguridad), la vista no se representará porque es posible que la solicitud ya se haya cumplido.
parámetro | ilustrar |
---|---|
|
Implementar |
|
Una cadena pasada a la instancia de contexto ( |
|
|
|
Si lanzar cuando no se encuentra un controlador para una solicitud De forma predeterminada, se establece en Tenga en cuenta que si también se configura el manejo de servlet predeterminado, las solicitudes no resueltas siempre se reenvían al servlet predeterminado y nunca se producirán errores 404. |
6. Coincidencia de ruta
La API de servlet expone la ruta de solicitud completa como requestURI
, y la divide en contextPath
, servletPath
y pathInfo
, cuyos valores varían según cómo se mapee el servlet. A partir de estas entradas, Spring MVC necesita determinar la ruta de búsqueda que se usará para el controlador asignado, excluyendo contextPath
los servletMapping
prefijos, si corresponde.
servletPath
y pathInfo
están decodificados, lo que hace imposible compararlos requestURI
directamente con los completos lookupPath
, lo que hace necesario requestURI
decodificar los . Sin embargo, esto presenta sus propios problemas, ya que la ruta puede contener caracteres reservados codificados como "/"
o ";"
, que a su vez cambian la estructura de la ruta después de decodificarse, lo que también puede generar problemas de seguridad. servletPath
Además, los contenedores de Servlet pueden normalizarse en diferentes grados , lo que hace que sea aún más imposible requestURI
comparar startsWith
.
Esta es la razón por la que es mejor evitar confiar en el servletPath que viene con el tipo de mapeo servletPath basado en prefijos. Si DispatcherServlet se asigna como el Servlet predeterminado con el prefijo "/" o sin "/*", y el contenedor de Servlet está por encima de 4.0, Spring MVC puede detectar el tipo de asignación de Servlet y evitar por completo el uso de servletPath y pathInfo. En el contenedor de Servlet 3.1, asumiendo el mismo tipo de mapeo de Servlet, se puede realizar proporcionando UrlPathHelper con alwaysUseFullPath=true a través de la coincidencia de ruta (Path) en la configuración de MVC.
Afortunadamente, el mapeo de Servlet predeterminado "/"
es una buena opción. Sin embargo, todavía hay un problema que requestURI
debe decodificarse para poder comparar con el mapeo del controlador. Esto tampoco es deseable, ya que es posible decodificar caracteres reservados que cambian la estructura de la ruta. Si no se esperan estos caracteres, puede negarlos (como Spring Security HTTP Firewall), o puede configurarlos UrlPathHelper
, urlDecode=false
pero la asignación del controlador debe coincidir con la ruta codificada, lo que puede no ser siempre agradable. Además, a veces DispatcherServlet
es necesario compartir el espacio de la URL con otro servlet, lo que puede requerir un mapeo por prefijo.
El problema anterior se resuelve al usar PathPatternParser
y analizar el patrón, ya que se puede AntPathMatcher
comparar con una ruta de cadena alternativa. PathPatternParser
Disponible en Spring MVC desde la versión 5.3 y habilitado de forma predeterminada desde la versión 6.0. AntPathMatcher
En lugar de decodificar la ruta de búsqueda o codificar el mapa del controlador, el analizado se compara con la representación analizada de la ruta llamada, un segmento de ruta a la vez PathPattern
. RequestPath
Esto permite que los valores de los segmentos de la ruta se decodifiquen y desinfecten individualmente sin el riesgo de alterar la estructura de la ruta. Parsed PathPattern
también admite el uso de servletPath
asignaciones de prefijos, siempre que se utilicen asignaciones de rutas de Servlet y los prefijos se mantengan simples, es decir, sin caracteres codificados.
7. Interceptación
Todas las implementaciones de HandlerMapping admiten interceptores de controladores, que son útiles cuando desea aplicar una funcionalidad específica a ciertas solicitudes, por ejemplo, verificar un principal. El interceptor debe implementar el HandlerInterceptor en el paquete org.springframework.web.servlet, que tiene tres métodos que deberían proporcionar suficiente flexibilidad para varios preprocesamientos y posprocesamientos:
- preHandle(..): antes de que se ejecute el controlador real
- postHandle (..): después de que se ejecuta el controlador
- afterCompletion(..): después de completar toda la solicitud
El método preHandle(..) devuelve un valor booleano. Puede utilizar este método para interrumpir o continuar el procesamiento de la cadena de ejecución. Cuando este método devuelve verdadero, la cadena de ejecución del controlador continúa. Cuando devuelve falso, DispatcherServlet considera que el propio interceptor ha procesado la solicitud (por ejemplo, presentando una vista adecuada) y no procede a ejecutar otros interceptores y controladores reales en la cadena de ejecución.
Para ver un ejemplo de cómo configurar interceptores, consulte Interceptores en la sección de configuración de MVC. También puede registrar implementaciones individuales de HandlerMapping directamente utilizando sus configuradores.
El método postHandle es menos útil en los métodos @ResponseBody y ResponseEntity porque las respuestas para esos métodos se escriben y confirman en el HandlerAdapter antes de postHandle. Esto significa que es demasiado tarde para realizar modificaciones en la respuesta, como agregar un encabezado adicional. Para este caso, puede implementar ResponseBodyAdvice y declararlo como Controller Advice Bean, o configurarlo directamente en RequestMappingHandlerAdapter
8. Excepciones
Si se produce una excepción durante la asignación de solicitudes o @Controller
se lanza desde un controlador de solicitudes (p. ej.), DispatcherServlet
el controlador se delega a la HandlerExceptionResolver
cadena de beans ExceptionResolver ( ) para resolver la excepción y proporcionar un manejo alternativo, que suele ser una respuesta de error.
La siguiente tabla enumera las HandlerExceptionResolver
implementaciones disponibles:
HandlerExceptionResolver |
ilustrar |
---|---|
|
Mapeo entre nombres de clase de excepción y nombres de vista de error. Útil para representar páginas de error en aplicaciones de navegador. |
DefaultHandlerExceptionResolver |
Analiza las excepciones lanzadas por Spring MVC y las asigna a códigos de estado HTTP. Consulte también |
|
Analiza |
|
Resolver excepciones llamando |
cadena de resolución
Puede formar una cadena de solucionadores de excepciones declarando varios beans HandlerExceptionResolver en su configuración de Spring y configurando sus propiedades de orden según sea necesario. Cuanto mayor sea el atributo de orden, más tarde se posicionará el analizador de excepciones. La convención de HandlerExceptionResolver, que puede devolver: Un ModelAndView que apunta a la vista de error. Un ModelAndView vacío si la excepción se manejó en el analizador. null si la excepción aún no se ha resuelto, para los intentos posteriores del analizador, para permitir la propagación en el contenedor de servlet si la excepción aún está al final. La configuración de MVC declara automáticamente los solucionadores integrados para las excepciones predeterminadas de Spring MVC, las excepciones anotadas con @ResponseStatus y la compatibilidad con los métodos @ExceptionHandler. Puede personalizar esta lista o reemplazarla
Página de error del contenedor (Página de error)
Si aún no se HandlerExceptionResolver
analiza una excepción y, por lo tanto, se permite que se propague, o si el estado de respuesta se establece en un estado de error (es decir, 4xx, 5xx), el contenedor de servlet puede generar una página de error predeterminada en HTML. Para personalizar las páginas de error predeterminadas del contenedor, puede web.xml
declarar una asignación de página de error en . El siguiente ejemplo muestra cómo hacer esto:
<error-page>
<location>/error</location>
</error-page>
/error
Dado el ejemplo anterior, cuando se genera una excepción o la respuesta tiene un estado de error, el contenedor de servlet envía ERROR a la URL configurada (p. ej., ) dentro del contenedor . Luego se DispatcherServlet
procesa, posiblemente asignándolo a un @Controller
, que se puede implementar para devolver un nombre de vista de error con un modelo o para generar una respuesta JSON, como en el siguiente ejemplo:
@RestController
public class ErrorController {
@RequestMapping(path = "/error")
public Map<String, Object> handle(HttpServletRequest request) {
Map<String, Object> map = new HashMap<>();
map.put("status", request.getAttribute("jakarta.servlet.error.status_code"));
map.put("reason", request.getAttribute("jakarta.servlet.error.message"));
return map;
}
}
9. Ver análisis
Spring MVC define ViewResolver
interfaces View
que le permiten renderizar modelos en el navegador sin estar vinculado a una tecnología de vista específica. ViewResolver
Proporciona una asignación entre los nombres de las vistas y las vistas reales. View
Aborda los problemas de preparación de datos antes de la transferencia a tecnologías de visualización específicas.
La siguiente tabla proporciona ViewResolver
más detalles sobre la jerarquía:
VerResolver | ilustrar |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|