SpringMVC source code-DispatcherServlet

1. SpringMVC request processing flow

  • DispatcherServlet: DispatcherServlet is the front controller in SpringMVC, which is responsible for receiving the request and forwarding the request to the corresponding processing component.
  • HandlerMapping: HanlerMapping is a component in SpringMVC that completes the mapping from url to Controller.
  • Handler: The Handler processor is actually our Controller. It has many specific forms, which can be classes or methods. If you can think of other forms of expression, it can also be used. Its type is Object. All methods with @RequestMapping can be regarded as a Handler. It can be a Handler as long as it can actually handle the request.
  • HandlerAdapter: Handler adapter. Because the Handler in SpingMVC can be in any form, as long as it can process the request, it is OK, but the structure of the processing method required by the Servlet is fixed, and it is a method with request and response as parameters (such as the doService method). How to make the fixed Servlet processing method call the flexible Handler for processing? This is what HandlerAdapter has to do.
  • ModelAndView: ModelAndView is a component that encapsulates the result view.
  • ViewResolver: ViewResolver view resolver, resolves ModelAndView objects, and returns the corresponding view View to the client.

The popular explanation is that Handler is a tool for working, HandlerMapping is used to find the corresponding tool according to the work that needs to be done, and HandlerAdapter is the person who uses the tool to work.

Processing flow:
1. The request enters the DispatcherServlet, and finds the corresponding handler through the request
2. Find the corresponding HandlerAdapter according to the handler
3. HandlerAdapter handles handler
4. Call the processDispatchResult method to process the above processing results (including finding the view rendering output to the user)

The corresponding code is as follows:

1. HandlerExecutionChain mappedHandler = getHandler(processedRequest);
2. HandlerAdapter ha = getHandlerAdapter(mappedHandler. getHandler());
3. ModelAndView mv = ha. handle(processedRequest, response, mappedHandler. getHandler());
4. processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

2. The working mechanism of SpringMVC

Flowchart

Please add picture description
1. When the IOC container is initialized, the corresponding relationship between all urls and controllers will be established and saved in Map.
When tomcat starts, it will notify the spring initialization container (loading bean definition information and initializing all singleton beans),
Then springmvc will traverse the beans in the container, obtain the url accessed by all methods in each controller, and then save the url and controller into a Map;
2. In this way, the controller can be quickly located according to the request, because the final processing of the request is the method in the controller, and only the corresponding relationship between the url and the controller is reserved in the Map, so the method in the controller must be further confirmed according to the url of the request. The url is matched, and the matching method is found;
3. After determining the method for processing the request, the next task is parameter binding, which binds the parameters in the request to the formal parameters of the method. This step is the most complicated step in the entire request processing process. springmvc provides two methods of binding request parameters and method parameters:
① Binding through annotations, @RequestParam
② Bind by parameter name.
Using annotations for binding, as long as we declare @RequestParam(“a”) in front of the method parameter, we can bind the value of parameter a in the request to the parameter of the method. The premise of using the parameter name for binding is that the name of the parameter in the method must be obtained. Java reflection only provides the type of parameter for obtaining the method, and does not provide a method for obtaining the parameter name. The way springmvc solves this problem is to use the asm framework to read the bytecode file to obtain the parameter name of the method. Use annotations to complete parameter binding, so that the operation of reading bytecodes of the asm framework can be omitted.

  1. When the ApplicationContext is initialized, the corresponding relationship between all urls and controller classes is established (saved with Map);
  2. Find the corresponding controller according to the request url, and find the method of processing the request from the controller;
  3. The request parameter is bound to the formal parameter of the method, the execution method processes the request, and returns the result view.

2.1, Create Map

when ApplicationContext is initialized

The entry class of the first part is the setApplicationContext method of ApplicationObjectSupport. The core part of the setApplicationContext method is to initialize the container initApplicationContext (context), and the subclass AbstractDetectingUrlHandlerMapping implements this method, so we directly look at the initialization container method in the subclass.

public abstract class AbstractDetectingUrlHandlerMapping extends AbstractUrlHandlerMapping {<!-- -->

private boolean detectHandlersInAncestorContexts = false;

public void setDetectHandlersInAncestorContexts(boolean detectHandlersInAncestorContexts) {<!-- -->
this. detectHandlersInAncestorContexts = detectHandlersInAncestorContexts;
}

@Override
public void initApplicationContext() throws ApplicationContextException {<!-- -->
super.initApplicationContext();
// detect the handler
detectHandlers();
}

  /**
* Establish the corresponding relationship between all controllers and urls in the current ApplicationContext
*/
protected void detectHandlers() throws BeansException {<!-- -->
ApplicationContext applicationContext = obtainApplicationContext();
// Get the names of all beans in the ApplicationContext container
String[] beanNames = (this. detectHandlersInAncestorContexts?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) :
applicationContext.getBeanNamesForType(Object.class));

// traverse beanNames and find the url corresponding to these beans
for (String beanName : beanNames) {<!-- -->
// Find all Urls on the bean (Url on the Controller + Url on the method), which is implemented by the corresponding subclass
String[] urls = determineUrlsForHandler(beanName);
if (!ObjectUtils.isEmpty(urls)) {<!-- -->
// URL paths found: Let's consider it a handler.
// register handler (emphasis)
// Save the correspondence between url and beanName
// put it to Map<urls, beanName>, this method is implemented in the parent class AbstractUrlHandlerMapping
registerHandler(urls, beanName);
}
}

if (mappingsLogger.isDebugEnabled()) {<!-- -->
mappingsLogger.debug(formatMappingName() + " " + getHandlerMap());
}
else if ((logger.isDebugEnabled() & amp; & amp; !getHandlerMap().isEmpty()) || logger.isTraceEnabled()) {<!-- -->
logger.debug("Detected " + getHandlerMap().size() + "mappings in " + formatMappingName());
}
}


    /** Get the url of all methods in the controller, implemented by subclasses, typical template mode **/
protected abstract String[] determineUrlsForHandler(String beanName);

}

The function of the determineUrlsForHandler(String beanName) method is to obtain the url in each controller. Different subclasses have different implementations. This is a typical template design pattern.

public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping {<!-- -->
// Check the given bean's name and alias, looking for urls starting with "/".
@Override
protected String[] determineUrlsForHandler(String beanName) {<!-- -->
List<String> urls = new ArrayList<>();
if (beanName. startsWith("/")) {<!-- -->
urls. add(beanName);
}
String[] aliases = obtainApplicationContext(). getAliases(beanName);
for (String alias : aliases) {<!-- -->
if (alias. startsWith("/")) {<!-- -->
urls. add(alias);
}
}
return StringUtils.toStringArray(urls);
}

}

At this point, the HandlerMapping component has established the corresponding relationship between all urls and controllers.

2.1. According to the access url, find the method of processing the request in the corresponding controller

DispatcherServlet is the core class of SpringMVC. The entry of the execution method of DispatcherServlet is doService(), but doService() is not directly processed, but is handed over to doDispatch() to implement the core logic

2.1.1 SpringMVC initialization process

Please add picture description

2.1.2 DispatcherServlet entry

Please add picture description

2.1.3 DispatcherServlet process Please add picture description

/** Central controller, control request forwarding **/
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {<!-- -->
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        int interceptorIndex = -1;
 
        try {<!-- -->
            ModelAndView mv;
            boolean errorView = false;
            try {<!-- -->
// 1. Check if it is a file upload request
                processedRequest = checkMultipart(request);
 
                // 2. Obtain the controller that handles the current request, also called hanlder here, the processor, the meaning of the first step is reflected here. This is not to return the controller directly, but to return the HandlerExecutionChain request processor chain object, which encapsulates the handler and interceptors.
                mappedHandler = getHandler(processedRequest, false);
// If the handler is empty, return 404
                if (mappedHandler == null || mappedHandler.getHandler() == null) {<!-- -->
                    noHandlerFound(processedRequest, response);
                    return;
                }
                //3. Get the processor adapter handler adapter that handles the request
                HandlerAdapter ha = getHandlerAdapter(mappedHandler. getHandler());
                // Handle the last-modified request header
                String method = request. getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {<!-- -->
                    long lastModified = ha. getLastModified(request, mappedHandler. getHandler());
                    if (logger.isDebugEnabled()) {<!-- -->
                        String requestUri = urlPathHelper. getRequestUri(request);
                        logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);
                    }
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) & amp; & amp; isGet) {<!-- -->
                        return;
                    }
                }
 
                // 4. The preprocessing method of the interceptor
                HandlerInterceptor[] interceptors = mappedHandler. getInterceptors();
                if (interceptors != null) {<!-- -->
                    for (int i = 0; i < interceptors. length; i ++ ) {<!-- -->
                        HandlerInterceptor interceptor = interceptors[i];
                        if (!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) {<!-- -->
                            triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
                            return;
                        }
                        interceptorIndex = i;
                    }
                }
 
                // 5. The actual processor processes the request and returns the result view object
                mv = ha. handle(processedRequest, response, mappedHandler. getHandler());
 
                // Processing of the result view object
                if (mv != null & amp; & amp; !mv.hasView()) {<!-- -->
                    mv.setViewName(getDefaultViewName(request));
                }
 
                // 6. The post-processing method of the interceptor
                if (interceptors != null) {<!-- -->
                    for (int i = interceptors. length - 1; i >= 0; i--) {<!-- -->
                        HandlerInterceptor interceptor = interceptors[i];
                        interceptor.postHandle(processedRequest, response, mappedHandler.getHandler(), mv);
                    }
                }
            }
            catch (ModelAndViewDefiningException ex) {<!-- -->
                logger.debug("ModelAndViewDefiningException encountered", ex);
                mv = ex. getModelAndView();
            }
            catch (Exception ex) {<!-- -->
                Object handler = (mappedHandler != null ? mappedHandler. getHandler() : null);
                mv = processHandlerException(processedRequest, response, handler, ex);
                errorView = (mv != null);
            }
 
            
            if (mv != null & amp; & amp; !mv. wasCleared()) {<!-- -->
                render(mv, processedRequest, response);
                if (errorView) {<!-- -->
                    WebUtils.clearErrorRequestAttributes(request);
                }
            }
            else {<!-- -->
                if (logger.isDebugEnabled()) {<!-- -->
                    logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
                            "': assuming HandlerAdapter completed request handling");
                }
            }
 
            // The method after the successful response of the request
            triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
        }

Step 2: The getHandler(processedRequest) method is actually to find the corresponding relationship between url and controller from HandlerMapping. This is the first step: the meaning of establishing Map.
We know that the final processing of the request is the method in the controller. We only know the controller now, and we need to further confirm the method of processing the request in the controller.

2.3 Reflection calls the method of processing the request and returns the result view

ha.handle(processedRequest, response, mappedHandler.getHandler());

handle is the actual processor that processes requests to the controller to implement business logic, and returns models and views,
AbstractHandlerMethodAdapter#handle() ==> RequestMappingHandlerAdapter#handleInternal() ==>invokeHandlerMethod()

protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {<!-- -->

ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {<!-- -->
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {<!-- -->
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this. returnValueHandlers != null) {<!-- -->
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);

WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

if (asyncManager.hasConcurrentResult()) {<!-- -->
Object result = asyncManager. getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager. clearConcurrentResult();
LogFormatUtils.traceDebug(logger, traceOn -> {<!-- -->
String formatted = LogFormatUtils. formatValue(result, !traceOn);
return "Resume with async result [" + formatted + "]";
});
invocableMethod = invocableMethod. wrapConcurrentResult(result);
}

invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager. isConcurrentHandlingStarted()) {<!-- -->
return null;
}

return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {<!-- -->
webRequest. requestCompleted();
}
}