Controller registration in SpringBoot

This article will start with servlets and see how controllers in web containers are registered in HandlerMapping through source code. Once the request comes in, how the Web container finds the corresponding Controller method based on the request path and executes it.

First, let’s talk about the general idea and flow chart of this paper:

  1. We use it all the timeRequestMappingThis annotation corresponds to a method that will eventually beRequestMappingHandlerMappingProcess, and encapsulate into oneHandlerMethodInjected into itselfmappingRegistryIn the container. This step is the registration of the Controller, and the trigger is performed becauseRequestMappingHandlerMappingThis class implementsInitializingBeanInterface, triggered by the Spring container.
  2. When the Tomcat container is started, the Servlet init method is finally called, where all theHandlerMappingRegistered to their own internalhandlerMappingsAttribute. This creates an indirect relationship between the Servlet and the RequestMapping-annotated Controller.
  3. When a request arrives, Tomcat calls the Servlet’s Service method after it gets the request body and wraps it. This method will eventually go toDispatcherServletthedoDispatchMethod, this method will find the best fitHandlerMappingAnd pull out the correspondingHadlerMethodAnd then give the correspondingHandlerAdapterThe execution.
  4. Controller Registration Flowchart

5. Flow chart of controller discovery and use The text start

DispatcherServlet to handle requests

Servlet interface source code

public interface Servlet {
    / / initialization
    public void init(ServletConfig config) throws ServletException;

    // Respond to the request
    public void service(ServletRequest req, ServletResponse res)
            throws ServletException, IOException;

    // Get servlet information
    public String getServletInfo(a);
        
    // Callback when the service stops
    public void destroy(a);
}
Copy the code

Springboot has a Built-in Tomcat container, which follows the Servlet specification. The servlet specification defines callback hooks for initialization, response, getting configuration information, and destruction. As can be seen from the servlet specification, the Init method of the servlet is called when Tomcat is started, the Service method is called when the request is processed, and the destroy method is called when the container is destroyed. Servlet in the most core implementation is we know DispatchServlet, take a look at the DispatchServlet inheritance system

Take a look at what the initialization of the Servlet does from the DispatchServlet inheritance architecture.

Initialization init for the Servlet

HttpServletBean init method source

@Override
public final void init(a) throws ServletException {

   // Set the properties of the servlet
   PropertyValues pvs = new 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);
         }
         throwex; }}// The specific initialization method is left to the subclass implementation
   initServletBean();
}
// Empty implementation, specific by the subclass implementation
protected void initServletBean(a) throws ServletException {}Copy the code

As you can see from the init method in HttpServletBean, the core here is to set some bean properties for the Servlet. Continue to subclass FrameworkServlet to see the initServletBean method

@Override
protected final void initServletBean(a) throws ServletException {
   getServletContext().log("Initializing Spring " + getClass().getSimpleName() + "'" + getServletName() + "'");
   if (logger.isInfoEnabled()) {
      logger.info("Initializing Servlet '" + getServletName() + "'");
   }
   long startTime = System.currentTimeMillis();

   try {
      // This initializes the Web context, which initializes the nine policies of the Servlet
      this.webApplicationContext = initWebApplicationContext();
      // This method is also an empty implementation
      initFrameworkServlet();
   }
   catch (ServletException | RuntimeException ex) {
      logger.error("Context initialization failed", ex);
      throw ex;
   }

   if (logger.isDebugEnabled()) {
      String value = this.enableLoggingRequestDetails ?
            "shown which may lead to unsafe logging of potentially sensitive data" :
            "masked to prevent unsafe logging of potentially sensitive data";
      logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
            "': request parameters and headers will be " + value);
   }

   if (logger.isInfoEnabled()) {
      logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms"); }}// Initialize the context
protected WebApplicationContext initWebApplicationContext(a) {
   WebApplicationContext rootContext =
         WebApplicationContextUtils.getWebApplicationContext(getServletContext());
   WebApplicationContext wac = null;

   if (this.webApplicationContext ! =null) {
      wac = this.webApplicationContext;
      if (wac instanceof ConfigurableWebApplicationContext) {
         ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
         if(! cwac.isActive()) {if (cwac.getParent() == null) { cwac.setParent(rootContext); } configureAndRefreshWebApplicationContext(cwac); }}}if (wac == null) {
      wac = findWebApplicationContext();
   }
   if (wac == null) {
      wac = createWebApplicationContext(rootContext);
   }

   if (!this.refreshEventReceived) {
      synchronized (this.onRefreshMonitor) {
          // SpringBoot will only enter this method, which is an empty implementation in the subclass DispatchServletonRefresh(wac); }}if (this.publishContext) {
      String attrName = getServletContextAttributeName();
      getServletContext().setAttribute(attrName, wac);
   }

   return wac;
}
Copy the code

Then follow the onRefresh method of DispatchServlet, which initializes the nine DispatchServlet policies. Here we are only concerned with the initHandlerMappings method

@Override
protected void onRefresh(ApplicationContext context) {
   initStrategies(context);
}

// Initialize the policy
protected void initStrategies(ApplicationContext context) {
   initMultipartResolver(context);
   initLocaleResolver(context);
   initThemeResolver(context);
   // This is the core of our concern, the Controller registration is implemented here
   initHandlerMappings(context);
   // This will handle Controller method calls. The logic is similar to the initHandlerMappings described above
   initHandlerAdapters(context);
   initHandlerExceptionResolvers(context);
   initRequestToViewNameTranslator(context);
   initViewResolvers(context);
   initFlashMapManager(context);
}
Copy the code

Take a look at the initHandlerMappings method

private void initHandlerMappings(ApplicationContext context) {
   this.handlerMappings = null;
    // Defaults to true
   if (this.detectAllHandlerMappings) {
      / / the default HandlerMapping eight, here we only care about RequestMappingHandlerMapping the class
      Map<String, HandlerMapping> matchingBeans =
            BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true.false);
      if(! matchingBeans.isEmpty()) {this.handlerMappings = new ArrayList<>(matchingBeans.values());
         / / sorting
         AnnotationAwareOrderComparator.sort(this.handlerMappings); }}else {
      try {
         HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
         // Let Serlvet establish an indirect relationship with the Controller. The main purpose of this method is to assign a value to the handlerMappings attribute
         this.handlerMappings = Collections.singletonList(hm);
      }
      catch (NoSuchBeanDefinitionException ex) {
      }
   }

   // If there is no HanlderMapping, a default is given
   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"); }}}Copy the code

Take a look at the default HandlerMapping

All we care about here isRequestMappingHandlerMappingThis class, this class is the one that handles RequestMapping annotations on our Controller.

Note the “handlerMappings” statement, which will select the most appropriate handlerMappings from the “handlerMappings” list to process the request

The Servlet’s request handles service

HttpServlet service method source

/** * This method simply converts a ServletRequest to HttpServletRequest * ServletResponse to HttpServletResponse */
@Override
public void service(ServletRequest req, ServletResponse res)
    throws ServletException, IOException {

    HttpServletRequest  request;
    HttpServletResponse response;

    try {
        request = (HttpServletRequest) req;
        response = (HttpServletResponse) res;
    } catch (ClassCastException e) {
        throw new ServletException(lStrings.getString("http.non_http"));
    }
    // Now look at this
    service(request, response);
}

protected void service(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException {
    // Get the method type
    String method = req.getMethod();
    // Call different methods according to different method types, from doGet, see subclass FrameworkServlet doGet methods
    if (method.equals(METHOD_GET)) {
        long lastModified = getLastModified(req);
        if (lastModified == -1) {
            doGet(req, resp);
        } else {
            long ifModifiedSince;
            try {
                ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
            } catch (IllegalArgumentException iae) {
                ifModifiedSince = -1;
            }
            if (ifModifiedSince < (lastModified / 1000 * 1000)) {
                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 {
        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); }}// doGet method for FrameworkServlet
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    / / to continue with
   processRequest(request, response);
}

rotected 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);

   try {
       // This is where the request is handled. Continue with the doService method of subclass DispatchServlet
      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

DoService method of DispatchServlet

@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // Prints logs
   logRequest(request);
   // Save the snapshot
   Map<String, Object> attributesSnapshot = null;
   if (WebUtils.isIncludeRequest(request)) {
      attributesSnapshot = newHashMap<>(); 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)); }}}// Set the properties
   request.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 {
       // Process the request core method
      doDispatch(request, response);
   }
   finally {
      if(! WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {if(attributesSnapshot ! =null) { restoreAttributesAfterInclude(request, attributesSnapshot); }}}}Copy the code

DoDispatch method of DispatchServlet

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{ processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest ! = request);// Get the handler for the current request
         mappedHandler = getHandler(processedRequest);
         if (mappedHandler == null) {
             / / 404
            noHandlerFound(processedRequest, response);
            return;
         }

         // Get the handlerAdapter that handles the current request
         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; }}// Call the pre-interceptor
         if(! mappedHandler.applyPreHandle(processedRequest, response)) {return;
         }

         // The method corresponding to the request is executed here
         mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

         if (asyncManager.isConcurrentHandlingStarted()) {
            return;
         }

         applyDefaultViewName(processedRequest, mv);
         // Call the post-interceptor
         mappedHandler.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));
   }
   finally {
      if (asyncManager.isConcurrentHandlingStarted()) {
         // Instead of postHandle and afterCompletion
         if(mappedHandler ! =null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); }}else {
         // Clean up any resources used by a multipart request.
         if(multipartRequestParsed) { cleanupMultipart(processedRequest); }}}}@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
   if (this.handlerMappings ! =null) {
      for (HandlerMapping mapping : this.handlerMappings) {
      / / the mapping is the RequestMappingHandlerMapping next to talk about
         HandlerExecutionChain handler = mapping.getHandler(request);
         if(handler ! =null) {
            returnhandler; }}}return null;
}
Copy the code

Here you can record the chain of calls for the Servlet to process the request service -> doGet -> processRequest -> doService -> doDispatch

RequestMappingHandlerMapping did what

Can be seen from the above inheritance figure RequestMappingHandlerMapping the InitializingBean interface, so the initialization will call the afterPropertiesSet method.

@Override
@SuppressWarnings("deprecation")
public void afterPropertiesSet(a) {
    / / configuration
   this.config = new RequestMappingInfo.BuilderConfiguration();
   this.config.setUrlPathHelper(getUrlPathHelper());
   this.config.setPathMatcher(getPathMatcher());
   this.config.setSuffixPatternMatch(useSuffixPatternMatch());
   this.config.setTrailingSlashMatch(useTrailingSlashMatch());
   this.config.setRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch());
   this.config.setContentNegotiationManager(getContentNegotiationManager());
    // Here is the core, which registers all controllers
   super.afterPropertiesSet();
}
Copy the code

Then the parent class AbstractHandlerMethodMapping afterPropertiesSet method

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

protected void initHandlerMethods(a) {
   for (String beanName : getCandidateBeanNames()) {
      if(! beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {// The controller is registered here
         processCandidateBean(beanName);
      }
   }
   handlerMethodsInitialized(getHandlerMethods());
}

protected void processCandidateBean(String beanName) { Class<? > beanType =null;
   try {
      beanType = obtainApplicationContext().getType(beanName);
   }
   catch (Throwable ex) {
      if (logger.isTraceEnabled()) {
         logger.trace("Could not resolve type for bean '" + beanName + "'", ex); }}// The isHandler here determines whether there is a Controller or RequestMapping annotation
   if(beanType ! =null && isHandler(beanType)) {
      // The controller will be registered heredetectHandlerMethods(beanName); }}protected void detectHandlerMethods(Object handler) {
    // Get the typeClass<? > handlerType = (handlerinstanceof String ?
         obtainApplicationContext().getType((String) handler) : handler.getClass());

   if(handlerType ! =null) { Class<? > userType = ClassUtils.getUserClass(handlerType); Map<Method, T> methods = MethodIntrospector.selectMethods(userType, (MethodIntrospector.MetadataLookup<T>) method -> {try {
               // This converts RequestMapping to RequestMappingInfo
                  return getMappingForMethod(method, userType);
               }
               catch (Throwable ex) {
                  throw new IllegalStateException("Invalid mapping on handler class [" +
                        userType.getName() + "]."+ method, ex); }});if (logger.isTraceEnabled()) {
         logger.trace(formatMappings(userType, methods));
      }
      methods.forEach((method, mapping) -> {
         Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
         // Register with mappingRegistry, then get the corresponding HandlerMethod according to requestregisterHandlerMethod(handler, invocableMethod, mapping); }); }}Copy the code

RequestMapping is converted to RequestMappingInfo

@Override
@Nullable
protected RequestMappingInfo getMappingForMethod(Method method, Class
        handlerType) {
    // Get the RequestMapping on the method and convert it to RequestMappingInfo
   RequestMappingInfo info = createRequestMappingInfo(method);
   if(info ! =null) {
       // Get the class RequestMapping and convert it to RequestMappingInfo
      RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
      if(typeInfo ! =null) {
          // Merge RequestMapping on the method and RequestMapping on the class, where the URL is merged
         info = typeInfo.combine(info);
      }
      String prefix = getPathPrefix(handlerType);
      if(prefix ! =null) {
         info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info); }}return info;
}
Copy the code

This method converts the RequestMapping on the method to RequestMappingInfo and the class to RequestMappingInfo. Then combine the two RequestMappingInfo into one (merge of urls).

Registration of HandlerMethod

protected void registerHandlerMethod(Object handler, Method method, T mapping) {
   // This is registered directly to mappingRegistry, and later obtained directly from mappingRegistry
   this.mappingRegistry.register(mapping, handler, method);
}

public void register(T mapping, Object handler, Method method) {
   if(KotlinDetector.isKotlinType(method.getDeclaringClass())) { Class<? >[] parameterTypes = method.getParameterTypes();if ((parameterTypes.length > 0) && "kotlin.coroutines.Continuation".equals(parameterTypes[parameterTypes.length - 1].getName())) {
         throw new IllegalStateException("Unsupported suspending handler method detected: "+ method); }}this.readWriteLock.writeLock().lock();
   try {
       / / create HandlerMethod
      HandlerMethod handlerMethod = createHandlerMethod(handler, method);
      validateMethodMapping(handlerMethod, mapping);
      this.mappingLookup.put(mapping, handlerMethod);

      List<String> directUrls = getDirectUrls(mapping);
      for (String url : directUrls) {
         this.urlLookup.add(url, mapping);
      }

      String name = null;
      if(getNamingStrategy() ! =null) {
         name = getNamingStrategy().getName(handlerMethod, mapping);
         addMappingName(name, handlerMethod);
      }

      CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
      if(corsConfig ! =null) {
         this.corsLookup.put(handlerMethod, corsConfig);
      }
       // This will be added to MappingRegistry
      this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
   }
   finally {
      this.readWriteLock.writeLock().unlock(); }}Copy the code

The important thing to note here is that the Controller registered is the HandlerMethod registered directly. The HandlerMethod is the method that corresponds to the specific request in the Controller class. This object encapsulates all the information. After we get the HandlerMethod, we call the specific method through reflection

Look into the RequestMappingHandlerMapping getHandler method, this method is realized in the superclass AbstractHandlerMapping, here with the help of the template method design pattern.

@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    / / here for the real processor, subclass AbstractHandlerMethodMapping implementation
   Object handler = getHandlerInternal(request);
   if (handler == null) {
      // If not, use the default
      handler = getDefaultHandler();
   }
   if (handler == null) {
      return null;
   }
   // Get the real instance from the IOC container if it is beanName
   if (handler instanceof String) {
      String handlerName = (String) handler;
      handler = obtainApplicationContext().getBean(handlerName);
   }
    // Get the chain of interceptors for the request
   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 (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
      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;
}
Copy the code

The core focus here is on two methods: the getHandlerInternal method to get the processor, and the getHandlerExecutionChain method to get the corresponding interceptor chain

AbstractHandlerMethodMapping getHandlerInternal method

@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
   String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
   request.setAttribute(LOOKUP_PATH, lookupPath);
   this.mappingRegistry.acquireReadLock();
   try {
      HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
      return(handlerMethod ! =null ? handlerMethod.createWithResolvedBean() : null);
   }
   finally {
      this.mappingRegistry.releaseReadLock(); }}@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
   List<Match> matches = new ArrayList<>();
   List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
   if(directPathMatches ! =null) {
      addMatchingMappings(directPathMatches, matches, request);
   }
   if (matches.isEmpty()) {
      // No choice but to go through all mappings...
      addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
   }

   if(! matches.isEmpty()) { Match bestMatch = matches.get(0);
      if (matches.size() > 1) {
         Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
         matches.sort(comparator);
         bestMatch = matches.get(0);
         if (logger.isTraceEnabled()) {
            logger.trace(matches.size() + " matching mappings: " + matches);
         }
         if (CorsUtils.isPreFlightRequest(request)) {
            return PREFLIGHT_AMBIGUOUS_MATCH;
         }
         Match secondBestMatch = matches.get(1);
         if (comparator.compare(bestMatch, secondBestMatch) == 0) {
            Method m1 = bestMatch.handlerMethod.getMethod();
            Method m2 = secondBestMatch.handlerMethod.getMethod();
            String uri = request.getRequestURI();
            throw new IllegalStateException(
                  "Ambiguous handler methods mapped for '" + uri + "' : {" + m1 + "," + m2 + "}");
         }
      }
      request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
      handleMatch(bestMatch.mapping, lookupPath, request);
      return bestMatch.handlerMethod;
   }
   else {
      return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request); }}Copy the code

Can be seen from the above methods, finally from mappingRegistry attributes to retrieve HandlerMethod, mappingRegistry in the RequestMappingHandlerMapping above has a detailed explanation

AbstractHandlerMapping getHandlerExecutionChain method

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
   HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
         (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
    // All interceptors will be taken and the appropriate interceptors will be matched through the path
   String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
   for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
      if (interceptor instanceof MappedInterceptor) {
         MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
         if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) { chain.addInterceptor(mappedInterceptor.getInterceptor()); }}else{ chain.addInterceptor(interceptor); }}return chain;
}
Copy the code

So here we have the chain of interceptors and the method that responds to the request. Next is the method is called, here the HandlerAdapter turn appeared, how to acquire the method getHandlerAdapter RequestMappingHandlerAdapter is skipped here

Back to the doDispatch method of DispatchServlet

/ / ha is RequestMappingHandlerAdapter class here
/ / core is RequestMappingHandlerAdapter handleInternal method of a class
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
Copy the code

RequestMappingHandlerAdapter handleInternal method of a class

@Override
protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

   ModelAndView mav;
   checkRequest(request);

   if (this.synchronizeOnSession) {
      HttpSession session = request.getSession(false);
      if(session ! =null) {
         Object mutex = WebUtils.getSessionMutex(session);
         synchronized (mutex) {
            // This is where HandlerMethod is actually calledmav = invokeHandlerMethod(request, response, handlerMethod); }}else {
          // This is where HandlerMethod is actually calledmav = invokeHandlerMethod(request, response, handlerMethod); }}else {
    // This is where HandlerMethod is actually called
      mav = invokeHandlerMethod(request, response, handlerMethod);
   }

   if(! response.containsHeader(HEADER_CACHE_CONTROL)) {if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
         applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
      }
      else{ prepareResponse(response); }}return mav;
}
Copy the code

At this point, the whole call process is over. In which the HandlerAdapter registration, acquisition, processing request reflection call HandlerMethod and so on will be analyzed in the following chapter.