En el framework Spring MVC, el componente central para el manejo de solicitudes HTTP es el DispatcherServlet. Su misión principal consiste en enrutar cada petición entrante hacia el controlador aproipado para su procesamiento. El proceso de arranque se desencadena mediante el método init() definido en la clase base HttpServlet.
1. Punto de partida: Método init() de HttpServletBean
La jerarquía de clases de DispatcherServlet comienza con HttpServletBean. Su implmeentación del método init() establece los pilares de la configuración inicial.
@Override
public final void init() throws ServletException {
// Extrae parámetros de inicialización del Servlet y los mapea a propiedades del bean
PropertyValues servletParams = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
BeanWrapper beanAccessor = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader loader = new ServletContextResourceLoader(getServletContext());
beanAccessor.registerCustomEditor(Resource.class, new ResourceEditor(loader, getEnvironment()));
initBeanWrapper(beanAccessor);
beanAccessor.setPropertyValues(servletParams, true);
// Delega la inicialización especializada a la subclase
initServletBean();
}
ServletConfigPropertyValues interpreta las directivas del web.xml, como contextConfigLocation. BeanWrapper inyecta estas configuraciones en los campos del DispatcherServlet. Finalmente, se invoca initServletBean(), que es implementado por FrameworkServlet.
2. Creación del contexto de aplicación web
Dentro de FrameworkServlet, el método initServletBean() se encarga de generar el WebApplicationContext, el contenedor IoC específico para aplicaciones web.
@Override
protected final void initServletBean() throws ServletException {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
La lógica principal reside en initWebApplicationContext(), que sigue una secuencia para determinar el contexto a utilizar.
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext parentContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext context = null;
if (this.webApplicationContext != null) {
context = this.webApplicationContext;
if (context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) context;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
cwac.setParent(parentContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (context == null) {
context = findWebApplicationContext();
}
if (context == null) {
context = createWebApplicationContext(parentContext);
}
if (!this.refreshEventReceived) {
onRefresh(context);
}
return context;
}
parentContext es el contexto raíz, típicamente cargado por el ContextLoaderListener. Si no se ha proporcionado un contexto explícitamente, se intenta localizar uno en el ServletContext. En caso de no hallarlo, createWebApplicationContext() instancia uno nuevo (por ejemplo, XmlWebApplicationContext). La culminación de este proceso es la invocación de onRefresh(), que dispara la configuración de los componentes específicos de Spring MVC.
3. Inicialización de los componentes estratégicos
La funcionalidad del DispatcherServlet depende de un conjunto de objetos especializados, tales como HandlerMapping, HandlerAdapter y ViewResolver. Su inicialización ocurre dentro de initStrategies().
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
3.1 Localización de los HandlerMapping
Los HandlerMapping asocian solicitudes entrantes con controladores. Su detección puede ser global o específica.
private void initHandlerMappings(ApplicationContext ctx) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
Map<String, HandlerMapping> mappings = BeanFactoryUtils.beansOfTypeIncludingAncestors(
ctx, HandlerMapping.class, true, false);
if (!mappings.isEmpty()) {
this.handlerMappings = new ArrayList<>(mappings.values());
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
} else {
try {
HandlerMapping singleMapping = ctx.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(singleMapping);
} catch (NoSuchBeanDefinitionException ex) {
// Se procederá a usar la estrategia por defecto
}
}
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(ctx, HandlerMapping.class);
}
}
La configuración predeterminada (cuando no se halla ningún bean) se define en el archivo DispatcherServlet.properties e incluye BeanNameUrlHandlerMapping y RequestMappingHandlerMapping.
3.2 Configuración de los HandlerAdapter
Los HandlerAdapter son responsables de invocar la lógica del controlador.
private void initHandlerAdapters(ApplicationContext ctx) {
this.handlerAdapters = null;
if (this.detectAllHandlerAdapters) {
Map<String, HandlerAdapter> adapters = BeanFactoryUtils.beansOfTypeIncludingAncestors(
ctx, HandlerAdapter.class, true, false);
if (!adapters.isEmpty()) {
this.handlerAdapters = new ArrayList<>(adapters.values());
AnnotationAwareOrderComparator.sort(this.handlerAdapters);
}
} else {
try {
HandlerAdapter singleAdapter = ctx.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
this.handlerAdapters = Collections.singletonList(singleAdapter);
} catch (NoSuchBeanDefinitionException ex) {
// Se procederá a usar la estrategia por defecto
}
}
if (this.handlerAdapters == null) {
this.handlerAdapters = getDefaultStrategies(ctx, HandlerAdapter.class);
}
}
Los adaptadores por defecto son HttpRequestHandlerAdapter, SimpleControllerHandlerAdapter y RequestMappingHandlerAdapter.
3.3 Registro de los ViewResolver
Los ViewResolver transforman nombres lógicos de vista en objetos de vista concretos.
private void initViewResolvers(ApplicationContext ctx) {
this.viewResolvers = null;
if (this.detectAllViewResolvers) {
Map<String, ViewResolver> resolvers = BeanFactoryUtils.beansOfTypeIncludingAncestors(
ctx, ViewResolver.class, true, false);
if (!resolvers.isEmpty()) {
this.viewResolvers = new ArrayList<>(resolvers.values());
AnnotationAwareOrderComparator.sort(this.viewResolvers);
}
} else {
try {
ViewResolver singleResolver = ctx.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);
this.viewResolvers = Collections.singletonList(singleResolver);
} catch (NoSuchBeanDefinitionException ex) {
// Se procederá a usar la estrategia por defecto
}
}
if (this.viewResolvers == null) {
this.viewResolvers = getDefaultStrategies(ctx, ViewResolver.class);
}
}
El ViewResolver predeterminado es InternalResourceViewResolver, que busca archivos JSP en el directorio /WEB-INF/.
Este proceso secuencial garantiza que el DispatcherServlet disponga de todos los componentes necesarios para gestionar el ciclo de vida completo de una solicitud HTTP, desde su recepción hasta la renderización de la respuesta.