Request Path Matching Routing Mode of Spring Source Code

  • 2021-11-10 09:31:48
  • OfStack

Directory request path matching route entry enters the above method SpringMVC initializes the mapping relationship for the processing of the request to find a matching method from the mapping relationship

Request path matching route

In spring, path matching will be done when a request comes over, so we will analyze path matching under 1 from the source code level.

Example:


@RequestMapping(value = "/user/{aid}/online/**", method = RequestMethod.GET)

Let's look at how this method is found and some corresponding tool classes

Entrance

My project uses the automatically configured RequestMappingHandlerMapping class, in the getHandlerInternal () method:


HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);

The above line is based on your request path and request to find the appropriate method. When the project starts, Spring loads the path and corresponding method into memory.

Enter the above method


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

You can see that if you match directly according to lookupPath, go to the first method; If you don't, you need to match according to the rules and go to the second method.

mappingRegistry. getMappings (). keySer () This method takes the type of RequestMappingInfo, followed by the getMatchingCondition () method of RequestMappingInfo:


 public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
  RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);
  ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);
  HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);
  ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request);
  ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);
 
  if (methods == null || params == null || headers == null || consumes == null || produces == null) {
   return null;
  }
 
  PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request);
  if (patterns == null) {
   return null;
  }

You can see that the code will check whether various conditions match, including the request method methods, the parameter params, the request header headers, and the related consumers, produces, etc. The last line is the path we are looking for matches patternsCondition. getMatchingCondition (request).

This method goes to the getMatchingPattern method of PatternRequestCondition, and then calls the following method to get pattern:


  if (this.pathMatcher.match(pattern, lookupPath)) {
   return pattern;
  }

The type of pathMatcher above is the AntPathMatcher class, which is to call the match method of the AntPathMatcher class to see if it matches, and then return pattern.

SpringMVC will request to find a matching process

In SpringMVC mode, how does a browser request map to the specified controller?

Initialize the mapping relationship

When the web server is started, the Spring container stores a data structure of map, which records the correspondence between the controller and url requests. So how did the data in this map come from?

First of all, let's look at the initHandlerMethods method of AbstractHandlerMethodMapping (as to why I found this method directly, I also searched online, and the previous call chain didn't struggle)


protected void initHandlerMethods() {
 if (logger.isDebugEnabled()) {
   logger.debug("Looking for request mappings in application context: " + getApplicationContext());
  }
 
        // Get Spring All of container assembly bean Name of 
 String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
    BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
    getApplicationContext().getBeanNamesForType(Object.class));
 
           // Traversal 
 for (String beanName : beanNames) {
                // Determine the bean Whether there is @controller Or @RequestMapping Annotation 
  if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX) &&
    isHandler(getApplicationContext().getType(beanName))){
                        // If you have the above annotations, you need to save the corresponding relationship 
   detectHandlerMethods(beanName);
  }
 }
 handlerMethodsInitialized(getHandlerMethods());
}
protected void detectHandlerMethods(final Object handler) {
        // Get passed over handler Class information of 
 Class<?> handlerType =
   (handler instanceof String ? getApplicationContext().getType((String) handler) : handler.getClass());
 
 // Avoid repeated calls to getMappingForMethod which would rebuild RequestMappingInfo instances
        // Initialization 1 Object that holds mapping information map
 final Map<Method, T> mappings = new IdentityHashMap<Method, T>();
 final Class<?> userType = ClassUtils.getUserClass(handlerType);
 
 Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter() {
  @Override
  public boolean matches(Method method) {
                // Get the mapping information of all methods in this class  T For RequestMappingInfo
                //mapping Values take the form of {[/test/test1],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}
   T mapping = getMappingForMethod(method, userType);
   if (mapping != null) {
                                // Add information to map
    mappings.put(method, mapping);
    return true;
   }
   else {
    return false;
   }
  }
 });
 
 for (Method method : methods) {
                // Registration HandlerMethod , carried out inside 1 Some duplicate validation, etc. 
  registerHandlerMethod(handler, method, mappings.get(method));
 }
}

A more important method, getMappingForMethod, is called in the above method, and an RequestMappingInfo object that we will use in the following 1 straight is generated by this method. The specific methods are as follows:


@Override
// The method takes two parameters, 1 One is the specific method, 1 Is the class where the method is located 
 protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
  RequestMappingInfo info = null;
// Find a way @RequestMapping Annotation 
  RequestMapping methodAnnotation = AnnotationUtils.findAnnotation(method, RequestMapping.class);
  if (methodAnnotation != null) {
// This method returns null
   RequestCondition<?> methodCondition = getCustomMethodCondition(method);
// Create RequestMappingInfo Object 
   info = createRequestMappingInfo(methodAnnotation, methodCondition);
// Find the @RequestMapping Annotation 
   RequestMapping typeAnnotation = AnnotationUtils.findAnnotation(handlerType, RequestMapping.class);
   if (typeAnnotation != null) {
// This method also returns 1 A null
    RequestCondition<?> typeCondition = getCustomTypeCondition(handlerType);
// If both classes and methods have @RequestMapping Annotation, then proceed combine Operation 
    info = createRequestMappingInfo(typeAnnotation, typeCondition).combine(info);
   }
  }
  return info;
 }

So how does the createRequestMappingInfo method called in the above method really create an RequestMappingInfo object?


protected RequestMappingInfo createRequestMappingInfo(RequestMapping annotation, RequestCondition<?> customCondition) {
// Get @RequestMapping On the annotation value Value 
  String[] patterns = resolveEmbeddedValuesInPatterns(annotation.value());
// Create 1 A RequestMappingInfo With the parameter of 1 Heap condition , out PatternsRequestCondition , and the rest are all used @RequestMapping Values on annotations 
  return new RequestMappingInfo(
    annotation.name(),
    new PatternsRequestCondition(patterns, getUrlPathHelper(), getPathMatcher(),
      this.useSuffixPatternMatch, this.useTrailingSlashMatch, this.fileExtensions),
    new RequestMethodsRequestCondition(annotation.method()),
    new ParamsRequestCondition(annotation.params()),
    new HeadersRequestCondition(annotation.headers()),
    new ConsumesRequestCondition(annotation.consumes(), annotation.headers()),
    new ProducesRequestCondition(annotation.produces(), annotation.headers(), this.contentNegotiationManager),
    customCondition);
 }

protected void registerHandlerMethod(Object handler, Method method, T mapping) {
//handler Here is with the @controller Or @RequestMapping The name of the class of 
// Initialization 1 A HandlerMethod , including 1 Information such as the names and methods of some classes 
  HandlerMethod newHandlerMethod = createHandlerMethod(handler, method);
  HandlerMethod oldHandlerMethod = this.handlerMethods.get(mapping);
// Determine if there is handlerMethods Whether there is duplicate data, throw an exception if there is, and add it if there is no handlerMethods map Medium 
  if (oldHandlerMethod != null && !oldHandlerMethod.equals(newHandlerMethod)) {
   throw new IllegalStateException("Ambiguous mapping found. Cannot map '" + newHandlerMethod.getBean() +
     "' bean method \n" + newHandlerMethod + "\nto " + mapping + ": There is already '" +
     oldHandlerMethod.getBean() + "' bean method\n" + oldHandlerMethod + " mapped.");
  }
 
  this.handlerMethods.put(mapping, newHandlerMethod);
  if (logger.isInfoEnabled()) {
   logger.info("Mapped \"" + mapping + "\" onto " + newHandlerMethod);
  }
 
// There will be no * Number and hello pattern Join to urlMap Medium 
  Set<String> patterns = getMappingPathPatterns(mapping);
  for (String pattern : patterns) {
   if (!getPathMatcher().isPattern(pattern)) {
    this.urlMap.add(pattern, mapping);
   }
  }
 
// Maintenance 1 A nameMap , key Is the first name, formatted as congroller Quasi-capital letters +#+ Method name 
// For example TestBank Class getBank Method, which can be TB#getBank
  if (this.namingStrategy != null) {
   String name = this.namingStrategy.getName(newHandlerMethod, mapping);
   updateNameMap(name, newHandlerMethod);
  }
 }

From the above registerHandlerMethod method, we can see that this method maintains three map:

handlermethods key is RequestMappingInfo value is HandlerMethod urlMap : key for no * and? pattern (eg/test/test1) value is RequestMappingInfo nameMap key is the first name, formatted as congroller class uppercase + # + method name, such as getBank method of TestBank, key is TB # getBank

The above three map will be used emphatically when matching which method is used to process browser requests.

Looking for matching methods from mapping relations

So how does DispatcherServlet handle a request?

Let's start with the doService method of DispatcherServlet, where the lookupHandlerMethod method of the AbstractHandlerMethodMapping class is eventually called to determine which method should handle this request. The code is as follows:


protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
  List<Match> matches = new ArrayList<Match>();
// From urlMap Looking for matching processing methods in 
  List<T> directPathMatches = this.urlMap.get(lookupPath);
// If from urlMap If a matching processing method is found in the addMatchingMappings Method, put the matching method into the matches Set 
  if (directPathMatches != null) {
   addMatchingMappings(directPathMatches, matches, request);
  }
// If urlMap No direct matching method was found in 
  if (matches.isEmpty()) {
   // No choice but to go through all mappings...
   addMatchingMappings(this.handlerMethods.keySet(), matches, request);
  }
 
  if (!matches.isEmpty()) {
// If a matching method is found, get the 1 A comparator 
   Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
// Sort the matched methods by comparator 
   Collections.sort(matches, comparator);
   if (logger.isTraceEnabled()) {
    logger.trace("Found " + matches.size() + " matching mapping(s) for [" + lookupPath + "] : " + matches);
   }
// If the only successful matching methods are 1 A, take this method back. If multiple methods are matched, compare the first two best matches. 
// If the comparison result is 0 Is thrown without finding the only 1 Exceptions of appropriate handling methods 
   Match bestMatch = matches.get(0);
   if (matches.size() > 1) {
    Match secondBestMatch = matches.get(1);
    if (comparator.compare(bestMatch, secondBestMatch) == 0) {
     Method m1 = bestMatch.handlerMethod.getMethod();
     Method m2 = secondBestMatch.handlerMethod.getMethod();
     throw new IllegalStateException(
       "Ambiguous handler methods mapped for HTTP path '" + request.getRequestURL() + "': {" +
       m1 + ", " + m2 + "}");
    }
   }
   handleMatch(bestMatch.mapping, lookupPath, request);
   return bestMatch.handlerMethod;
  }
  else {
// If no match is found, return null
   return handleNoMatch(handlerMethods.keySet(), lookupPath, request);
  }
 }

As can be seen from the above code, the program will first look for a matching method from this. urlMap, so when did the data in this urlMap load? We looked at the registerHandlerMethod method online, which initialized the data in urlMap when the web server started.

From the above analysis, we can get a general understanding of how the Spring container maintains the mapping relationship between url and methods, and how to match the requests to the correct methods when they are received.

As for what the combine operation triggered when both classes and methods have @ RequestMapping annotations does, and how multiple matching methods are sorted by comparators, we will analyze it next time.


Related articles: