NOTE
Spring MVC Source Analysis
Breakpoint call stack, servlet class relationships, DispatcherServlet loading, request dispatch, HandlerMapping, HandlerAdapter, handler invocation, and view rendering.
This is a historical learning note and may contain outdated or incomplete understanding.
1. Set a Breakpoint
We set a breakpoint in com.zsk.controller.TestController#test and inspect the call stack as follows:
test:17, TestController (com.zsk.controller)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
doInvoke:190, InvocableHandlerMethod (org.springframework.web.method.support)
invokeForRequest:138, InvocableHandlerMethod (org.springframework.web.method.support)
invokeAndHandle:104, ServletInvocableHandlerMethod (org.springframework.web.servlet.mvc.method.annotation)
invokeHandlerMethod:892, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handleInternal:797, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handle:87, AbstractHandlerMethodAdapter (org.springframework.web.servlet.mvc.method)
doDispatch:1039, DispatcherServlet (org.springframework.web.servlet)
doService:942, DispatcherServlet (org.springframework.web.servlet)
processRequest:1005, FrameworkServlet (org.springframework.web.servlet)
doGet:897, FrameworkServlet (org.springframework.web.servlet)
service:634, HttpServlet (javax.servlet.http)
service:882, FrameworkServlet (org.springframework.web.servlet)
service:741, HttpServlet (javax.servlet.http)
internalDoFilter:231, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
doFilter:52, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
invoke:199, StandardWrapperValve (org.apache.catalina.core)
invoke:96, StandardContextValve (org.apache.catalina.core)
invoke:528, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:139, StandardHostValve (org.apache.catalina.core)
invoke:81, ErrorReportValve (org.apache.catalina.valves)
invoke:678, AbstractAccessLogValve (org.apache.catalina.valves)
invoke:87, StandardEngineValve (org.apache.catalina.core)
service:343, CoyoteAdapter (org.apache.catalina.connector)
service:609, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:810, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1506, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:748, Thread (java.lang)
From the middle part of the call stack, we can see that it mainly passes through methods in HttpServlet, FrameworkServlet, and DispatcherServlet. We first clarify the relationship between these classes.
2. Servlet Class Diagram

The diagram shows that these classes are all servlets. Recall the servlet lifecycle in Servlet.md.
Therefore, we begin tracing the source from HttpServlet.service.
3. How Is DispatcherServlet Loaded?
- If it is configured in
web.xml:
This is configured by ourselves in web.xml. When Tomcat starts, it loads the Servlet with <load-on-startup>1</load-on-startup> and passes in the parameters from <init-param></init-param>.
- If it is configured using the Servlet 3.0 approach:
4. Which Method Is the Key One?
4.1. HttpServlet service
@Override
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException
{
HttpServletRequest request;
HttpServletResponse response;
if (!(req instanceof HttpServletRequest &&
res instanceof HttpServletResponse)) {
throw new ServletException("non-HTTP request or response");
}
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
// Only wrap the request and response objects as HttpServletRequest and HttpServletResponse
// Call the subclass method
// org.springframework.web.servlet.FrameworkServlet#service
service(request, response);
}
4.2. FrameworkServlet service
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
processRequest(request, response);
}
else {
// Call another service method of the parent class
// javax.servlet.http.HttpServlet#service(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
super.service(request, response);
}
}
4.3. Another HttpServlet service
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String method = req.getMethod();
// Call the doXXX method according to the HTTP method
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
// GET method
// org.springframework.web.servlet.FrameworkServlet#doGet
doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
if (ifModifiedSince < lastModified) {
// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
//
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
//
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
4.4. FrameworkServlet doGet
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// Call FrameworkServlet#processRequest
processRequest(request, response);
}
4.5. FrameworkServlet processRequest
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//...
try {
// Call DispatcherServlet#doService
doService(request, response);
}
catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
logResult(request, response, failureCause, asyncManager);
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
4.6. DispatcherServlet doService
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
//...
try {
// Call DispatcherServlet#doDispatch
doDispatch(request, response);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
4.7. DispatcherServlet doDispatch [Key]
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
// Process requests containing file binary streams
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 1. Obtain the corresponding HandlerExecutionChain [including handler and interceptor] to process the request
// handler is our own processing class
// interceptor is the interceptor
mappedHandler = getHandler(processedRequest);
// If no handler is found, print not found in the console and go to 404
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 2. Wrap the Handler as a HandlerAdapter [the adapter adapts different handlers,
// such as @Controller, implements Controller, implements RequestHandler, etc.]
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 3. Call interceptor.preHandle
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 4. Actually call the handler method
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
// 5. Call interceptor.postHandle
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 6. Render the view
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
5. How Is the Handler Found?
5.1. Try All HandlerMappings One by One
getHandler
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// Find a suitable HandlerExecutionChain [including handler and interceptor]
if (this.handlerMappings != null) {
// Different Handlers are processed by different HandlerMappings
// XML configuration or implements Controller -> BeanNameUrlHandlerMapping
// @Controller annotation -> RequestMappingHandlerMapping
// Static-resource mapping with <mvc:default-servlet-handler/> enabled -> SimpleUrlHandlerMapping
// HandlerMappings have already been defined in:
// <USER_HOME>/.m2/repository/org/springframework/spring-webmvc/5.1.8.RELEASE/spring-webmvc-5.1.8.RELEASE.jar!/org/springframework/web/servlet/DispatcherServlet.properties
for (HandlerMappig mapping : this.handlerMappings) {
// Return as soon as one capable of handling the request is found
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
5.1.1. HandlerMapping Types
5.1.1.1. XML configuration or implements Controller uses BeanNameUrlHandlerMapping
5.1.1.2. @Controller annotation configuration uses RequestMappingHandlerMapping
5.1.1.3. Static-resource mapping with mvc:default-servlet-handler/ enabled uses SimpleUrlHandlerMapping
5.1.2. Obtain the Handler and Interceptor Chain Through HandlerMapping
AbstractHandlerMapping#getHandler
@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = getHandlerInternal(request);
// Try static resources
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
// Add interceptors
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (logger.isTraceEnabled()) {
logger.trace("Mapped to " + handler);
}
else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
logger.debug("Mapped to " + executionChain.getHandler());
}
if (CorsUtils.isCorsRequest(request)) {
CorsConfiguration globalConfig = this.corsConfigurationSource.getCorsConfiguration(request);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
5.1.2.1. Obtain the Handler Corresponding to the URL
AbstractHandlerMethodMapping#getHandlerInternal
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
// Obtain the URL path
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
this.mappingRegistry.acquireReadLock();
try {
// Look it up in the map by URL path
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
6. How Does the Handler Adapter Adapt?
6.1. Try All HandlerAdapters One by One
getHandlerAdapter
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
// @Controller annotation -> RequestMappingHandlerMappingAdapter [AbstractHandlerMethodAdapter]
// Static resources or implements Controller -> SimpleControllerHandlerAdapter
// implements HttpRequestHandler -> HttpRequestHandlerAdapter
// Servlet -> SimpleServletHandlerAdapter
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
// Whether the adapter matches the current handler
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
6.1.1. HandlerAdapter Types
6.1.1.1. Static Resources or implements Controller Uses SimpleControllerHandlerAdapter
SimpleControllerHandlerAdapter#supports
public boolean supports(Object handler) {
return (handler instanceof Controller);
}
6.1.1.2. implements HttpRequestHandler Uses HttpRequestHandlerAdapter
HttpRequestHandlerAdapter#supports
public boolean supports(Object handler) {
return (handler instanceof HttpRequestHandler);
}
6.1.1.3. Servlet Uses SimpleServletHandlerAdapter
SimpleServletHandlerAdapter#supports
public boolean supports(Object handler) {
return (handler instanceof Servlet);
}
6.1.1.4. @Controller Annotation Uses RequestMappingHandlerMappingAdapter
AbstractHandlerMethodAdapter#supports
public final boolean supports(Object handler) {
return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}
7. How Is the Handler Method Called?
7.1. If It Implements Controller, Cast and Call It Directly
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// Cast and call directly
return ((Controller) handler).handleRequest(request, response);
}
7.2. Reflective Invocation for @Controller
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
// This one
mav = invokeHandlerMethod(request, response, handlerMethod);
return mav;
}
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
// This one
invocableMethod.invokeAndHandle(webRequest, mavContainer);
}
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// This one
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
}
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// Resolve invocation arguments
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
// Invoke through reflection
return doInvoke(args);
}
8. How Is the View Rendered?
processDispatchResult
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
// Render the ModelAndView returned by the handler
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
8.1. Obtain the Appropriate View and Render It
DispatcherServlet#render
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response.
Locale locale =
(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
response.setLocale(locale);
View view;
String viewName = mv.getViewName();
if (viewName != null) {
// We need to resolve the view name.
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
"' in servlet with name '" + getServletName() + "'");
}
}
else {
// No need to lookup: the ModelAndView object contains the actual View object.
// Obtain the corresponding view using the view name and ViewResolver
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
"View object in servlet with name '" + getServletName() + "'");
}
}
// Delegate to the View object for rendering.
if (logger.isTraceEnabled()) {
logger.trace("Rendering view [" + view + "] ");
}
try {
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
}
// Render using the view
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "]", ex);
}
throw ex;
}
}
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
Locale locale, HttpServletRequest request) throws Exception {
if (this.viewResolvers != null) {
// Obtain it through the view resolvers
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
}
return null;
}
Discussion
Sign in with GitHub to comment. Discussions are stored as GitHub Issues.View on GitHub