Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.

The paper come zhongjue shallow, and must know this to practice

Note: This article’s SpringBoot version is 2.5.2; The JDK version is JDK 11.

Preface:

Previously: Do you know what data structures are used to store API information when SpringBoot starts? (last)

The reason for writing the article, the previous said no longer repeat.

The questions are as follows:

Why does the browser know which interface it is looking for when it makes a request to the back end? What matching rules are used?

How does the SpringBoot backend store API information? What data structure is it stored in?

@ResponseBody
@GetMapping("/test")
public String test(a){
    return "test";
}
Copy the code

To be honest, after listening to him ask, I feel I am not enough volume, it is soul torture, I can not answer. Let’s go to understand!

If there are deficiencies in the article, please be sure to approve in time! I hereby solemnly thank you.

👉 Startup process

I. Request process

Without further ado, we’ll just start with the DispatcherServlet.

Let’s just look at what we care about, and if it’s not what we care about, we won’t talk much about it.

Here is also drawn a flow chart for your reference:

1.1, the DispatcherServlet

We’re all familiar with SpringMVC’s pattern for handling requests, so we won’t go into that. Straight to the liver.0

1) doService

@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    logRequest(request);

    // Keep a snapshot of the request attributes in case of an include,
    // to be able to restore the original attributes after the include.
    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)); }}}// Make frame objects available to handlers and view objects.
    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);
    }

    RequestPath previousRequestPath = null;
    if (this.parseRequestPath) {
        previousRequestPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
        ServletRequestPathUtils.parseAndCache(request);
    }

    try {
        // From here to the next step.
        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); }}if (this.parseRequestPath) { ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request); }}}Copy the code

2) doDispatch

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);// Determine handler for the current request.
          // Get the matching execution chain here is our next entry
         mappedHandler = getHandler(processedRequest);
         if (mappedHandler == null) {
            noHandlerFound(processedRequest, response);
            return;
         }

         // Returns the HandlerAdapter of this handler object.
         HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

         // Process last-modified header, if supported by the handler.
         String method = request.getMethod();
         boolean isGet = HttpMethod.GET.matches(method);
         if (isGet || HttpMethod.HEAD.matches(method)) {
            long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
            if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
               return; }}if(! mappedHandler.applyPreHandle(processedRequest, response)) {return;
         }

         // Actually invoke the handler.
         // Use the given handler to process the request. Reflection performs business methods within this
         mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

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

         applyDefaultViewName(processedRequest, mv);
         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); }}}}Copy the code

3) getHandler

Returns the HandlerExecutionChain for this request. Try all handler mappings in order.

@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
   if (this.handlerMappings ! =null) {
      for (HandlerMapping mapping : this.handlerMappings) {
          // Return HandlerExecutionChain and we'll continue from there
         HandlerExecutionChain handler = mapping.getHandler(request);
         if(handler ! =null) {
            returnhandler; }}}return null;
}
Copy the code

1.2, HandlerMapping

public interface HandlerMapping {
    / /... The rest of the code is left
    
    /** Returns a handler for this request and any interceptors. The selection can be based on any of the request URL, session state, or implementation class selection. The HandlerExecutionChain returned contains a handler object, not a label interface, so the handler is not constrained in any way. For example, a HandlerAdapter can be written to allow the use of handler objects from another framework. If no match is found, null is returned. This is not a mistake. The DispatcherServlet will query all registered HandlerMapping Beans to find a match and only determine an error */ if no handler is found
	@Nullable
	HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}
Copy the code

1.3, AbstractHandlerMapping

AbstractHandlerMapping: Abstract base class for the HandlerMapping implementation. Support for sorting, default handlers, and handler interceptors, including handler interceptors mapped by path patterns.

public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
    implements HandlerMapping.Ordered.BeanNameAware {
	
    //....

    /** Finds the handler for a given request, and if no specific handler is found, falls back to the default handler. * /
    @Override
    @Nullable
    public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        // Finds the handler for the given request, and returns null if the specific request is not found.
        // Let's focus on this method and follow along
        Object handler = getHandlerInternal(request);
        if (handler == null) {
            handler = getDefaultHandler();
        }
        if (handler == null) {
            return null;
        }
        // Bean name or resolved handler?
        if (handler instanceof String) {
            String handlerName = (String) handler;
            handler = obtainApplicationContext().getBean(handlerName);
        }

        // Make sure there are cache lookup paths for interceptors and others
        if(! ServletRequestPathUtils.hasCachedPath(request)) { initLookupPath(request); }/ / getHandlerExecutionChain () : for a given processing program to build a HandlerExecutionChain, including applicable interceptors.
        HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

        // Cross-domain correlation is not examined
        if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
            CorsConfiguration config = getCorsConfiguration(handler, request);
            if(getCorsConfigurationSource() ! =null) { CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request); config = (globalConfig ! =null ? globalConfig.combine(config) : config);
            }
            if(config ! =null) {
                config.validateAllowCredentials();
            }
            executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
        }

        return executionChain;
    }
    // ...
}
Copy the code

The getHandlerInternal method is defined as AbstractHandlerMapping, but it’s an abstract method, and we’ll see what it does if we look down at its implementation.

/** Finds the handler for the given request, or returns NULL if the specific request is not found. The default handler will result if a null return value is set. * /
@Nullable
protected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception;
Copy the code

Let’s look at his implementation below:

1.4, AbstractHandlerMethodMapping < T >

1.4.1, getHandlerInternal

/** * find the handler method for the given request. * /
@Override
@Nullable
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    // The initLookupPath method is implemented in the AbstractHandlerMapping class
    // Initializes the path used to request the mapping.
    LookupPath = lookupPath = lookupPath
    String lookupPath = initLookupPath(request);
    this.mappingRegistry.acquireReadLock();
    try {
        // Find the best match handler method for the current request. If more than one match is found, the best match is selected
        // This is how we match.
        HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
        return(handlerMethod ! =null ? handlerMethod.createWithResolvedBean() : null);
    }
    finally {
        this.mappingRegistry.releaseReadLock(); }}Copy the code

1.4.2 lookupHandlerMethod (matching interface code)

Note that the matching method is based on the value path in @requestMapping. If multiple matches are found, for example, wildcard or precise configuration, they will be put into a set, sorted according to rules, and then the first element of the set will be retrieved. Interested can take a look at this sort of rules, in theory must be the path more accurate will take precedence, the specific code implementation is as follows:

/** Find the best match handler method for the current request. If more than one match is found, the best match is selected. If we look at the doc comment, we can see that this is an important point
@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
    List<Match> matches = new ArrayList<>();
    // Returns a match for the given URL path.
    List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
    if(directPathMatches ! =null) {
        / / below
        addMatchingMappings(directPathMatches, matches, request);
    }
    if (matches.isEmpty()) {
        addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
    }
    if(! matches.isEmpty()) {// Take the first one, too, and use it directly when there are no more matches
        Match bestMatch = matches.get(0);
        if (matches.size() > 1) {
            // Collation
            Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
            // Sort
            matches.sort(comparator);
            // get the first one
            bestMatch = matches.get(0);
            if (logger.isTraceEnabled()) {
                logger.trace(matches.size() + " matching mappings: " + matches);
            }
            // Cross-domain correlation
            if (CorsUtils.isPreFlightRequest(request)) {
                for (Match match : matches) {
                    if (match.hasCorsConfig()) {
                        returnPREFLIGHT_AMBIGUOUS_MATCH; }}}else {
                Match secondBestMatch = matches.get(1);
                if (comparator.compare(bestMatch, secondBestMatch) == 0) {
                    Method m1 = bestMatch.getHandlerMethod().getMethod();
                    Method m2 = secondBestMatch.getHandlerMethod().getMethod();
                    String uri = request.getRequestURI();
                    throw new IllegalStateException(
                        "Ambiguous handler methods mapped for '" + uri + "' : {" + m1 + "," + m2 + "}"); }}}// This code is shown below.
        request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod());
        // This method is called when a matching mapping is found. The specific role is not understood
        handleMatch(bestMatch.mapping, lookupPath, request);
        return bestMatch.getHandlerMethod();
    }
    else {
        return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request); }}Copy the code

MappingRegistry = new mappingRegistry (); this.mappingRegistry = new mappingRegistry ();

Its method getMappingsByDirectPath(lookupPath) is actually called as follows:

/** returns a match for the given URL path. * /
@Nullable
public List<T> getMappingsByDirectPath(String urlPath) {
    return this.pathLookup.get(urlPath);
}
Copy the code

HXDM: This. MappingRegistry and this. PathLookup are the same classes and data structures that store information when we start, XD.

Well, then the result is pretty clear.

The List

directPathMatches is a List of all the interfaces that we scanned at startup, and then sorted, taking the first one and finding the best match.

XDM, we’re done here.

1.4.3, addMatchingMappings

private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
    for (T mapping : mappings) {
        // Checks if the mapping matches the current request and returns a (possibly new) condition that the mapping is related to the current request.
        T match = getMatchingMapping(mapping, request);
        if(match ! =null) {
            // Comment Match is a wrapper around the matched HandlerMethod and its mapping, used to compare the best Match with the comparator in the context of the current request.
            . / / here enclosing mappingRegistry getRegistrations () returns is registered project startup is decorated RequestMapping annotation method of relevant information
            //private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
            //.get(mapping) is the way to get our request back
            // Mapping is the requested URL, method, etc.
            matches.add(new Match(match, this.mappingRegistry.getRegistrations().get(mapping))); }}}Copy the code

It’s a little tricky to say, but let’s just go to the method call and see what it changes.

You simply store the information in the matches variable. The other thing is to get the instance that matches the HandlerMethod.

Second, the summary

  1. Scan all registered beans
  2. These beans are iterated over, in turn, to determine if they are processors, and to detect their HandlerMethod
  3. Iterate through all the methods in the Handler to find those marked by the @requestMapping annotation.
  4. Get @requestMapping instance on method.
  5. Check that the class to which the method belongs has @requestMapping annotations
  6. RequestMapping of class level and method level together (CreatermappingInfo)
  7. When the request arrives, go to urlMap to find the matching URL and get the corresponding mapping instance, and then go to handlerMethods to get the matching HandlerMethod instance.
  8. Next comes the SpringMVC execution flow.
  9. Register the RequestMappingInfo instance with the handler method in the cache.

This is basically enough to answer the three questions mentioned above.

He’s asking why, when the browser makes a request to the back end, it knows what API it’s looking for, how does your SpringBoot backend framework store the API information? What data structure is it stored in?

The first answer: store all the interface information into a HashMap, and when requested, pull out the associated interfaces, sort them, and match the best interface.

The second answer is that it is related to the MappingRegistry class.

Third answer: When we saw that the underlying data is stored using HashMap, we can see that the underlying data structure is array + linked list + red-black tree

Three, after the language

If it were not for the three questions mentioned by my friends, I think I would not be so interested in reading the relevant source code step by step, and this article would probably be stillborn.

Thank you very much @Xiaoyu. To be sure, he invited me to read the ORM framework source code together. But it’s gonna be a long time.

Personal Remarks:

Reading the source code process, in fact, is really full of fun and boring.

Read some key things, is very happy; Like “I forgot where I debug again, my mind is cold again”, I will start to complain (I often finish a sentence or two), and then continue to watch.

Hello everyone, I am ning Zaichun: homepage

A young man who likes literature and art but takes the path of programming.

Hope: by the time we meet on another day, we will have achieved something.

In addition, it can only be said to provide a personal opinion here. Due to the lack of writing skills, knowledge, not to write a very terminology of the article, hope forgive me.

If you find this article useful, please give a thumbs up and encouragement.

We also hope that we can have positive exchanges. If there is any deficiency, please approve in time, here solemnly thank you.