SpringMVC in the Java backend, is a very important and very common framework, this article to analyze the source code of SpringMVC. The version is 5.2.0.

  • The web.xml configuration

Although SpringMVC doesn’t require us to write servlets, SpringMVC encapsulates servlets and provides dispatcherServlets to help us handle it. So you need to configure the DispatcherServlet in web.xml.

As you can see, the DispatcherServlet maps to a URL of /, so all requests will be intercepted by it and processed to us. Let’s go inside and have a look.

<! DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc., / / DTD Web Application / 2.3 / EN "" http://java.sun.com/dtd/web-app_2_3.dtd" > < Web - app > < the display - the name > Archetype Created  Web Application</display-name> <! <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <! Classpath: SpringMVC. XML </param-value> </init-param> <! <load-on-startup>1</load-on-startup> </ Servlet > <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> // omit other configurations... </web-app>Copy the code

Initialize the

As we know, the init() method of the Servlet is called when the Servlet is initialized. We go into the DispatcherServlet and find that this method is not there, so it must be on its integrated parent class. The DispatcherServlet inherits from the FrameworkServlet class.

HttpServletBean

Finally, HttpServletBean inherits from HttpServlet. Let’s look at the init() method.

@Override public final void init() throws ServletException {// Obtains the parameter PropertyValues PVS = new in the configuration of web.xml ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); if (! pvs.isEmpty()) { try { BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext()); bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment())); initBeanWrapper(bw); bw.setPropertyValues(pvs, true); } catch (BeansException ex) { if (logger.isErrorEnabled()) { logger.error("Failed to set bean properties on servlet '" +  getServletName() + "'", ex); } throw ex; }} // key: an empty method, template mode, subclass FrameworkServlet, overrides it initServletBean(); }Copy the code

FrameworkServlet

The initServletBean() method is an initialization. Methods NaZhu if call initWebApplicationContext WebApplicationContext () is initialized, and invoke initFrameworkServlet (), this is an empty method, can provide subclasses autotype later, Doing some initialization stuff, not being overwritten for the time being.

@Override protected final void initServletBean() throws ServletException { getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'"); if (logger.isInfoEnabled()) { logger.info("Initializing Servlet '" + getServletName() + "'"); } long startTime = System.currentTimeMillis(); Try {/ / key: initialize WebApplicationContext enclosing WebApplicationContext = initWebApplicationContext (); // an empty method that can be provided to future subclasses to override initFrameworkServlet() to do some initialization; } catch (ServletException | RuntimeException ex) { logger.error("Context initialization failed", ex); throw ex; } // omit irrelevant code... }Copy the code

The following analysis initWebApplicationContext () method.

  • InitWebApplicationContext () method

This method initializes the WebApplicationContext, and it integrates with the ApplicationContext, so it is also an IoC container.

So the responsibility of the FrameworkServlet class is to make an association between Spring and Servler.

This method, in addition to initializing the WebApplicationContext, calls an onRefresh() method, again in template mode, that is empty, allowing subclasses to override it logically, such as the DispatcherServlet subclass

protected WebApplicationContext initWebApplicationContext() { WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null; / / a parameter constructor, the incoming webApplicationContext object, will enter the judge if (this. WebApplicationContext! = null) { wac = this.webApplicationContext; if (wac instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac; // The container's refresh() has not called if (! Caca.isactive ()) {// setParent if (caca.getparent () == null) {caca.setparent (rootContext); } configureAndRefreshWebApplicationContext(cwac); }}} if (wac == null) {// Get the ServletContext set by setAttribute. Now through the getAttribute access to wac = findWebApplicationContext (); } if (wac == null) {// Create WebApplicationContext, set the environment, parent container, Local resource file wac = createWebApplicationContext (rootContext); } if (! Enclosing refreshEventReceived) {synchronized (enclosing onRefreshMonitor) {/ / refresh, is also a template pattern, empty methods, let subclasses override logic processing, The DispatcherServlet subclass overrides it onRefresh(WAC); }} // use setAttribute(), Set the container to the ServletContext the if (this. PublishContext) {String attrName = getServletContextAttributeName (); getServletContext().setAttribute(attrName, wac); } return wac; } //WebApplicationContext public interface WebApplicationContext extends ApplicationContext { //... }Copy the code

Next, let’s look at the onRefresh() method that subclasses DispatcherServlet to replicate.

DispatcherServlet

The FrameworkServlet class is responsible for associating Spring with a Servler. For the DispatcherServlet, the initialization method is onRefresh().

The onRefresh() method calls the initStrategies() method to initialize various components.

/ / initHandlerMappings()

@Override protected void onRefresh(ApplicationContext context) { initStrategies(context); } protected void initStrategies(ApplicationContext context) {// Parse the request initMultipartResolver(context); // Internationalize initLocaleResolver(context); / / theme initThemeResolver (context); // Methods that handle controllers and URL mappings initHandlerMappings(context); // Initialize adapters, then return ModelAndView initHandlerAdapters(context); / / initialization exception handler initHandlerExceptionResolvers (context); / / initialize the view forward initRequestToViewNameTranslator (context); // Initialize the view resolver, convert the view information saved by ModelAndView into a view, output data initViewResolvers(context); // Initialize the mapping handler initFlashMapManager(context); }Copy the code

Summarize the roles and responsibilities of the three Servlet classes

  • HttpServletBean

Do some initialization work by parsing parameters configured in web.xml into servlets, such as those configured in init-param. Provides the initServletBean() template method, which is implemented by subclasses FrameworkServlet.

  • FrameworkServlet

Associate the Servlet with the SpringIoC container. It initializes the WebApplicationContext, which represents the SpringMVC context. It has a parent context, the ContextLoaderListener configured in the Web.xml configuration file, which initializes the container context. The onRefresh() template method is provided, implemented as a subclass of DispatcherServlet, as an initialization entry method.

  • DispatcherServlet

The final subclass, as the front-end controller, initializes various components, such as request mapping, view resolution, exception handling, request handling, and so on.

Component initialization process

Let’s begin the component initialization process analysis.

HandlerMapping Processor mapping

Initialize the Url mapping of the Controller.

Mainly the Map < String, HandlerMapping > matchingBeans = BeanFactoryUtils. BeansOfTypeIncludingAncestors (context, HandlerMapping.class, true, false); This gets all the HandlerMapping from the container.

@nullable private List<HandlerMapping> handlerMappings; Private Boolean detectAllHandlerMappings = true; // A switch that indicates whether to get all handler mappings, if false, then search for the instance of a Bean named handlerMapping. Public static final String HANDLER_MAPPING_BEAN_NAME = "handlerMapping"; Private void initHandlerMappings(ApplicationContext context) {// Empty the set this.handlerMappings = null; / / a switch, the default is true, set to false, only the logic of the else if (this. DetectAllHandlerMappings) {/ / key points: Find all HandlerMapping maps <String in the container, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false); If (! matchingBeans.isEmpty()) { this.handlerMappings = new ArrayList<>(matchingBeans.values()); AnnotationAwareOrderComparator.sort(this.handlerMappings); }} else {// Specify to search for handlerMapping instance named handlerMapping try {handlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class); this.handlerMappings = Collections.singletonList(hm); } the catch (NoSuchBeanDefinitionException ex) {/ / Ignore, we 'l l add a default HandlerMapping later.}} / / not find mapping relation, Set a default 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"); }} / / profile name private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet. Properties"; // Obtain the configured component from the configuration file. If no other component is found, Protected <T> List<T> getDefaultStrategies(ApplicationContext Context, Class<T> strategyInterface) { //... }}Copy the code

If none of the mappings are found, the default configuration is retrieved from the configuration file using the getDefaultStrategies method. This method is also called for default configuration when no other component is found.

Profile name: DispatcherServlet. Properties. Will join two BeanNameUrlHandlerMapping, RequestMappingHandlerMapping the default mapping relations.

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
Copy the code

HandlerAdapter Processor adapter

The initialization logic of the HandlerAdapter is basically the same as HandlerMapping above. All instances of HandlerAdapter are searched from the container. If not, get the default HandlerAdapter from the configuration file.

@nullable private List<HandlerAdapter> handlerAdapters; // As with the HandlerMapping above, a switch that searches for all handlerAdapters in the container, if false, Private Boolean detectAllHandlerAdapters = true; Public static final String HANDLER_ADAPTER_BEAN_NAME = "HandlerAdapter "; Private void initHandlerAdapters(ApplicationContext Context) {this.handlerAdapters = null; // Also a switch, default true, Search all HandlerAdapter if in the container (enclosing detectAllHandlerAdapters) {Map < String, HandlerAdapter> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false); If (! matchingBeans.isEmpty()) { this.handlerAdapters = new ArrayList<>(matchingBeans.values()); AnnotationAwareOrderComparator.sort(this.handlerAdapters); }} else {// specify to find handlerAdapter named handlerAdapter try {handlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class); this.handlerAdapters = Collections.singletonList(ha); } catch (NoSuchBeanDefinitionException ex) { // Ignore, We'll add a default HandlerAdapter later.}} Get the default HandlerAdapter from the configuration file if (this.handlerAdapters == null) {this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class); if (logger.isTraceEnabled()) { logger.trace("No HandlerAdapters declared for servlet '" + getServletName() + "': using default strategies from DispatcherServlet.properties"); }}}Copy the code

HandlerExceptionResolver Exception handler

As above, all instances of exception handlers are searched from the container. There is also a switch to search for exception handlers with the specified name.

@Nullable private List<HandlerExceptionResolver> handlerExceptionResolvers; // Switch, whether to shuttle all exception handlers, set to false, Will find the following Bean instance called handlerExceptionResolver private Boolean detectAllHandlerExceptionResolvers = true; // Specify an instance named handlerExceptionResolver public static final String HANDLER_EXCEPTION_RESOLVER_BEAN_NAME = "handlerExceptionResolver"; Private void initHandlerExceptionResolvers (ApplicationContext context) {/ / empty set enclosing handlerExceptionResolvers = null; / / switch, The default true if (this. DetectAllHandlerExceptionResolvers) {/ / search all of the exception handler Map < String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils .beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false); // find if (! matchingBeans.isEmpty()) { this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values()); / / sorting AnnotationAwareOrderComparator. Sort (enclosing handlerExceptionResolvers); } } else { try { HandlerExceptionResolver her = context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class); this.handlerExceptionResolvers = Collections.singletonList(her); } the catch (NoSuchBeanDefinitionException ex) {/ / Ignore, no HandlerExceptionResolver is fine too.}} / / an exception handler, From the configuration file for the default if (this. HandlerExceptionResolvers = = null) {enclosing handlerExceptionResolvers = getDefaultStrategies (context,  HandlerExceptionResolver.class); if (logger.isTraceEnabled()) { logger.trace("No HandlerExceptionResolvers declared in servlet '" + getServletName() + "': using default strategies from DispatcherServlet.properties"); }}}Copy the code

ViewResolver ViewResolver

The view parser, like the parser logic above, has a switch that determines whether to search for all in the container or the named one.

@nullable private List<ViewResolver> viewResolvers; Private Boolean detectAllViewResolvers = true; Public static final String VIEW_RESOLVER_BEAN_NAME = "viewResolver"; Private void initViewResolvers(ApplicationContext context) {this.viewResolvers = null; If (this. DetectAllViewResolvers) {/ / search for all views parser Map < String, ViewResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false); if (! matchingBeans.isEmpty()) { this.viewResolvers = new ArrayList<>(matchingBeans.values()); / / sorting AnnotationAwareOrderComparator. Sort (enclosing viewResolvers); }} else {try {// search for a viewResolver Bean named viewResolver viewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class); this.viewResolvers = Collections.singletonList(vr); } the catch (NoSuchBeanDefinitionException ex) {/ / Ignore, we 'l l add a default ViewResolver later.}} / / didn't find any one view parser, If (this.viewresolvers == null) {this.viewresolvers = getDefaultStrategies(context, viewresolver.class); if (logger.isTraceEnabled()) { logger.trace("No ViewResolvers declared for servlet '" + getServletName() + "': using default strategies from DispatcherServlet.properties"); }}}Copy the code

Request Flow analysis

When a request comes in, we all know that the Servlet’s service() method is called, so we tried searching in DispatchServlet and found none. We went to the parent class FrameworkServlet and found it.

Because the parent class is HttpServlet, each request method is separated into its corresponding callback method in the service.

  1. GET request: doGet
  2. POST request: doPost
  3. PUT request: doPut
  4. DELETE request: doDelete
  5. OPTIONS Request: doOptions
  6. Trace request: doTrace

As you can see, each request is overwritten, and the processRequest() method is called to process the request.

@Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {HttpMethod = httpmethod.resolve (request.getmethod ()); // If it is PATCH or not available, Then follow your own the processRequest () method if (httpMethod = = httpMethod. PATCH | | httpMethod = = null) {the processRequest (request, response); } // In other cases, follow the parent HttpServlet logic and call other distinguished request methods, such as doGet, doPost else {super.service(request, response); Override protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); Override protected final void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } // Override protected final void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); Override protected final void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } @override protected void doOptions(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { if (this.dispatchOptionsRequest || CorsUtils.isPreFlightRequest(request)) { processRequest(request, response); if (response.containsHeader("Allow")) { // Proper OPTIONS response coming from a handler - we're done. return; } } // Use response wrapper in order to always add PATCH to the allowed methods super.doOptions(request, new HttpServletResponseWrapper(response) { @Override public void setHeader(String name, String value) { if ("Allow".equals(name)) { value = (StringUtils.hasLength(value) ? value + ", " : "") + HttpMethod.PATCH.name(); } super.setHeader(name, value); }}); Override protected void doTrace(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { if (this.dispatchTraceRequest) { processRequest(request, response); if ("message/http".equals(response.getContentType())) { // Proper TRACE response coming from a handler - we're done. return; } } super.doTrace(request, response); }Copy the code
  • ProcessRequest processes the request

A lot of processing and setup, not our focus, is mainly the doService() method, which is an abstract method that forces subclasses to overwrite. So eventually subclasses DispatcherServlet will definitely duplicate the doService() method.

protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { long startTime = System.currentTimeMillis(); Throwable failureCause = null; LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext(); LocaleContext localeContext = buildLocaleContext(request); RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes); WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor()); initContextHolders(request, localeContext, requestAttributes); // doService() is an abstract method that forces subclasses to copy 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); }}Copy the code

DispatcherServlet doService()

In the doService() method, the main component distribution processing logic is in the doDispatch() method.

@Override protected void doService(HttpServletRequest request, HttpServletResponse Response) throws Exception {// Print request logRequest(request); Map<String, Object> attributesSnapshot = null; if (WebUtils.isIncludeRequest(request)) { attributesSnapshot = new HashMap<>(); Enumeration<? > attrNames = request.getAttributeNames(); while (attrNames.hasMoreElements()) { String attrName = (String) attrNames.nextElement(); if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) { attributesSnapshot.put(attrName, request.getAttribute(attrName)); SetAttribute (WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext())); request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource()); if (this.flashMapManager ! = null) { FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response); if (inputFlashMap ! = null) { request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap)); } request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap()); request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager); } try {// Key: the main component distribution processing logic in doDispatch() method 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); }}}}Copy the code
  • DoDispatch () dispatches requests to individual components for processing

This method is very important, and the distribution logic is presented here. It’s all about annotation.

Distribution steps:

  1. getHandler()Gets the handler execution chain for this request, including Controller and interceptor, which are combined into one execution chainHandlerExecutionChain.
  2. getHandlerAdapter(), to obtain the adapter of the processor, because there are many ways to implement the processor, such as direct Servlet as the processor, implement the Controller interface, use Controller annotations, etc., each interface method returns a variety of values, so the adapter mode is used here. Let the adapter uniformly output the return value to the processor as ModelAndView.
  3. mappedHandler.applyPreHandleThe chain of responsibility mode invokes the interceptor in the processor chainpreHandle()Method that represents the request ready for processing. Interceptors can intercept processing. If the interceptor picks it up, keep going.
  4. ha.handle(), calls the adapter’s processing method, passes it to the processor, calls the processor interface method, and ADAPTS the result to the processor as ModelAndView.
  5. mappedHandler.applyPostHandleTraversal that calls the interceptor in the processor execution chainpostHandle()A post-processing method that represents the request to be processed, but the view is not yet rendered
  6. processDispatchResult(), processes the view and results, calls the view handler, creates the actual view, and renders the view data. And after rendering, call interceptorafterCompletion()Method, which means the view has been rendered.
  7. mappedHandler.applyAfterConcurrentHandlingStarted, calls the interceptor in the handler execution chain to process the current request whether it succeeds or fails.
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; Exception dispatchException = null; Try {// Check if it is a file upload request. If yes, do some processing processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest ! = request); MappedHandler = getHandler(processedRequest); If (mappedHandler == null) {noHandlerFound(processedRequest, response); if (mappedHandler == null) {noHandlerFound(processedRequest, response); return; } 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; }} // if (!) {} // if (!) {// if (!) {// If (!) {// If (!) {// If (! mappedHandler.applyPreHandle(processedRequest, response)) { return; } // Key: Call the processing method of the adapter, pass it to the processor, and let the adapter convert the result of the processor into a unified ModelAndView mv = ha.handle(processedRequest, Response, mappedHandler.gethandler ()); if (asyncManager.isConcurrentHandlingStarted()) { return; } // If the default view is not found, set the default view applyDefaultViewName(processedRequest, mv); / / key: processing is completed, the call of the interceptor postHandle mappedHandler (rear) processing method. The applyPostHandle (processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) {As of 4.3, we're processing Errors thrown from handler methods As well, // making them available for @ExceptionHandler methods and other scenarios. dispatchException = new NestedServletException("Handler dispatch failed", err); 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)); } the finally {the if (asyncManager isConcurrentHandlingStarted ()) {/ / key points: View rendering completed, call interceptor afterConcurrentHandlingStarted () method if (mappedHandler! = null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { if (multipartRequestParsed) { cleanupMultipart(processedRequest); }}}}Copy the code

So let’s analyze each of these steps.

GetHandler () searches for the processor object for this request

In the chain of Responsibility pattern, iterating through the handlerMappings collection to find handlers and interceptors, the AbstractHandlerMapping getHandler() method is called. Finally, the handler and interceptor are wrapped in the HandlerExecutionChain object.

GetHandlerInternal (), subclass implementation, the main implementation are two, the first is AbstractUrlHandlerMapping, generally with its subclasses SimpleUrlHandlerMapping, this way need configuration in an XML configuration file, has been seldom used. The second is the AbstractHandlerMethodMapping, is to handle our @ Controller and @ RequestMapping.

Typically use a subclass of AbstractHandlerMethodMapping RequestMappingHandlerMapping, including query annotation method for isHandler (), analysis of words here, space is too large to subsequent articles, analysis again.

@Nullable protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { if (this.handlerMappings ! = null) { for (HandlerMapping mapping : this.handlerMappings) { HandlerExecutionChain handler = mapping.getHandler(request); if (handler ! = null) { return handler; } } } return null; } // Class AbstractHandlerMapping implements this method. It is an abstract class from which all HandlerMapping implementations are derived. Abstract methods are extracted for subclasses to implement, Public Interface HandlerMapping {@Nullable HandlerExecutionChain getHandler(HttpServletRequest Request) throws Exception; } public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport implements HandlerMapping, Ordered, BeanNameAware { @Override @Nullable public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Object Handler = getHandlerInternal(request); If (handler == null) {handler = getDefaultHandler(); If (handler == null) {return null; If (handler instanceof String) {String handlerName = (String) handler; handler = obtainApplicationContext().getBean(handlerName); } HandlerExecutionChain = 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 (hasCorsConfigurationSource(handler)) { CorsConfiguration config = (this.corsConfigurationSource ! = null ? this.corsConfigurationSource.getCorsConfiguration(request) : null); CorsConfiguration handlerConfig = getCorsConfiguration(handler, request); config = (config ! = null ? config.combine(handlerConfig) : handlerConfig); executionChain = getCorsHandlerExecutionChain(request, executionChain, config); } return executionChain; } // Form the processor execution chain, 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 mappedInterceptor = (MappedInterceptor) interceptor; // Match the requested URL, Matching to the if (mappedInterceptor. Matches (lookupPath, this.pathMatcher)) { chain.addInterceptor(mappedInterceptor.getInterceptor()); } } else { chain.addInterceptor(interceptor); } } return chain; }}Copy the code

Next, analyze the adapter component, getHandlerAdapter()

GetHandlerAdapter () gets the adapter corresponding to the processor

The chain of responsibility pattern iterates through the call adapter collection, calling the supports() method to ask each adapter if it supports the current processor. If true is returned, the adapter is found, the traversal stops, and the adapter is returned.

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { if (this.handlerAdapters ! = null) { for (HandlerAdapter adapter : If this. HandlerAdapters, adapters, adapters, adapters, adapters, adapters, adapters, adapters, adapters, adapters, adapters, adapters, adapters, adapters Return adapter 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"); }Copy the code

Let’s look at the adapter interface and its subclasses

  1. HttpRequestHandlerAdapterAnd adaptationHttpRequestHandlerAn adapter that acts as a handler.
  2. SimpleServletHandlerAdapterAnd adaptationServletAn adapter that acts as a handler
  3. SimpleControllerHandlerAdapterAnd adaptationControllerInterface acts as an adapter for the handler
Public interface HandlerAdapter {Boolean supports(Object handler); // The supports() method above returns true. This method is called to adapt, @nullable ModelAndView Handle (HttpServletRequest Request, HttpServletResponse Response, Object handler) throws Exception; Long getLastModified(HttpServletRequest Request, Object handler); // Long getLastModified(HttpServletRequest Request, Object handler); } / / HttpRequestHandler adapter public class HttpRequestHandlerAdapter implements HandlerAdapter {@ Override public Boolean  supports(Object handler) { return (handler instanceof HttpRequestHandler); } @Override @Nullable public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { ((HttpRequestHandler) handler).handleRequest(request, response); return null; } @Override public long getLastModified(HttpServletRequest request, Object handler) { if (handler instanceof LastModified) { return ((LastModified) handler).getLastModified(request); } return -1L; }} / / Servlet adapter public class SimpleServletHandlerAdapter implements HandlerAdapter {@ Override public Boolean supports(Object handler) { return (handler instanceof Servlet); } @Override @Nullable public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { ((Servlet) handler).service(request, response); return null; } @Override public long getLastModified(HttpServletRequest request, Object handler) { return -1; }} / / Controller interface adapter is a public class SimpleControllerHandlerAdapter implements HandlerAdapter {@ Override public boolean supports(Object handler) { return (handler instanceof Controller); } @Override @Nullable public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return ((Controller) handler).handleRequest(request, response); } @Override public long getLastModified(HttpServletRequest request, Object handler) { if (handler instanceof LastModified) { return ((LastModified) handler).getLastModified(request); } return -1L; }}Copy the code

Once the corresponding adapter is obtained, the interceptor can be notified according to the process

Interceptor pre-notification

Traversing the interceptor chain, calling its preHandle() method, notifies the interceptor to intercept and append processing prior to request processing. If one of the interceptors returns false for interception, the process is interrupted and it is intercepted.

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception { HandlerInterceptor[] interceptors = getInterceptors(); if (! ObjectUtils.isEmpty(interceptors)) { for (int i = 0; i < interceptors.length; i++) { HandlerInterceptor interceptor = interceptors[i]; if (! interceptor.preHandle(request, response, this.handler)) { triggerAfterCompletion(request, response, null); return false; } this.interceptorIndex = i; } } return true; }Copy the code

The handle() method of the adapter is then called to adapt and return the ModelAndView. After processing, it also means that the request has been processed into the Controller, and then the interceptor notification.

Interceptor post-notification

Unlike pre-notification, post-notification has no blocking capability and can only be enhanced. The logic still iterates through the interceptor chain, calling the interceptor’s postHandle() method.

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception { HandlerInterceptor[] interceptors = getInterceptors(); if (! ObjectUtils.isEmpty(interceptors)) { for (int i = interceptors.length - 1; i >= 0; i--) { HandlerInterceptor interceptor = interceptors[i]; interceptor.postHandle(request, response, this.handler, mv); }}}Copy the code

View and data are obtained, and you can perform view generation and data rendering.

The result processing

Because of the doDispatch() process, SpringMVC does try-catch for us, so it catches the exception and passes it in.

Then judge whether any exception is generated in the process of processing, and use exception processor to process. If there is no exception, you continue down, determine if you need to render, render if you need to render, and finally call back to the interceptor for notification.

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @nullable Exception Exception) throws Exception {// Whether to display an error page Boolean errorView = false; // Handle exceptions 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); }} // Determine whether the processor needs to return to the view if (mv! = null && ! Mv. WasCleared ()) {// key: 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; } // When the view is rendered, the callback interceptor if (mappedHandler! = null) { // Exception (if any) is already handled.. mappedHandler.triggerAfterCompletion(request, response, null); }} // Interceptor callback to inform interceptor that the view has been rendered, Void triggerAfterCompletion(HttpServletRequest Request, HttpServletResponse Response, @Nullable Exception ex) throws Exception { HandlerInterceptor[] interceptors = getInterceptors(); if (! ObjectUtils.isEmpty(interceptors)) { for (int i = this.interceptorIndex; i >= 0; i--) { HandlerInterceptor interceptor = interceptors[i]; try { interceptor.afterCompletion(request, response, this.handler, ex); } catch (Throwable ex2) { logger.error("HandlerInterceptor.afterCompletion threw exception", ex2); }}}}Copy the code

View parsing and rendering

First determine whether the view parser is needed for view parsing, and finally call the render() method of the parsed view to 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); // The real View object View View; String viewName = mv.getViewName(); if (viewName ! 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 {// There is no need to look up, ModelAndView already contains the real view 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() + "'"); } } if (logger.isTraceEnabled()) { logger.trace("Rendering view [" + view + "] "); } try { if (mv.getStatus() ! = null) { response.setStatus(mv.getStatus().value()); } // focus: start rendering view.render(mv.getModelInternal(), request, response); } catch (Exception ex) { if (logger.isDebugEnabled()) { logger.debug("Error rendering view [" + view + "]", ex); } throw ex; }}Copy the code

View resolution

Walk through the collection of view parsers, and different views require different parsers to process them.

The ViewResolver parser is an interface that has several implementation classes corresponding to the supported view technology.

  1. AbstractCachingViewResolverAbstract class, support cache view, all parsers inherit it, it has an internal Map, cache parsed view objects, solve the efficiency problem.
  2. UrlBasedViewResolverInheritance inAbstractCachingViewResolverWhen our Controller returns a string, such as success, it finds the prefix and suffix suffix from our XML configuration file, concatenates it with the URL, and outputs a finished view address. The other option is we go backredirect:If the prefix is a string, the redirection View will be parsed and redirected.
  3. InternalResourceViewResolverInternal resource resolver, inherited from aboveUrlBasedViewResolver, soUrlBasedViewResolverSome functions, it has, mainly for loading/WEB-INF/Resources in the directory.
  4. There are other less commonly used parsers that I won’t cover here
<! - the parser configuration view - > < bean id = "viewResolver" class = "org. Springframework. Web. Servlet. The InternalResourceViewResolver" > <! <property name="prefix" value="/WEB-INF/pages/"/> <! <property name="suffix" value=".jsp"/> </bean>Copy the code
@Nullable protected View resolveViewName(String viewName, @Nullable Map<String, Object> model, Locale locale, HttpServletRequest request) throws Exception { if (this.viewResolvers ! = null) {// Walk through the set of view parsers, different views need different parsers to handle for (ViewResolver ViewResolver: this.viewResolvers) { View view = viewResolver.resolveViewName(viewName, locale); if (view ! = null) { return view; } } } return null; } public interface ViewResolver {// Try to resolve the view name as a view object. Throws Nullable View resolveViewName(String viewName, Locale Locale) throws Exception; }Copy the code

The view to render

When the View is parsed, the View object is generated, and the View is also an interface, which has the following implementation classes:

  1. AbstractView, AbstractView abstract class, defines the rendering process, abstracts some abstract methods, subclasses do special processing, most implementation classes inherit from it
  2. VelocityView, which supports pages generated by the Velocity framework.
  3. FreeMarkerView, which supports pages generated by the FreeMarker framework.
  4. JstlView, supports the generation of JSTL views.
  5. RedirectView, which supports the generation of page skipping view.
  6. MappingJackson2JsonView, output Json view, using Jackson library to implement Json sequence

The essence of a view is to write() to the client via a Response object.

public interface View { String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus"; String PATH_VARIABLES = View.class.getName() + ".pathVariables"; String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType"; Content-type @nullable default String getContentType() {return null; } // render data to view void render(@nullable Map<String,? > model, HttpServletRequest request, HttpServletResponse response) throws Exception; }Copy the code

Final notice

The doDispatch() method, after the whole try-catch, finally code block, calls the interceptor for final notification.

The interceptor traversed must be an implementation class of the AsyncHandlerInterceptor interface.

void applyAfterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response) { HandlerInterceptor[] interceptors = getInterceptors(); if (! ObjectUtils.isEmpty(interceptors)) { for (int i = interceptors.length - 1; i >= 0; i--) { if (interceptors[i] instanceof AsyncHandlerInterceptor) { try { AsyncHandlerInterceptor asyncInterceptor = (AsyncHandlerInterceptor) interceptors[i]; asyncInterceptor.afterConcurrentHandlingStarted(request, response, this.handler); } catch (Throwable ex) { logger.error("Interceptor [" + interceptors[i] + "] failed in afterConcurrentHandlingStarted", ex); } } } } }Copy the code

conclusion

The complicated version

  • Initialization time

    • HandlerMapping, HandlerAdapter adapter, exception handler, view parser, etc.
  • When the request arrives

    • DoService () of the DispatchServlet is called to look up the corresponding processor, including our Controller and interceptor, based on the requested Url.
    • According to the processor, find the corresponding HandlerAdapter adapter, because there are many forms of processor, such as Servlet as the processor, implementation of the Controller interface, using the @Controller annotation, etc., return different values. You use an adapter to match the results to the ModelAndView.
    • The interceptor’s preprocessing method is called back before executing the adapter’s processing method, which can be intercepted here, and if intercepted, the process is not continued. If not, go ahead and call the adapter’s processing method, and in the processing method, call the handler, call our Controller. After execution, the interceptor’s post-processing callback method is called back.
    • After obtaining the ModelAndView, it is delivered to the ViewResolver, which resolves the view name as a specific view instance, and then renders the view and data back to the client, and calls back the callback method of the interceptor after rendering the view.

Simple version of

When the request comes, start from the front-end controller of DispatchServlet, search the processor through the URL, find the adapter corresponding to the processor, execute the processing method of the adapter, and return the ModelAndView. The view processor parses the ModelAndView, generates the corresponding view object, renders the view and data, and returns it to the client.