Spring MVC DispatcherServlet Internals: Initialization, Request Handling, and View Rendering

DispatcherServlet Initialization

The front controller, responsible for accepting client requests and dispatching them to the appropriate handler. As the entry point of the request processing flow, it implements the Servlet interface. It is essentially a Servlet.

DispatcherServlet class hierarchy

During servlet initialization, the init method is called. Tracing up the parent class hierarchy, the init method of HttpServletBean is invoked, which performs some bean parameter configuration and then calls initServletBean().

init() method call in HttpServletBean

Here a Spring container is instantiated: the WebApplicationContext attribute of FrameworkServlet (the parent class of DispatcherServlet). This means that inside DispatcherServlet there is a Spring container that manages beans used by DispatcherServlet.

During debugging, when Tomcat starts, it calls init, which invokes initServletBean() to create the Spring container and initialize the servlet.

breakpoint in initServletBean

creation of WebApplicationContext

The environment used for debugging is Spring Boot.

The container first collects initialization parameters (such as <init-param>) for the current Servlet defined in web.xml (or via annotations) and packages them as a ServletConfig object. Spring Boot configures a default DispatcherServlet using its own implementation classes.

ServletConfig and default DispatcherServlet

The annotation-based Spring container AnnotationConfigServletWebServerApplicationContext is obtained, which contains the bean factory and the user-defined beans.

For external access, DispatcherServlet can call these beans.

AnnotationConfigServletWebServerApplicationContext details

If annotations are not used, the XML-based Spring container implementation XmlWebApplicationContext is created.

XmlWebApplicationContext fallback

private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;
public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class
// The default can be overridden via XML or annotations to switch the Spring container implementation.

After the container is created, onRefresh is called.

onRefresh method invocation

And onRefresh calls initStrategies, which initializes HandlerMappings, HandlerAdapters, etc.

initStrategies call

For example, initHandlerMappings looks for HandlerMapping beans in the Spring container, sorts them if there are multiple, and if no handler mappings are found, it loads some default ones.

private void initHandlerMappings(ApplicationContext context) {
    this.handlerMappings = null;

    if (this.detectAllHandlerMappings) {
        // Obtain all HandlerMappings from the ApplicationContext, including ancestor contexts.
        Map<String, HandlerMapping> foundMappings =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
        if (!foundMappings.isEmpty()) {
            this.handlerMappings = new ArrayList<>(foundMappings.values());
            // Preserve HandlerMappings in sorted order.
            AnnotationAwareOrderComparator.sort(this.handlerMappings);
        }
    } else {
        try {
            HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
            this.handlerMappings = Collections.singletonList(hm);
        } catch (NoSuchBeanDefinitionException ex) {
            // Ignore; a default HandlerMapping will be added later.
        }
    }

    // Guarantee at least one HandlerMapping by registering a default one if none are found.
    if (this.handlerMappings == null) {
        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
        if (logger.isTraceEnabled()) {
            logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
                    "': using default strategies from DispatcherServlet.properties");
        }
    }

    for (HandlerMapping mapping : this.handlerMappings) {
        if (mapping.usesPathPatterns()) {
            this.parseRequestPath = true;
            break;
        }
    }
}

getDefaultStrategies looks up resource files and loads the default configuration via reflection.

getDefaultStrategies method

DispatcherServlet.properties

It calls createDefaultStrategy to instantiate beans using a factory approach.

createDefaultStrategy

protected Object createDefaultStrategy(ApplicationContext context, Class<?> clazz) {
    return context.getAutowireCapableBeanFactory().createBean(clazz);
}

For instance, RequestMappingHandlerMapping implements InitializingBean. When the bean is created via the factory, its initialization operations are triggered, followed by the parent afterPropertiesSet().

RequestMappingHandlerMapping afterPropertiesSet

@Override
public void afterPropertiesSet() {
    initHandlerMethods();
}

initHandlerMethods retrieves all bean names and calls processCandidateBean for each.

protected void initHandlerMethods() {
    for (String beanName : getCandidateBeanNames()) {
        if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
            processCandidateBean(beanName);
        }
    }
    handlerMethodsInitialized(getHandlerMethods());
}

Inside, it checks whether the bean is a controller or contains @RequestMapping.

checking for controller annotation

If so, it enters detectHandlerMethods, which further inspects all methods in the controller that carry @RequestMapping and wraps them into RequestMappingInfo (including parameters, method names, etc.).

detectHandlerMethods

These are then registered in the mappingRegistry. This is the underlying principle behind Spring controller memory webshells: when writing a malicious controller, the crafted controller is placed into the mappingRegistry.

register method

protected void registerHandlerMethod(Object handler, Method method, T mapping) {
    this.mappingRegistry.register(mapping, handler, method);
}

The initialization of RequestMappingHandlerAdapter follows the same pattern. This class primarily handles requests that target a method annotated with @RequestMapping.

Other adapters, such as HttpRequestHandlerAdapter, handle beans that implement HttpRequestHandler (which can also act as controllers).

HttpRequestHandlerAdapter

The supports method determines whether a given handler object is supported.

supports check

initControllerAdviceCache finds classes annotated with @ControllerAdvice, which is commonly used for exception handlers.

initControllerAdviceCache

It then looks for methods annotated with specific annotations (such as @ModelAttribute, @InitBinder) and stores them for later use.

Set<Method> modelAttributeMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);
public static final MethodFilter MODEL_ATTRIBUTE_METHODS = method ->
        (!AnnotatedElementUtils.hasAnnotation(method, RequestMapping.class) &&
                AnnotatedElementUtils.hasAnnotation(method, ModelAttribute.class));
Set<Method> initBinderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);
public static final MethodFilter INIT_BINDER_METHODS = method ->
        AnnotatedElementUtils.hasAnnotation(method, InitBinder.class);

How the DispatcherServlet is Created

This section describes the process in forward order; one can simply set breakpoints and debug in reverse.

How does Tomcat discover and load Spring?

Spring implements Tomcat’s ServletContainerInitializer through its SpringServletContainerInitializer. Tomcat uses the SPI (Service Provider Interface) mechanism to load Spring’s initializer, establishing the connection between Tomcat and Spring.

SPI is a built-in service discovery mechanism in the JDK. It works by looking for files in the META-INF/services directory on the classpath and automatically loading the classes defined there. (Referenced in a previous SnakeYAML article about a gadget chain.)

SpringServletContainerInitializer and SPI

Based on the annotation @HandlesTypes(WebApplicationInitializer.class), it finds all implementations of WebApplicationInitializer and invokes their onStartup methods.

onStartup invocation

Therefore, we can write our own WebApplicationInitializer implementation to configure the DispatcherServlet. Spring Boot already provides SpringBootServletInitializer, so explicit Spring MVC configuration is unnecessary.

SpringBootServletInitializer

You can simplify configuration by extending SpringBootServletInitializer and overriding configure:

public class CustomServletInitializer extends SpringBootServletInitializer {
    @Override 
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(MyApplication.class) 
                     .properties("spring.mvc.throw-exception-if-no-handler-found=true"); 
    }
}

DispatcherServlet Request Processing

In Spring Boot, the default DispatcherServlet mapping path is /, which can be adjusted via server.servlet.context-path.

default servlet mapping

Example controller:

@Controller
public class GreetingController {

    @GetMapping("/greet")
    @ResponseBody
    public String sayHello(String username){
        return "Hello " + username;
    }
}

When a request reaches the path mapped to the DispatcherServlet (e.g., /), Tomcat invokes the corresponding service method. This calls FrameworkServlet#service, which parses the HTTP method and then delegates to the parent class service.

FrameworkServlet service method

This ends up calling HttpServlet's (the Tomcat Servlet specification) doGet (or doPost, etc.).

HttpServlet doGet

FrameworkServlet#doGet is then executed.

FrameworkServlet doGet

The core operation is the call to doService.

doService

Within doService, the central dispatching method doDispatch is envoked.

doDispatch

First, getHandler is called to obtain the handler matching the request.

getHandler

getHandler iterates through each HandlerMapping. The primary one used here is RequestMappingHandlerMapping, so only its key logic is described. Other HandlerMapping implementations, such as BeanNameUrlHandlerMapping, handle beans that implement Controller and whose names start with "/".

It uses getPathWithinApplication to extract the request path, handling differences between the servlet mapping and the URI. This is where path normalization differences between Spring and security frameworks like Shiro can lead to authorization bypasses.

path extraction

path handling

lookupHandlerMethod

Initially, it tries to directly obtain a RequestMappingInfo by the path. If not found, it iterates through all mappings to find a match or selects the best one from multiple candidates. Here, because the path /greet exists, it is retrieved immediately.

direct lookup

getMatchingMappings wraps the information into a RequestMappingInfo and retrieves the corresponding handler method from the mappingRegistry.

getMatchingMappings

mappingRegistry lookup

After the handler is obtained, an execution chain is created, consisting of interceptors and the handler itself.

handler execution chain

Next, the appropriate HandlerAdapter is determined based on the HandlerMethod.

getHandlerAdapter

It loops through the available adapters, calling supports on each to find the matching adapter. The adapter then invokes the actual handler method based on the handler’s type.

selecting handler adapter

applyPreHandle is called.

applyPreHandle

All interceptors’ preHandle methods are executed; if any returns false, the chain is interrupted.

preHandle execution

The adapter’s handle method is then called, which invokes the actual handler method and returns a ModelAndView object.

Inside handleInternal, invokeHandlerMethod is called.

invokeHandlerMethod

getDataBinderFactory finds all @InitBinder annotated methods (for parameter binding and conversion) and creates a ModelFactory to generate the model (encluding processing of @ModelAttribute). Parameter and return-value resolvers are then set up, and a ModelAndView container is created and initialized.

setting resolvers

invokeAndHandle is invoked, which obtains arguments, resolves them, calls the actual method, and then processes the return value using a return-value handler.

invokeAndHandle

In invokeAndHandle, getArgumentResolver is called, which iterates through the argument resolvers to find one that supports the parameter.

getArgumentResolver

Some resolvers handle annotated parameters:

annotated resolvers

The resolver is then used to resolve the argument.

argument resolution

This step retrieves the actual parameter name.

parameter name resolution

resolveName fetches the value; if it is null, a default value is looked up. If a type converter is configured (e.g., String to Date), it is applied, along with handling of specific annotations. Finally, doInvoke uses reflection to execute the original method.

doInvoke

Now the return-value processing logic kicks in.

Either the response is written directly to the browser or a view is rendered.

returnValueHandlers have been initialized with a set of return-value handlers. handleReturnValue is called.

returnValueHandlers

handleReturnValue invokes selectHandler to pick the appropriate return-value resolver.

selectHandler

If the method has a @ResponseBody annotation, the corresponding resolver is selected.

ResponseBody selection

RequestResponseBodyMethodProcessor processes the return value by calling handleReturnValue, which delegates to writeWithMessageConverters.

writeWithMessageConverters

First, it checks whether the return type is a String and sets the value type and parameter type.

value type check

It then loops through the message converters. For String return types, StringHttpMessageConverter is chosen. Next, getAdvice().beforeBodyWrite is called. If any @ControllerAdvice or similar advice is registered, its beforeBodyWrite runs before the body is written.

beforeBodyWrite

advice processing

This converter supports plain text and any type. In the canWrite check shown earlier, both the value type and the selected media type (acceptable to the browser) are supported.

canWrite logic

Finally, the response is written directly.

direct write

Many other return-value handlers exist, such as ViewNameMethodReturnValueHandler, which determines whether a redirect is needed and stores the view name in the ModelAndView container for later resolution.

ViewNameMethodReturnValueHandler

DispatcherServlet View Rendering

After invokeAndHandle processes the return value, getModelAndView is called to obtain the ModelAndView.

getModelAndView

It first calls updateModel, placing attributes annotated with @SessionAttributes into the model, sets the view name, and creates the ModelAndView to return.

updateModel

If the method had @ResponseBody, the requestHandled flag was already set to true during return-value processing, so no view rendering occurs.

requestHandled flag

Next, processDispatchResult is called for rendering.

processDispatchResult

It calls render, which uses the configured view resolvers to resolve the view name.

render method

(The debugger started having issues here; the rest is based on code reading.)

The core handling in the demo is the creation of the view. After handling redirect and forward scenarios, it enters the parent class’s createView.

createView

Eventually, UrlBasedViewResolver#buildView creates the view and sets the URL. In this case no prefix or suffix was configured, so the view’s URL is just index.

buildView

The view.render method searches for the resource. The key method is:

render method internals

exposeModelAsRequestAttributes iterates through the model and sets attributes in the request so they can be used after forwarding.

exposeModelAsRequestAttributes

Then a function is called to locate the path, which becomes the previously set index.

path resolution

A RequestDispatcher (Tomcat’s internal forwarder) is created using this path: DispatcherServlet root + index → http://..../app/index, and the request is forwarded. Obviously this path cannot be found unless a static resource or a JSP exists.

RequestDispatcher

One can locate an index.jsp file or use forward:/index.jsp.

Spring MVC can find JSP files by adding a prefix and suffix to the view name:

@EnableWebMvc
public class MvcConfiguration implements WebMvcConfigurer {
    @Bean
    public InternalResourceViewResolver jspViewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/jsp/");
        resolver.setSuffix(".jsp");
        return resolver;
    }
}

Tags: Spring MVC DispatcherServlet servlet Tomcat WebApplicationContext

Posted on Tue, 27 Oct 2026 16:11:48 +0000 by Talon