Understanding How Spring MVC DispatcherServlet Registers Controllers and Dispatches Requests via HandlerMapping
This article explains, using servlet source code as a starting point, how Spring MVC registers @RequestMapping‑annotated controllers into RequestMappingHandlerMapping during container initialization and how the DispatcherServlet processes incoming HTTP requests by locating the appropriate HandlerMethod through a series of handler mappings and interceptor chains.
The article uses the Servlet API as a entry point to trace how a Spring MVC controller annotated with @RequestMapping is registered into a HandlerMapping and how an incoming request is routed to the corresponding controller method.
During Spring container startup, RequestMappingHandlerMapping implements InitializingBean ; its afterPropertiesSet() method creates a RequestMappingInfo.BuilderConfiguration and then calls the superclass method, which ultimately scans all bean definitions, detects those annotated with @Controller or @RequestMapping , and registers them.
Key servlet interfaces:
public interface Servlet {
// initialization
public void init(ServletConfig config) throws ServletException;
// request handling
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
// other lifecycle methods
public String getServletInfo();
public void destroy();
}Spring’s HttpServletBean overrides init() to set bean properties and then delegates to initServletBean() , which is further specialized by FrameworkServlet to create the web application context.
@Override
public final void init() throws ServletException {
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
initServletBean();
}
protected void initServletBean() throws ServletException { }The DispatcherServlet initializes its strategies in initStrategies() , where the crucial step is initHandlerMappings(context) that discovers all HandlerMapping beans (especially RequestMappingHandlerMapping ).
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
Map
matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
} else {
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
} catch (NoSuchBeanDefinitionException ex) { }
}
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
}
}When a request arrives, DispatcherServlet.doDispatch() obtains the best matching HandlerMapping , retrieves a HandlerExecutionChain , and finally invokes the controller method via the appropriate HandlerAdapter .
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerExecutionChain mappedHandler = getHandler(request);
if (mappedHandler == null) { noHandlerFound(request, response); return; }
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
ModelAndView mv = ha.handle(request, response, mappedHandler.getHandler());
// post‑processing omitted for brevity
}The core lookup happens in AbstractHandlerMethodMapping.getHandlerInternal() , which extracts the lookup path, acquires a read lock on the mappingRegistry , and finds the best HandlerMethod based on URL patterns.
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
this.mappingRegistry.acquireReadLock();
try {
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
} finally {
this.mappingRegistry.releaseReadLock();
}
}After the handler is resolved, AbstractHandlerMapping.getHandlerExecutionChain() builds the interceptor chain by matching MappedInterceptor patterns against the request path.
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mi = (MappedInterceptor) interceptor;
if (mi.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mi.getInterceptor());
}
} else {
chain.addInterceptor(interceptor);
}
}
return chain;
}Finally, the RequestMappingHandlerAdapter invokes the resolved HandlerMethod , completing the request‑processing lifecycle.
The article concludes that the entire request flow—from servlet initialization, through handler registration, to request dispatch—has been traced, laying the groundwork for deeper analysis of HandlerAdapter and HandlerMethod reflection in later chapters.
YunZhu Net Technology Team
Technical practice sharing from the YunZhu Net Technology Team
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.