1. 程式人生 > 其它 >Spring MVC請求地址對映詳解:HandlerMapping

Spring MVC請求地址對映詳解:HandlerMapping

1 HandlerMapping介紹

HandlerMapping是Spring MVC的核心元件之一,用來儲存request-handler之間的對映。
簡單來說,request指的是請求地址(還包括請求方法等),handler指的是Controller中對應的方法。
例如,在日常開發時,我們會定義Controller來接收請求:

@RestController
public class TestController {
	@RequestMapping("/hello")
	public String hello() {
		return "Hello HandlerMapping`";
	}
}

這裡的請求地址/hello表示request,而TestController#hello()就是對應的handler
HandlerMapping在初始化的時候,會掃描整個專案,快取所有Controller的request-handler對映。
當接收到請求時,會根據請求地址等資訊從HandlerMapping中找到對應的handler,從而執行對應的業務邏輯。

2 DispatcherServlet中使用HandlerMapping

DispatcherServlet是Spring MVC的核心,它持有HandlerMapping作為成員變數:

private List<HandlerMapping> handlerMappings;

在初始化時會載入HandlerMapping作為成員變數,在處理請求時會從HandlerMapping的快取中找到對應的handler

2.1 初始化HandlerMapping

在專案啟動時,DispatcherServlet會進行HandlerMapping初始化。
載入HandlerMapping可能存在三種情況:

  1. 從Spring容器中獲取所有型別為HandlerMapping的bean,作為handerMappings成員變數。
  2. 從Spring容器中獲取名為handlerMapping的bean,作為handerMappings成員變數。
  3. DispatcherServlet.properties
    配置檔案獲取預設HandlerMapping實現類的全限定類名,例項化並作為handerMappings成員變數。
    org.springframework.web.servlet.DispatcherServlet#initHandlerMappings原始碼:
private void initHandlerMappings(ApplicationContext context) {  
   this.handlerMappings = null;  
  
   if (this.detectAllHandlerMappings) {  
      // 1、Find all HandlerMappings in the ApplicationContext, including ancestor contexts.  
      Map<String, HandlerMapping> matchingBeans =  
            BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);  
      if (!matchingBeans.isEmpty()) {  
         this.handlerMappings = new ArrayList<>(matchingBeans.values());  
         // We keep HandlerMappings in sorted order.  
         AnnotationAwareOrderComparator.sort(this.handlerMappings);  
      }  
   }  
   else {  
      try {  
	      // 2、從Spring容器中獲取名為`handlerMapping`的bean
         HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);  
         this.handlerMappings = Collections.singletonList(hm);  
      }  
      catch (NoSuchBeanDefinitionException ex) {  
         // Ignore, we'll add a default HandlerMapping later.  
      }  
   }  
  
   // 3、Ensure we have at least one HandlerMapping, by registering  
   // a default HandlerMapping if no other mappings are found.   
   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");  
      }  
   }  
  
   for (HandlerMapping mapping : this.handlerMappings) {  
      if (mapping.usesPathPatterns()) {  
         this.parseRequestPath = true;  
         break;  
      }  
   }  
}

this.detectAllHandlerMappings預設為true,所以預設會從Spring容器中獲取所有型別為HandlerMapping的bean,作為handerMappings成員變數。

如果通過前兩種方式都沒有新增請求對映器,會從DispatcherServlet.properties檔案中新增預設請求對映器:

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\  
   org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\  
   org.springframework.web.servlet.function.support.RouterFunctionMapping

新增預設請求對映器會按照如下步驟進行:

  1. 讀取配置檔案中預設請求對映器的全限定類名
  2. 例項化請求對映器,作為bean物件交給Spring容器管理
  3. 賦值給DispatcherServlet#handlerMappings作為請求對映器
    org.springframework.web.servlet.DispatcherServlet#getDefaultStrategies原始碼如下:
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {  
   if (defaultStrategies == null) {  
      try {  
         // Load default strategy implementations from properties file.  
         // This is currently strictly internal and not meant to be customized         // by application developers.         ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);  
         defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);  
      }  
      catch (IOException ex) {  
         throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());  
      }  
   }  
  
   String key = strategyInterface.getName();  
   String value = defaultStrategies.getProperty(key);  
   if (value != null) {  
      String[] classNames = StringUtils.commaDelimitedListToStringArray(value);  
      List<T> strategies = new ArrayList<>(classNames.length);  
      for (String className : classNames) {  
         try {  
            Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());  
            Object strategy = createDefaultStrategy(context, clazz);  
            strategies.add((T) strategy);  
         }  
         catch (ClassNotFoundException ex) {  
            throw new BeanInitializationException(  
                  "Could not find DispatcherServlet's default strategy class [" + className +  
                  "] for interface [" + key + "]", ex);  
         }  
         catch (LinkageError err) {  
            throw new BeanInitializationException(  
                  "Unresolvable class definition for DispatcherServlet's default strategy class [" +  
                  className + "] for interface [" + key + "]", err);  
         }  
      }  
      return strategies;  
   }  
   else {  
      return Collections.emptyList();  
   }  
}

2.2 獲取HandlerExecutionChain

DispatcherServlet會根據請求,從對應的HandlerMapping中獲取HandlerExecutionChainorg.springframework.web.servlet.DispatcherServlet#getHandler

/**  
 * Return the HandlerExecutionChain for this request. * <p>Tries all handler mappings in order.  
 * @param request current HTTP request  
 * @return the HandlerExecutionChain, or {@code null} if no handler could be found  
 */@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;  
}

在這個過程中,org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler會完成以下幾個功能:

  1. HandlerMapping實現類中獲取對映的handler。
  2. 新增配置攔截器
  3. 新增跨域攔截器
/**  
 * Look up a handler for the given request, falling back to the default * handler if no specific one is found. * @param request current HTTP request  
 * @return the corresponding handler instance, or the default handler  
 * @see #getHandlerInternal  
 */@Override  
@Nullable  
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {  
	// 1、從HandlerMapping實現類中獲取對映的handler
   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);  
   }  
  
   // Ensure presence of cached lookupPath for interceptors and others  
   if (!ServletRequestPathUtils.hasCachedPath(request)) {  
      initLookupPath(request);  
   }  
	// 2、新增配置的攔截器
   HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);  
  
   if (logger.isTraceEnabled()) {  
      logger.trace("Mapped to " + handler);  
   }  
   else if (logger.isDebugEnabled() && !DispatcherType.ASYNC.equals(request.getDispatcherType())) {  
      logger.debug("Mapped to " + executionChain.getHandler());  
   }  
	// 3、新增跨域攔截器
   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;  
}

org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandlerInternal是一個抽象方法,由子類實現,並制定各自的對映規則:

protected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception;

org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandlerExecutionChain方法會新增配置好的攔截器:

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {  
   HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?  
         (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));  
  
   for (HandlerInterceptor interceptor : this.adaptedInterceptors) {  
      if (interceptor instanceof MappedInterceptor) {  
         MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;  
         if (mappedInterceptor.matches(request)) {  
            chain.addInterceptor(mappedInterceptor.getInterceptor());  
         }  
      }  
      else {  
         chain.addInterceptor(interceptor);  
      }  
   }  
   return chain;  
}

這裡的攔截器來Spring Boot新增的預設攔截器,以及使用者自定義攔截器:

@Configuration  
@EnableWebMvc  
public class WebMvcConfig implements WebMvcConfigurer {  
    @Override  
    public void addInterceptors(InterceptorRegistry registry) {  
        // 新增自定義攔截器  
    }  
}

跨域攔截器會根據是否新增跨域配置進行判斷,這裡的跨域配置來自於WebMvcConfigurer/@CrossOrigin/CorsConfigurationSource

2.3 執行HandlerExecutionChain

HandlerExecutionChain中包含handlerinterceptorList
DispatcherServlet會按順序依次執行:

  1. interceptorListpreHandle方法
  2. 使用handlerApdater呼叫handler
  3. interceptorListpostHandle方法
  4. interceptorListafterCompletion方法

3 RequestMappingHandlerMapping

RequestMappingHandlerMapping是針對@Controller@RequestMapping的實現類,是日常專案中使用最多的請求地址對映器。

3.1 初始化:註冊bean

org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#requestMappingHandlerMapping

@Bean  
@SuppressWarnings("deprecation")  
public RequestMappingHandlerMapping requestMappingHandlerMapping(  
      @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,  
      @Qualifier("mvcConversionService") FormattingConversionService conversionService,  
      @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {  
  
   RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();  
   mapping.setOrder(0);  
   mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));  
   mapping.setContentNegotiationManager(contentNegotiationManager);  
   mapping.setCorsConfigurations(getCorsConfigurations());  
  
   PathMatchConfigurer pathConfig = getPathMatchConfigurer();  
   if (pathConfig.getPatternParser() != null) {  
      mapping.setPatternParser(pathConfig.getPatternParser());  
   }  
   else {  
      mapping.setUrlPathHelper(pathConfig.getUrlPathHelperOrDefault());  
      mapping.setPathMatcher(pathConfig.getPathMatcherOrDefault());  
  
      Boolean useSuffixPatternMatch = pathConfig.isUseSuffixPatternMatch();  
      if (useSuffixPatternMatch != null) {  
         mapping.setUseSuffixPatternMatch(useSuffixPatternMatch);  
      }  
      Boolean useRegisteredSuffixPatternMatch = pathConfig.isUseRegisteredSuffixPatternMatch();  
      if (useRegisteredSuffixPatternMatch != null) {  
         mapping.setUseRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch);  
      }  
   }  
   Boolean useTrailingSlashMatch = pathConfig.isUseTrailingSlashMatch();  
   if (useTrailingSlashMatch != null) {  
      mapping.setUseTrailingSlashMatch(useTrailingSlashMatch);  
   }  
   if (pathConfig.getPathPrefixes() != null) {  
      mapping.setPathPrefixes(pathConfig.getPathPrefixes());  
   }  
  
   return mapping;  
}

SpringMVC攔截器HandlerInterceptor初始化:

mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));

org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#getInterceptors

/**  
 * Provide access to the shared handler interceptors used to configure * {@link HandlerMapping} instances with.  
 * <p>This method cannot be overridden; use {@link #addInterceptors} instead.  
 */protected final Object[] getInterceptors(  
      FormattingConversionService mvcConversionService,  
      ResourceUrlProvider mvcResourceUrlProvider) {  
  
   if (this.interceptors == null) {  
      InterceptorRegistry registry = new InterceptorRegistry();  
      addInterceptors(registry);  
      registry.addInterceptor(new ConversionServiceExposingInterceptor(mvcConversionService));  
      registry.addInterceptor(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider));  
      this.interceptors = registry.getInterceptors();  
   }  
   return this.interceptors.toArray();  
}

org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#addInterceptors作為擴充套件點,支援新增自定義攔截器:

@Configuration  
@EnableWebMvc  
public class WebMvcConfig implements WebMvcConfigurer {  
    @Override  
    public void addInterceptors(InterceptorRegistry registry) {  
	    // 新增自定義攔截器
     }  
}

SpringMVC內容協商管理器ContentNegotiationManager初始化:

mapping.setContentNegotiationManager(contentNegotiationManager);

org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#mvcContentNegotiationManager

/**  
 * Return a {@link ContentNegotiationManager} instance to use to determine  
 * requested {@linkplain MediaType media types} in a given request.  
 */@Bean  
public ContentNegotiationManager mvcContentNegotiationManager() {  
   if (this.contentNegotiationManager == null) {  
      ContentNegotiationConfigurer configurer = new ContentNegotiationConfigurer(this.servletContext);  
      configurer.mediaTypes(getDefaultMediaTypes());  
      configureContentNegotiation(configurer);  
      this.contentNegotiationManager = configurer.buildContentNegotiationManager();  
   }  
   return this.contentNegotiationManager;  
}

org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#configureContentNegotiation作為擴充套件點,支援新增自定義內容協商管理器:

@Configuration  
@EnableWebMvc  
public class WebMvcConfig implements WebMvcConfigurer {  
    @Override  
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {  
        // 新增自定義內容協商管理器  
    }  
}

SpringMVC跨域配置CorsConfiguration初始化:

mapping.setCorsConfigurations(getCorsConfigurations());

org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#getCorsConfigurations

/**  
 * Return the registered {@link CorsConfiguration} objects,  
 * keyed by path pattern. * @since 4.2 */protected final Map<String, CorsConfiguration> getCorsConfigurations() {  
   if (this.corsConfigurations == null) {  
      CorsRegistry registry = new CorsRegistry();  
      addCorsMappings(registry);  
      this.corsConfigurations = registry.getCorsConfigurations();  
   }  
   return this.corsConfigurations;  
}

org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#addCorsMappings作為擴充套件點,支援新增自定義跨域配置:

@Configuration  
@EnableWebMvc  
public class WebMvcConfig implements WebMvcConfigurer {  
  
    @Override  
    public void addCorsMappings(CorsRegistry registry) {  
        // 新增自定義跨域配置
    }
}

SpringMVC地址解析器相關初始化:

PathMatchConfigurer pathConfig = getPathMatchConfigurer();  
if (pathConfig.getPatternParser() != null) {  
   mapping.setPatternParser(pathConfig.getPatternParser());  
}  
else {  
   mapping.setUrlPathHelper(pathConfig.getUrlPathHelperOrDefault());  
   mapping.setPathMatcher(pathConfig.getPathMatcherOrDefault());  
  
   Boolean useSuffixPatternMatch = pathConfig.isUseSuffixPatternMatch();  
   if (useSuffixPatternMatch != null) {  
      mapping.setUseSuffixPatternMatch(useSuffixPatternMatch);  
   }  
   Boolean useRegisteredSuffixPatternMatch = pathConfig.isUseRegisteredSuffixPatternMatch();  
   if (useRegisteredSuffixPatternMatch != null) {  
      mapping.setUseRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch);  
   }  
}  
Boolean useTrailingSlashMatch = pathConfig.isUseTrailingSlashMatch();  
if (useTrailingSlashMatch != null) {  
   mapping.setUseTrailingSlashMatch(useTrailingSlashMatch);  
}  
if (pathConfig.getPathPrefixes() != null) {  
   mapping.setPathPrefixes(pathConfig.getPathPrefixes());  
}

org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#getPathMatchConfigurer

/**  
 * Callback for building the {@link PathMatchConfigurer}.  
 * Delegates to {@link #configurePathMatch}. * @since 4.1 */protected PathMatchConfigurer getPathMatchConfigurer() {  
   if (this.pathMatchConfigurer == null) {  
      this.pathMatchConfigurer = new PathMatchConfigurer();  
      configurePathMatch(this.pathMatchConfigurer);  
   }  
   return this.pathMatchConfigurer;  
}

org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#configurePathMatch作為擴充套件點,支援新增自定義地址解析器:

@Configuration  
@EnableWebMvc  
public class WebMvcConfig implements WebMvcConfigurer {  
    @Override  
    public void configurePathMatch(PathMatchConfigurer configurer) {  
        // 新增自定義地址解析器  
    }  
}

3.2 初始化:請求地址對映掃描

org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#afterPropertiesSet,實現InitializingBean介面:

/**  
 * Detects handler methods at initialization. * @see #initHandlerMethods */@Override  
public void afterPropertiesSet() {  
   initHandlerMethods();  
}

org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#initHandlerMethods

/**  
 * Scan beans in the ApplicationContext, detect and register handler methods. 
 * @see #getCandidateBeanNames() 
 * @see #processCandidateBean 
 * @see #handlerMethodsInitialized 
 */
 protected void initHandlerMethods() { 
	// 1、遍歷所有bean 
   for (String beanName : getCandidateBeanNames()) {  
      if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {  
		// 2、請求地址對映處理
         processCandidateBean(beanName);  
      }  
   }   
   handlerMethodsInitialized(getHandlerMethods());  
}

org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#processCandidateBean

/**  
 * Determine the type of the specified candidate bean and call 
 * {@link #detectHandlerMethods} if identified as a handler type.  
 * <p>This implementation avoids bean creation through checking  
 * {@link org.springframework.beans.factory.BeanFactory#getType}  
 * and calling {@link #detectHandlerMethods} with the bean name.  
 * @param beanName the name of the candidate bean  
 * @since 5.1 
 * @see #isHandler 
 * @see #detectHandlerMethods 
 */
 protected void processCandidateBean(String beanName) {  
   Class<?> beanType = null;  
   try {  
		// 1、獲取bean的類物件
      beanType = obtainApplicationContext().getType(beanName);  
   }  
   catch (Throwable ex) {  
      // An unresolvable bean type, probably from a lazy bean - let's ignore it.  
      if (logger.isTraceEnabled()) {  
         logger.trace("Could not resolve type for bean '" + beanName + "'", ex);  
      }  
   }   
   // 2、判斷當前HandlerMapping實現類能否處理當前bean
   if (beanType != null && isHandler(beanType)) { 
	   // 3、解析&對映請求地址 
      detectHandlerMethods(beanName);  
   }  
}

isHandler()由子類實現,用來判斷該類物件是否需要進行請求地址解析。例如,org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#isHandler

/**  
 * {@inheritDoc}  
 * <p>Expects a handler to have either a type-level @{@link Controller}  
 * annotation or a type-level @{@link RequestMapping} annotation.  
 */@Override  
protected boolean isHandler(Class<?> beanType) {  
   return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||  
         AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));  
}

org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#detectHandlerMethods

/**  
 * Look for handler methods in the specified handler bean. * @param handler either a bean name or an actual handler instance  
 * @see #getMappingForMethod */protected void detectHandlerMethods(Object handler) {  
   Class<?> handlerType = (handler instanceof String ?  
         obtainApplicationContext().getType((String) handler) : handler.getClass());  
  
   if (handlerType != null) {  
      Class<?> userType = ClassUtils.getUserClass(handlerType); 
      // 1、遍歷Handler類的方法
      Map<Method, T> methods = MethodIntrospector.selectMethods(userType,  
            (MethodIntrospector.MetadataLookup<T>) method -> {  
               try {  
	               // 2、構造請求地址對映
                  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));  
      }  
      else if (mappingsLogger.isDebugEnabled()) {  
         mappingsLogger.debug(formatMappings(userType, methods));  
      }  
      methods.forEach((method, mapping) -> {  
	      // 3、獲取實際能夠執行的方法
         Method invocableMethod = AopUtils.selectInvocableMethod(method, userType); 
         // 4、儲存請求地址對映資訊 
         registerHandlerMethod(handler, invocableMethod, mapping);  
      });  
   }  
}

org.springframework.core.MethodIntrospector#selectMethods(java.lang.Class<?>, org.springframework.core.MethodIntrospector.MetadataLookup<T>)

/**  
 * Select methods on the given target type based on the lookup of associated metadata. * <p>Callers define methods of interest through the {@link MetadataLookup} parameter,  
 * allowing to collect the associated metadata into the result map. * @param targetType the target type to search methods on  
 * @param metadataLookup a {@link MetadataLookup} callback to inspect methods of interest,  
 * returning non-null metadata to be associated with a given method if there is a match, * or {@code null} for no match  
 * @return the selected methods associated with their metadata (in the order of retrieval), * or an empty map in case of no match */public static <T> Map<Method, T> selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup) {  
   final Map<Method, T> methodMap = new LinkedHashMap<>();  
   Set<Class<?>> handlerTypes = new LinkedHashSet<>();  
   Class<?> specificHandlerType = null;  
  
   if (!Proxy.isProxyClass(targetType)) {  
      specificHandlerType = ClassUtils.getUserClass(targetType);  
      handlerTypes.add(specificHandlerType);  
   }  
   handlerTypes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetType));  
  
   for (Class<?> currentHandlerType : handlerTypes) {  
      final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType);  
  
      ReflectionUtils.doWithMethods(currentHandlerType, method -> {  
         Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);  
         T result = metadataLookup.inspect(specificMethod);  
         if (result != null) {  
            Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);  
            if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) {  
               methodMap.put(specificMethod, result);  
            }  
         }      }, ReflectionUtils.USER_DECLARED_METHODS);  
   }  
  
   return methodMap;  
}

getMappingForMethod()由子類實現,用來指定不同的解析規則。例如,org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#getMappingForMethod

/**  
 * Uses method and type-level @{@link RequestMapping} annotations to create  
 * the RequestMappingInfo. * @return the created RequestMappingInfo, or {@code null} if the method  
 * does not have a {@code @RequestMapping} annotation.  
 * @see #getCustomMethodCondition(Method)  
 * @see #getCustomTypeCondition(Class)  
 */@Override  
@Nullable  
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {  
	// 1、獲取方法級別的地址資訊
   RequestMappingInfo info = createRequestMappingInfo(method);  
   if (info != null) {  
	   // 2、獲取類級別的地址資訊
      RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);  
      if (typeInfo != null) {  
	      // 3、合併完整地址資訊
         info = typeInfo.combine(info);  
      }  
      String prefix = getPathPrefix(handlerType);  
      if (prefix != null) {  
         info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);  
      }  
   }   return info;  
}

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#createRequestMappingInfo(org.springframework.web.bind.annotation.RequestMapping, org.springframework.web.servlet.mvc.condition.RequestCondition<?>)

/**  
 * Create a {@link RequestMappingInfo} from the supplied  
 * {@link RequestMapping @RequestMapping} annotation, which is either  
 * a directly declared annotation, a meta-annotation, or the synthesized * result of merging annotation attributes within an annotation hierarchy. */protected RequestMappingInfo createRequestMappingInfo(  
      RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {  
  // 從註解中獲取資訊,封裝成物件
   RequestMappingInfo.Builder builder = RequestMappingInfo  
         .paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))  
         .methods(requestMapping.method())  
         .params(requestMapping.params())  
         .headers(requestMapping.headers())  
         .consumes(requestMapping.consumes())  
         .produces(requestMapping.produces())  
         .mappingName(requestMapping.name());  
   if (customCondition != null) {  
      builder.customCondition(customCondition);  
   }  
   return builder.options(this.config).build();  
}

org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#registerHandlerMethod

/**  
 * Register a handler method and its unique mapping. Invoked at startup for * each detected handler method. * @param handler the bean name of the handler or the handler instance  
 * @param method the method to register  
 * @param mapping the mapping conditions associated with the handler method  
 * @throws IllegalStateException if another method was already registered  
 * under the same mapping */protected void registerHandlerMethod(Object handler, Method method, T mapping) {  
   this.mappingRegistry.register(mapping, handler, method);  
}

org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#register

public void register(T mapping, Object handler, Method method) {  
   this.readWriteLock.writeLock().lock();  
   try {  
      HandlerMethod handlerMethod = createHandlerMethod(handler, method);  
      validateMethodMapping(handlerMethod, mapping);  
  
      Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);  
      for (String path : directPaths) {  
         this.pathLookup.add(path, mapping);  
      }  
  
      String name = null;  
      if (getNamingStrategy() != null) {  
         name = getNamingStrategy().getName(handlerMethod, mapping);  
         addMappingName(name, handlerMethod);  
      }  
		// @CrossOrigin配置資訊初始化
      CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);  
      if (corsConfig != null) {  
         corsConfig.validateAllowCredentials();  
         this.corsLookup.put(handlerMethod, corsConfig);  
      }  
  
      this.registry.put(mapping,  
            new MappingRegistration<>(mapping, handlerMethod, directPaths, name, corsConfig != null));  
   }  
   finally {  
      this.readWriteLock.writeLock().unlock();  
   }  
}

3.3 處理請求:請求地址對映

org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal

/**  
 * Look up a handler method for the given request. 
 */
@Override  
@Nullable  
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {  
	// 1、解析請求地址
   String lookupPath = initLookupPath(request);  
   this.mappingRegistry.acquireReadLock();  
   try {  
	   // 2、根據請求地址找對映方法
      HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);  
      return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);  
   }  
   finally {  
      this.mappingRegistry.releaseReadLock();  
   }  
}