主頁 > 後端開發 > 精盡Spring MVC原始碼分析 - HandlerMapping 組件(三)之 AbstractHandlerMethodMapping

精盡Spring MVC原始碼分析 - HandlerMapping 組件(三)之 AbstractHandlerMethodMapping

2020-12-17 06:20:41 後端開發

該系列檔案是本人在學習 Spring MVC 的原始碼程序中總結下來的,可能對讀者不太友好,請結合我的原始碼注釋 Spring MVC 原始碼分析 GitHub 地址 進行閱讀

Spring 版本:5.2.4.RELEASE

該系列其他檔案請查看:《精盡 Spring MVC 原始碼分析 - 文章導讀》

HandlerMapping 組件

HandlerMapping 組件,請求的處理器匹配器,負責為請求找到合適的 HandlerExecutionChain 處理器執行鏈,包含處理器(handler)和攔截器們(interceptors

  • handler 處理器是 Object 型別,可以將其理解成 HandlerMethod 物件(例如我們使用最多的 @RequestMapping 注解所標注的方法會決議成該物件),包含了方法的所有資訊,通過該物件能夠執行該方法

  • HandlerInterceptor 攔截器對處理請求進行增強處理,可用于在執行方法前、成功執行方法后、處理完成后進行一些邏輯處理

由于 HandlerMapping 組件涉及到的內容比較多,考慮到內容的排版,所以將這部分內容拆分成了四個模塊,依次進行分析:

  • 《HandlerMapping 組件(一)之 AbstractHandlerMapping》
  • 《HandlerMapping 組件(二)之 HandlerInterceptor 攔截器》
  • 《HandlerMapping 組件(三)之 AbstractHandlerMethodMapping》
  • 《HandlerMapping 組件(四)之 AbstractUrlHandlerMapping》

HandlerMapping 組件(三)之 AbstractHandlerMethodMapping

先來回顧一下HandlerMapping 介面體系的結構:

《HandlerMapping 組件(一)之 AbstractHandlerMapping》檔案中已經分析了 HandlerMapping 組件的 AbstractHandlerMapping 抽象類基類

那么本文就接著來分析圖中紅色框部分的 AbstractHandlerMethodMapping 系,該系是基于 Method 進行匹配,例如,我們所熟知的 @RequestMapping 等注解的方式,一共就三個類,不多??????

涉及到的內容比較多,可以直接查看我的總結

回顧

先來回顧一下在 DispatcherServlet 中處理請求的程序中通過 HandlerMapping 組件,獲取到 HandlerExecutionChain 處理器執行鏈的方法,是通過AbstractHandlerMapping 的 getHandler 方法來獲取的,如下:

@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    // <1> 獲得處理器(HandlerMethod 或者 HandlerExecutionChain),該方法是抽象方法,由子類實作
    Object handler = getHandlerInternal(request);
    // <2> 獲得不到,則使用默認處理器
    // <3> 還是獲得不到,則回傳 null
    // <4> 如果找到的處理器是 String 型別,則從 Spring 容器中找到對應的 Bean 作為處理器
    // <5> 創建 HandlerExecutionChain 物件(包含處理器和攔截器)
    // ... 省略相關代碼
    return executionChain;
}

在 AbstractHandlerMapping 獲取 HandlerExecutionChain 處理器執行鏈的方法中,需要先呼叫 getHandlerInternal(HttpServletRequest request) 抽象方法,獲取請求對應的處理器,該方法由子類去實作,也就上圖中黃色框紅色框兩類子類,本文分析紅色框部分內容

注解

Spring MVC 的請求匹配的注解,體系結構如下:

關于這些注解,大家已經非常熟悉了,各自的屬性就不再進行講述了,可具體查看原始碼:

  • org.springframework.web.bind.annotation.@Mapping

  • org.springframework.web.bind.annotation.@RequestMapping

  • org.springframework.web.bind.annotation.@GetMapping

  • org.springframework.web.bind.annotation.@PostMapping

  • org.springframework.web.bind.annotation.@PutMapping

  • org.springframework.web.bind.annotation.@DeleteMapping

  • org.springframework.web.bind.annotation.@PatchMapping

AbstractHandlerMethodMapping

org.springframework.web.servlet.result.method.AbstractHandlerMethodMapping,實作 InitializingBean 介面,繼承 AbstractHandlerMapping 抽象類,以 Method 方法 作為 Handler 處理器 的 HandlerMapping 抽象類,提供 Mapping 的初始化、注冊等通用的骨架方法,

那么具體是什么呢?AbstractHandlerMethodMapping 定義為了 <T> 泛型,交給子類做決定,例如,子類 RequestMappingInfoHandlerMapping 使用 RequestMappingInfo 類作為 <T> 泛型,也就是我們在上面注解模塊看到的 @RequestMapping 等注解資訊,

構造方法

public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
	/**
	 * 是否只掃描可訪問的 HandlerMethod 們
	 */
	private boolean detectHandlerMethodsInAncestorContexts = false;
	/**
	 * Mapping 命名策略
	 */
	@Nullable
	private HandlerMethodMappingNamingStrategy<T> namingStrategy;
	/**
     * Mapping 注冊表
     */
	private final MappingRegistry mappingRegistry = new MappingRegistry();
}
  • <T> 泛型,就是我們前面要一直提到的 Mapping 型別

  • mappingRegistry:Mapping 注冊表,詳細見下文

  • namingStrategyorg.springframework.web.servlet.handler.HandlerMethodMappingNamingStrategy 介面,HandlerMethod Mapping 名字生成策略介面

    @FunctionalInterface
    public interface HandlerMethodMappingNamingStrategy<T> {
    	/**
    	 * 根據 HandlerMethod 獲取名稱,就是為對應的 Mappring 物件生成一個名稱,便于獲取
    	 */
    	String getName(HandlerMethod handlerMethod, T mapping);
    }
    // org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMethodMappingNamingStrateg.java
    public class RequestMappingInfoHandlerMethodMappingNamingStrategy implements HandlerMethodMappingNamingStrategy<RequestMappingInfo> {
    
    	/** Separator between the type and method-level parts of a HandlerMethod mapping name. */
    	public static final String SEPARATOR = "#";
    
    	@Override
    	public String getName(HandlerMethod handlerMethod, RequestMappingInfo mapping) {
    		// 情況一,mapping 名字非空,則使用 mapping 的名字
    		if (mapping.getName() != null) {
    			return mapping.getName();
    		}
    		// 情況二,使用類名大寫 + "#" + 方法名
    		StringBuilder sb = new StringBuilder();
    		String simpleTypeName = handlerMethod.getBeanType().getSimpleName();
    		for (int i = 0; i < simpleTypeName.length(); i++) {
    			if (Character.isUpperCase(simpleTypeName.charAt(i))) {
    				sb.append(simpleTypeName.charAt(i));
    			}
    		}
    		sb.append(SEPARATOR).append(handlerMethod.getMethod().getName());
    		return sb.toString();
    	}
    
    }
    
    • 情況一,如果 Mapping 已經配置名字,則直接回傳,例如,@RequestMapping(name = "login", value = "https://www.cnblogs.com/lifullmoon/p/user/login") 注解的方法,它對應的 Mapping 的名字就是 login
    • 情況二,如果 Mapping 未配置名字,則使用使用類名大寫 + "#" + 方法名,例如,@RequestMapping(value = "https://www.cnblogs.com/lifullmoon/p/user/login") 注解的方法,假設它所在的類為 UserController ,對應的方法名為 login ,則它對應的 Mapping 的名字就是 USERCONTROLLER#login

MappingRegistry 注冊表

AbstractHandlerMethodMapping 的內部類,Mapping 注冊表

構造方法
class MappingRegistry {
    /**
     * 注冊表
     *
     * Key: Mapping
     * Value:{@link MappingRegistration}(Mapping + HandlerMethod)
     */
    private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
    /**
     * 注冊表2
     *
     * Key:Mapping
     * Value:{@link HandlerMethod}
     */
    private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();
    /**
     * 直接 URL 的映射
     *
     * Key:直接 URL(就是固定死的路徑,而非多個)
     * Value:Mapping 陣列
     */
    private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
    /**
     * Mapping 的名字與 HandlerMethod 的映射
     *
     * Key:Mapping 的名字
     * Value:HandlerMethod 陣列
     */
    private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();

    private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();
    /**
     * 讀寫鎖
     */
    private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
}
  • registry:注冊表,Key: Mapping,即 <T> 泛型;Value:MappingRegistration 物件(Mapping + HandlerMethod)
  • mappingLookup:注冊表2,Key: Mapping,即 <T> 泛型;Value:HandlerMethod 物件
  • urlLookup:直接 URL 的映射,Key:直接 URL(就是固定死的路徑,而非多個);Value:Mapping 陣列
  • nameLookup:Mapping 的名字與 HandlerMethod 的映射,Key:Mapping 的名字;Value:HandlerMethod 陣列
  • readWriteLock:讀寫鎖,為了才操作上述屬性時保證執行緒安全
register

register(T mapping, Object handler, Method method)方法,將 Mapping、Method、handler(方法所在類)之間的映射關系進行注冊,會生成 HandlerMethod 物件,就是處理器物件,方法如下:

public void register(T mapping, Object handler, Method method) {
    // <1> 獲得寫鎖
    this.readWriteLock.writeLock().lock();
    try {
        // <2.1> 創建 HandlerMethod 物件
        HandlerMethod handlerMethod = createHandlerMethod(handler, method);
        // <2.2> 校驗當前 mapping 是否存在對應的 HandlerMethod 物件,如果已存在但不是當前的 handlerMethod 物件則拋出例外
        assertUniqueMethodMapping(handlerMethod, mapping);
        // <2.3> 將 mapping 與 handlerMethod 的映射關系保存至 this.mappingLookup
        this.mappingLookup.put(mapping, handlerMethod);

        // <3.1> 獲得 mapping 對應的普通 URL 陣列
        List<String> directUrls = getDirectUrls(mapping);
        // <3.2> 將 url 和 mapping 的映射關系保存至 this.urlLookup
        for (String url : directUrls) {
            this.urlLookup.add(url, mapping);
        }

        // <4> 初始化 nameLookup
        String name = null;
        if (getNamingStrategy() != null) {
            // <4.1> 獲得 Mapping 的名字
            name = getNamingStrategy().getName(handlerMethod, mapping);
            // <4.2> 將 mapping 的名字與 HandlerMethod 的映射關系保存至 this.nameLookup
            addMappingName(name, handlerMethod);
        }

        // <5> 初始化 CorsConfiguration 配置物件
        CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
        if (corsConfig != null) {
            this.corsLookup.put(handlerMethod, corsConfig);
        }
        // <6> 創建 MappingRegistration 物件
        // 并與 mapping 映射添加到 registry 注冊表中
        this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
    }
    finally {
        // <7> 釋放寫鎖
        this.readWriteLock.writeLock().unlock();
    }
}
  1. 獲得寫鎖

  2. 添加相關映射至Map<T, HandlerMethod> mappingLookup屬性

    1. 呼叫 createHandlerMethod(Object handler, Method method) 方法,創建 HandlerMethod 物件,詳情見下文
    2. 校驗當前 Mapping 是否存在對應的 HandlerMethod 物件,如果已存在但不是前一步創建 HandlerMethod 物件則拋出例外,保證唯一性
    3. 將 Mapping 與 HandlerMethod 的映射關系保存至 mappingLookup
  3. 添加相關映射至MultiValueMap<String, T> urlLookup屬性

    1. 呼叫 getDirectUrls 方法,獲得 Mapping 對應的直接 URL 陣列,如下:

      private List<String> getDirectUrls(T mapping) {
          List<String> urls = new ArrayList<>(1);
          // 遍歷 Mapping 對應的路徑
          for (String path : getMappingPathPatterns(mapping)) {
              // 非**模式**路徑
              if (!getPathMatcher().isPattern(path)) {
                  urls.add(path);
              }
          }
          return urls;
      }
      
      • 例如,@RequestMapping("/user/login") 注解對應的路徑,就是直接路徑
      • 例如,@RequestMapping("/user/${id}") 注解對應的路徑,不是直接路徑,因為不確定性
    2. 將 url 和 Mapping 的映射關系保存至 urlLookup

  4. 添加相關映射至Map<String, List<HandlerMethod>> nameLookup屬性

    1. 呼叫 HandlerMethodMappingNamingStrategy#getName(HandlerMethod handlerMethod, T mapping) 方法,獲得 Mapping 的名字

    2. 呼叫 addMappingName(String name, HandlerMethod handlerMethod) 方法,添加 Mapping 的名字 + HandlerMethod 至 nameLookup,如下:

      private void addMappingName(String name, HandlerMethod handlerMethod) {
          // 獲得 Mapping 的名字,對應的 HandlerMethod 陣列
          List<HandlerMethod> oldList = this.nameLookup.get(name);
          if (oldList == null) {
              oldList = Collections.emptyList();
          }
          // 如果已經存在,則不用添加
          for (HandlerMethod current : oldList) {
              if (handlerMethod.equals(current)) {
                  return;
              }
          }
          // 添加到 nameLookup 中
          List<HandlerMethod> newList = new ArrayList<>(oldList.size() + 1);
          newList.addAll(oldList);
          newList.add(handlerMethod);
          this.nameLookup.put(name, newList);
      }
      

      和已有的進行合并,說明名稱不是唯一哦

  5. 初始化 CorsConfiguration 配置物件,暫時忽略

  6. 創建 MappingRegistration 物件,并和 Mapping 進行映射添加至 registry

  7. 釋放寫鎖

unregister

unregister(T mapping) 方法,取消上面方法注冊的相關資訊,方法如下:

public void unregister(T mapping) {
// 獲得寫鎖
this.readWriteLock.writeLock().lock();
try {
    // 從 registry 中移除
    MappingRegistration<T> definition = this.registry.remove(mapping);
    if (definition == null) {
        return;
    }

    // 從 mappingLookup 中移除
    this.mappingLookup.remove(definition.getMapping());

    // 從 urlLookup 移除
    for (String url : definition.getDirectUrls()) {
        List<T> list = this.urlLookup.get(url);
        if (list != null) {
            list.remove(definition.getMapping());
            if (list.isEmpty()) {
                this.urlLookup.remove(url);
            }
        }
    }

    // 從 nameLookup 移除
    removeMappingName(definition);

    // 從 corsLookup 中移除
    this.corsLookup.remove(definition.getHandlerMethod());
}
finally {
    // 釋放寫鎖
    this.readWriteLock.writeLock().unlock();
}

register 方法邏輯相反,依次移除相關映射

createHandlerMethod

createHandlerMethod(Object handler, Method method)方法,創建 Method 對應的 HandlerMethod 物件

protected HandlerMethod createHandlerMethod(Object handler, Method method) {
    HandlerMethod handlerMethod;
    // <1> 如果 handler 型別為 String, 說明對應一個 Bean 物件的名稱
    // 例如 UserController 使用 @Controller 注解后,默認入參 handler 就是它的 beanName ,即 `userController`
    if (handler instanceof String) {
        String beanName = (String) handler;
        handlerMethod = new HandlerMethod(beanName, obtainApplicationContext().getAutowireCapableBeanFactory(), method);
    }
    // <2> 如果 handler 型別非 String ,說明是一個已經是一個 handler 物件,就無需處理,直接創建 HandlerMethod 物件
    else {
        handlerMethod = new HandlerMethod(handler, method);
    }
    return handlerMethod;
}
  1. 如果 handler 型別為 String, 說明對應一個 Bean 物件的名稱,例如 UserController 使用 @Controller 注解后,默認入參 handler 就是它的 beanName ,即 userController
  2. 如果 handler 型別非 String ,說明是一個已經是一個 handler 物件,就無需處理,直接創建 HandlerMethod 物件

所以你會發現 HandlerMethod 處理器物件,就是handler(方法所在類)+method(方法物件)的組合,通過它能執行該方法

HandlerMethod 處理器

org.springframework.web.method.HandlerMethod,處理器物件,也就是某個方法的封裝物件(Method+所在類的 Bean 物件),有以下屬性:

public class HandlerMethod {
	/**
	 * Bean 物件
	 */
	private final Object bean;
	@Nullable
	private final BeanFactory beanFactory;
	/**
	 * Bean 的型別
	 */
	private final Class<?> beanType;
	/**
	 * 方法物件
	 */
	private final Method method;
	/**
	 * {@link #method} 的橋接方法
	 * 存在泛型型別,編譯器則會自動生成一個橋接方法(java1.5向后兼容)
	 */
	private final Method bridgedMethod;
	/**
	 * 方法的引數型別陣列
	 */
	private final MethodParameter[] parameters;
	/**
	 * 回應的狀態碼,即 {@link ResponseStatus#code()}
	 */
	@Nullable
	private HttpStatus responseStatus;
	/**
	 * 回應的狀態碼原因,即 {@link ResponseStatus#reason()}
	 */
	@Nullable
	private String responseStatusReason;
	/**
	 * 決議自哪個 HandlerMethod 物件
	 *
	 * 僅構造方法中傳入 HandlerMethod 型別的引數適用,例如 {@link #HandlerMethod(HandlerMethod)}
	 */
	@Nullable
	private HandlerMethod resolvedFromHandlerMethod;
	/**
	 * 父介面的方法的引數注解陣列
	 */
	@Nullable
	private volatile List<Annotation[][]> interfaceParameterAnnotations;
}

根據上面的注釋理解上面的屬性,包含該方法的所有資訊

它的建構式非常多,不過原理都差不多,我們挑兩個來看看

HandlerMethod(String beanName, BeanFactory beanFactory, Method method) 構造方法

對應 createHandlerMethod(Object handler, Method method)方法的 <1>,代碼如下:

public HandlerMethod(String beanName, BeanFactory beanFactory, Method method) {
    Assert.hasText(beanName, "Bean name is required");
    Assert.notNull(beanFactory, "BeanFactory is required");
    Assert.notNull(method, "Method is required");
    // <1> 將 beanName 賦值給 bean 屬性,說明 beanFactory + bean 的方式,獲得 handler 物件
    this.bean = beanName;
    this.beanFactory = beanFactory;
    // <2> 初始化 beanType 屬性
    Class<?> beanType = beanFactory.getType(beanName);
    if (beanType == null) {
        throw new IllegalStateException("Cannot resolve bean type for bean with name '" + beanName + "'");
    }
    this.beanType = ClassUtils.getUserClass(beanType);
    // <3> 初始化 method、bridgedMethod 屬性
    this.method = method;
    // 如果不是橋接方法則直接為該方法
    this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
    // <4> 初始化 parameters 屬性,決議該方法(或者橋接方法)的引數型別
    this.parameters = initMethodParameters();
    // <5> 初始化 responseStatus、responseStatusReason 屬性,通過 @ResponseStatus 注解
    evaluateResponseStatus();
}
  1. beanName 賦值給 bean 屬性,說明 beanFactory + bean 的方式,獲得 handler 物件

  2. 初始化 beanType 屬性

  3. 初始化 methodbridgedMethod 屬性

  4. 初始化 parameters 屬性,決議該方法(或者橋接方法)的引數型別,呼叫 initMethodParameters() 方法,如下:

    private MethodParameter[] initMethodParameters() {
        int count = this.bridgedMethod.getParameterCount();
        // 創建 MethodParameter 陣列
        MethodParameter[] result = new MethodParameter[count];
        // 遍歷 bridgedMethod 方法的引數,逐個決議它的引數型別
        for (int i = 0; i < count; i++) {
            HandlerMethodParameter parameter = new HandlerMethodParameter(i);
            GenericTypeResolver.resolveParameterType(parameter, this.beanType);
            result[i] = parameter;
        }
        return result;
    }
    
  5. 初始化 responseStatusresponseStatusReason 屬性,通過 @ResponseStatus 注解

關于橋接方法呢,是 java1.5 引入泛型向后兼容的一種方法,具體可參考Effects of Type Erasure and Bridge Methods

HandlerMethod(Object bean, Method method) 構造方法

對應 createHandlerMethod(Object handler, Method method)方法的 <2>,代碼如下:

public HandlerMethod(Object bean, Method method) {
    Assert.notNull(bean, "Bean is required");
    Assert.notNull(method, "Method is required");
    // <1> 初始化 Bean
    this.bean = bean;
    this.beanFactory = null;
    // <2> 初始化 beanType 屬性
    this.beanType = ClassUtils.getUserClass(bean);
    // <3> 初始化 method、bridgedMethod 屬性
    this.method = method;
    // 如果不是橋接方法則直接為該方法
    this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
    // <4> 初始化 parameters 屬性,決議該方法(或者橋接方法)的引數型別
    this.parameters = initMethodParameters();
    // <5> 初始化 responseStatus、responseStatusReason 屬性,通過 @ResponseStatus 注解
    evaluateResponseStatus();
}

和上面的構造方法差不多,不同的是這里的 bean 物件就是方法所在類的 Bean 物件

MappingRegistration 注冊登記

AbstractHandlerMethodMapping 的私有靜態內部類,Mapping 的注冊登記資訊,包含 Mapiing、HandlerMethod、直接 URL 路徑、Mapping 名稱,代碼如下:

private static class MappingRegistration<T> {
    /**
     * Mapping 物件
     */
    private final T mapping;
    /**
     * HandlerMethod 物件
     */
    private final HandlerMethod handlerMethod;
    /**
     * 直接 URL 陣列(就是固定死的路徑,而非多個)
     */
    private final List<String> directUrls;
    /**
     * {@link #mapping} 的名字
     */
    @Nullable
    private final String mappingName;
    
    public MappingRegistration(T mapping, HandlerMethod handlerMethod,
            @Nullable List<String> directUrls, @Nullable String mappingName) {
        Assert.notNull(mapping, "Mapping must not be null");
        Assert.notNull(handlerMethod, "HandlerMethod must not be null");
        this.mapping = mapping;
        this.handlerMethod = handlerMethod;
        this.directUrls = (directUrls != null ? directUrls : Collections.emptyList());
        this.mappingName = mappingName;
    }
}

很簡單,就是保存了 Mapping 注冊時的一些資訊

1.afterPropertiesSet 初始化方法

因為 AbstractHandlerMethodMapping 實作了 InitializingBean 介面,在 Sping 初始化該 Bean 的時候,會呼叫該方法,完成一些初始化作業,方法如下:

@Override
public void afterPropertiesSet() {
    // <x> 初始化處理器的方法們
    initHandlerMethods();
}

protected void initHandlerMethods() {
    // <1> 遍歷 Bean ,逐個處理
    for (String beanName : getCandidateBeanNames()) {
        // 排除目標代理類,AOP 相關,可查看注釋
        if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
            // <2> 處理 Bean
            processCandidateBean(beanName);
        }
    }
    // <3> 初始化處理器的方法們,目前是空方法,暫無具體的實作
    handlerMethodsInitialized(getHandlerMethods());
}
  1. 呼叫 getCandidateBeanNames() 方法,獲取到 Spring 背景關系中所有為 Object 型別的 Bean 的名稱集合,然后進行遍歷,方法如下:

    protected String[] getCandidateBeanNames() {
        // 獲取背景關系中所有的 Bean 的名稱
        return (this.detectHandlerMethodsInAncestorContexts ?
                BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class)
                : obtainApplicationContext().getBeanNamesForType(Object.class));
    }
    

    detectHandlerMethodsInAncestorContexts:是否只掃描可訪問的 HandlerMethod 們,默認false

  2. 呼叫 processCandidateBean(String beanName) 方法,處理每個符合條件的 Bean 物件(例如有 @Controller 或者 @RequestMapping 注解的 Bean),決議這些 Bean 下面相應的方法,往 MappingRegistry 注冊,詳情見下文

  3. 呼叫 handlerMethodsInitialized(Map<T, HandlerMethod> handlerMethods) 方法,初始化 HandlerMethod 處理器們,空方法,暫無子類實作

2.processCandidateBean

processCandidateBean(String beanName)方法,處理符合條件的 Bean 物件,決議其相應的方法,往 MappingRegistry 注冊,方法如下:

protected void processCandidateBean(String beanName) {
    // <1> 獲得 Bean 對應的 Class 物件
    Class<?> beanType = null;
    try {
        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> 判斷 Bean 是否為處理器(例如有 @Controller 或者 @RequestMapping 注解)
    if (beanType != null && isHandler(beanType)) {
        // <3> 掃描處理器方法
        detectHandlerMethods(beanName);
    }
}
  1. 獲得 Bean 對應的 Class 物件
  2. 呼叫 isHandler(Class<?> beanType) 抽象方法,判斷 Bean 的型別是否需要處理,其 RequestMappingHandlerMapping 子類的實作:有 @Controller 或者 @RequestMapping 注解的 Bean
  3. 呼叫 detectHandlerMethods(Object handler) 方法,掃描 Bean 的方法進行處理

3.detectHandlerMethods

detectHandlerMethods(Object handler)方法,初始化 Bean 下面的方法們為 HandlerMethod 物件,并注冊到 MappingRegistry 注冊表中,代碼如下:

protected void detectHandlerMethods(Object handler) {
    // <1> 獲得 Bean 對應的 Class 物件
    Class<?> handlerType = (handler instanceof String ? obtainApplicationContext().getType((String) handler) : handler.getClass());
    if (handlerType != null) {
        // <2> 獲得真實的 Class 物件,因為 `handlerType` 可能是代理類
        Class<?> userType = ClassUtils.getUserClass(handlerType);
        // <3> 獲得匹配的方法和對應的 Mapping 物件
        Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
                (MethodIntrospector.MetadataLookup<T>) method -> {
                    try {
                        // 創建該方法對應的 Mapping 物件,例如根據 @RequestMapping 注解創建 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));
        }
        // <4> 遍歷方法,逐個注冊 HandlerMethod
        methods.forEach((method, mapping) -> {
            Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
            registerHandlerMethod(handler, invocableMethod, mapping);
        });
    }
}
  1. 獲得 Bean 對應的 Class 物件 handlerType

  2. 呼叫getUserClass(Class<?> clazz)方法,獲得真實的 Class 物件,因為 handlerType 可能是代理類,如下:

    public static Class<?> getUserClass(Class<?> clazz) {
        // 如果 Class 物件的名稱包含 "$$",則是 CG_CLASS 代理類,則獲取其父類
        if (clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) {
            Class<?> superclass = clazz.getSuperclass();
            if (superclass != null && superclass != Object.class) {
                return superclass;
            }
        }
        return clazz;
    }
    
  3. 獲得匹配的方法和對應的 Mapping 物件,其中泛型 T,也就是 Mapping 物件,需要通過 getMappingForMethod(Method method, Class<?> handlerType) 抽象方法回傳,其 RequestMappingHandlerMapping 子類的實作,根據 @RequestMapping 注解為方法創建 RequestMappingInfo 物件

    所以這里的 Mapping 物件就是 RequestMappingInfo 物件

  4. 遍歷方法(方法與 Mapping 一一映射了),呼叫registerHandlerMethod(Object handler, Method method, T mapping),逐個注冊對應的 HandlerMethod 物件,如下:

    protected void registerHandlerMethod(Object handler, Method method, T mapping) {
        this.mappingRegistry.register(mapping, handler, method);
    }
    

    也就是上面 MappingRegistry 的 register 方法,已經分析過了??

到這里我們已經分析完了 AbstractHandlerMethodMapping 的初始化作業,部分細節在子類中實作

大致邏輯:掃描有 @Controller 或者 @RequestMapping 注解的類下面的方法,如果方法上面有 @RequestMapping 注解,則會為該方法創建對應的 RequestMappingInfo 物件

將所有的 RequestMappingInfo 物件和 Method 以及方法所在類,往 MappingRegistry 進行注冊,會生成 HandlerMethod 處理器(Method 所有資訊)物件

這樣一來,當 Spring MVC 的 DispatcherServlet 處理請求的時候,獲取到對應的 HandlerMethod 處理器,就可以通過反射執行對應的方法了

到這里,思路是不是越來越清晰了,我們繼續往下分析

【重點】getHandlerInternal

由于上面初始化涉及到內容有點多,先回到本文上面的回顧這一小節,通過 AbstractHandlerMappinggetHandler(HttpServletRequest request) 方法獲取 HandlerExecutionChain 處理器執行鏈時,需要呼叫 getHandlerInternal 抽象方法獲取處理器,這個方法由子類去實作,就到這里了

getHandlerInternal(ServerWebExchange exchange)方法,獲得請求對應的 HandlerMethod 處理器物件,方法如下:

@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    // <1> 獲得請求的路徑
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    // <2> 獲得讀鎖
    this.mappingRegistry.acquireReadLock();
    try {
        // <3> 獲得 HandlerMethod 物件
        HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
        // <4> 進一步,獲得一個新的 HandlerMethod 物件
        return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
    }
    finally {
        // <5> 釋放讀鎖
        this.mappingRegistry.releaseReadLock();
    }
}
  1. 獲得請求路徑

  2. 獲得讀鎖

  3. 呼叫 lookupHandlerMethod(ServerWebExchange exchange) 方法,獲得請求對應的 HandlerMethod 處理器物件,詳情見下文

  4. 如果獲得到 HandlerMethod 物件,則呼叫 HandlerMethod#createWithResolvedBean() 方法,進一步,獲得 HandlerMethod 物件,如下:

    public HandlerMethod createWithResolvedBean() {
        Object handler = this.bean;
        // 如果是 bean 是 String 型別,則獲取對應的 Bean,因為創建該物件時 bean 可能是對應的 beanName
        if (this.bean instanceof String) {
            Assert.state(this.beanFactory != null, "Cannot resolve bean name without BeanFactory");
            String beanName = (String) this.bean;
            handler = this.beanFactory.getBean(beanName);
        }
        return new HandlerMethod(this, handler);
    }
    
  5. 釋放讀鎖

lookupHandlerMethod 獲取處理器方法

lookupHandlerMethod(ServerWebExchange exchange)方法,獲得請求對應的 HandlerMethod 處理器物件,方法如下:

@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
    // <1> Match 陣列,存盤匹配上當前請求的結果(Mapping + HandlerMethod)
    List<Match> matches = new ArrayList<>();
    // <1.1> 優先,基于直接 URL (就是固定死的路徑,而非多個)的 Mapping 們,進行匹配
    List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
    if (directPathMatches != null) {
        addMatchingMappings(directPathMatches, matches, request);
    }
    // <1.2> 其次,掃描注冊表的 Mapping 們,進行匹配
    if (matches.isEmpty()) {
        // No choice but to go through all mappings...
        addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
    }

    // <2> 如果匹配到,則獲取最佳匹配的 Match 結果的 `HandlerMethod`屬性
    if (!matches.isEmpty()) {
        // <2.1> 創建 MatchComparator 物件,排序 matches 結果,排序器
        Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
        matches.sort(comparator);
        // <2.2> 獲得首個 Match 物件,也就是最匹配的
        Match bestMatch = matches.get(0);
        // <2.3> 處理存在多個 Match 物件的情況!!
        if (matches.size() > 1) {
            if (logger.isTraceEnabled()) {
                logger.trace(matches.size() + " matching mappings: " + matches);
            }
            if (CorsUtils.isPreFlightRequest(request)) {
                return PREFLIGHT_AMBIGUOUS_MATCH;
            }
            // 比較 bestMatch 和 secondBestMatch ,如果相等,說明有問題,拋出 IllegalStateException 例外
            // 因為,兩個優先級一樣高,說明無法判斷誰更優先
            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);
        // <2.4> 處理首個 Match 物件
        handleMatch(bestMatch.mapping, lookupPath, request);
        // <2.5> 回傳首個 Match 物件的 handlerMethod 屬性
        return bestMatch.handlerMethod;
    }
    // <3> 如果匹配不到,則處理不匹配的情況
    else {
        return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
    }
}
  1. 定義 Match 陣列 matches,存盤匹配上當前請求的結果(Mapping + HandlerMethod

    1. 優先,基于直接 URL (就是固定死的路徑,而非多個)的 Mapping 們,進行匹配
    2. 其次,掃描注冊表的 Mapping 們,進行匹配

    上述的1.11.2,都會呼叫addMatchingMappings(Collection<T> mappings, List<Match> matches, ServerWebExchange exchange) 方法

    將當前請求和注冊表中的 Mapping 進行匹配,匹配成功則生成匹配結果 Match,添加到 matches 中,方法如下:

    private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
        // 遍歷 Mapping 陣列
        for (T mapping : mappings) {
            // <1> 執行匹配,抽象方法,交由子類實作
            T match = getMatchingMapping(mapping, request);
            if (match != null) {
                // <2> 如果匹配,則創建 Match 物件,添加到 matches 中
                    matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
            }
        }
    }
    
  2. 如果匹配到,則獲取最佳匹配的 Match 結果的 HandlerMethod 屬性

    1. 創建 MatchComparator 物件,排序 matches 的結果,排序器
    2. 獲得首個 Match 結果,也就是最匹配的
    3. 處理存在多個 Match 的情況,則判斷第二匹配的和最匹配的是否“相同”,是的話就拋出例外
    4. 處理最匹配的 Match,設定請求路徑 lookupPath 到請求屬性
    5. 回傳最匹配的 Match 的 HandlerMethod 處理器物件
  3. 如果匹配不到,則處理不匹配的情況,呼叫handleNoMatch(Set<T> mappings, String lookupPath, HttpServletRequest request)方法,這里回傳null

到這里 AbstractHandlerMethodMapping 抽象類差不多全部分析完了,其中有幾個抽象方法交由子類去實作

  • protected abstract boolean isHandler(Class<?> beanType);
  • protected abstract T getMappingForMethod(Method method, Class<?> handlerType);
  • protected abstract Set<String> getMappingPathPatterns(T mapping);
  • protected abstract T getMatchingMapping(T mapping, HttpServletRequest request);
  • protected abstract Comparator<T> getMappingComparator(HttpServletRequest request);

RequestMappingInfoHandlerMapping

org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping,繼承 AbstractHandlerMethodMapping 抽象類,定義了使用的泛型 <T>org.springframework.web.servlet.mvc.method.RequestMappingInfo 類,即 Mapping 型別就是 RequestMappingInfo 物件

這樣有什么好處呢

RequestMappingInfoHandlerMapping 定義了使用 RequestMappingInfo 物件,而其子類 RequestMappingHandlerMapping 將使用了 @RequestMapping 注解的方法,決議生成 RequestMappingInfo 物件,這樣,如果未來我們自己定義注解,或者其他方式來生成 RequestMappingHandlerMapping 物件,未嘗不可,

構造方法

public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMethodMapping<RequestMappingInfo> {
    protected RequestMappingInfoHandlerMapping() {
        // 設定父類的 namingStrategy 屬性 Mapping 命名策略物件,為 RequestMappingInfoHandlerMethodMappingNamingStrategy 物件
        setHandlerMethodMappingNamingStrategy(new RequestMappingInfoHandlerMethodMappingNamingStrategy());
    }
}
  • <T> 泛型,為 RequestMappingInfo 型別

  • 設定父類 AbstractHandlerMethodMappingnamingStrategy 屬性為 RequestMappingInfoHandlerMethodMappingNamingStrategy 物件

    是否還記得這個為 Mapping 生成名稱的類?在 AbstractHandlerMethodMapping 中進行分析過了

RequestMappingInfo 物件

RequestMappingInfo 不是 RequestMappingInfoHandlerMapping 的內部類,而是 RequestMappingInfoHandlerMapping 的前綴

org.springframework.web.servlet.mvc.method.RequestMappingInfo,實作 RequestCondition 介面,每個方法的定義的請求資訊,也就是 @RequestMapping 等注解的資訊

關于 org.springframework.web.servlet.mvc.condition.RequestCondition,條件介面,定義了三個方法,分別是:

  • combine(T other),合并方法
  • getMatchingCondition(HttpServletRequest request),匹配方法
  • compareTo(T other, HttpServletRequest request),比較方法
構造方法
public final class RequestMappingInfo implements RequestCondition<RequestMappingInfo> {
	/**
	 * 名字
	 */
	@Nullable
	private final String name;
	/**
	 * 請求路徑的條件
	 */
	private final PatternsRequestCondition patternsCondition;
	/**
	 * 請求方法的條件
	 */
	private final RequestMethodsRequestCondition methodsCondition;
	/**
	 * 請求引數的條件
	 */
	private final ParamsRequestCondition paramsCondition;
	/**
	 * 請求頭的條件
	 */
	private final HeadersRequestCondition headersCondition;
	/**
	 * 可消費的 Content-Type 的條件
	 */
	private final ConsumesRequestCondition consumesCondition;
	/**
	 * 可生產的 Content-Type 的條件
	 */
	private final ProducesRequestCondition producesCondition;
	/**
	 * 自定義的條件
	 */
	private final RequestConditionHolder customConditionHolder;
}
  • 可以看到屬性中有各種條件,實際上,和 @RequestMapping 注解是一一對應的,所以,每個屬性的詳細解釋,相信你經常使用到
  • ?? 實際上,我們日常使用最多的還是 patternsCondition 請求路徑條件,和 methodsCondition 請求方法條件

RequestCondition 介面體系結構如下:

getMatchingCondition

getMatchingCondition(HttpServletRequest request)方法,從當前 RequestMappingInfo 獲得匹配的條件,如果匹配,則基于其匹配的條件,創建新的 RequestMappingInfo 物件,如果不匹配,則回傳 null ,代碼如下:

@Override
@Nullable
public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
    // 匹配 methodsCondition、paramsCondition、headersCondition、consumesCondition、producesCondition
    // 如果任一為空,則回傳 null ,表示匹配失敗
    RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);
    if (methods == null) {
        return null;
    }
    ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);
    if (params == null) {
        return null;
    }
    HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);
    if (headers == null) {
        return null;
    }
    ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request);
    if (consumes == null) {
        return null;
    }
    ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);
    if (produces == null) {
        return null;
    }
    PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request);
    if (patterns == null) {
        return null;
    }
    RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request);
    if (custom == null) {
        return null;
    }
    /*
     * 創建匹配的 RequestMappingInfo 物件
     * 為什么要創建 RequestMappingInfo 物件呢?
     *
     * 因為當前 RequestMappingInfo 物件,一個 methodsCondition 可以配置 GET、POST、DELETE 等等條件,
     * 但是實際就匹配一個請求型別,此時 methods 只代表其匹配的那個,
     */
    return new RequestMappingInfo(this.name, patterns,
            methods, params, headers, consumes, produces, custom.getCondition());
}
  • 雖然代碼非常長,實際都是呼叫每個屬性對應的 getMatchingCondition(HttpServletRequest request) 方法,獲得其匹配的真正的條件

可能你會疑惑,如果一個 @RequestMapping(value = "https://www.cnblogs.com/lifullmoon/p/user/login") 注解,并未寫 RequestMethod 的條件,豈不是會報空?

實際上不會,在這種情況下,會創建一個 RequestMethodsRequestCondition 物件,并且在匹配時,直接回傳自身,代碼如下:

@Override
@Nullable
public RequestMethodsRequestCondition getMatchingCondition(HttpServletRequest request) {
    if (CorsUtils.isPreFlightRequest(request)) {
        return matchPreFlight(request);
    }
    // 空的情況下,就回傳自身
    if (getMethods().isEmpty()) {
        if (RequestMethod.OPTIONS.name().equals(request.getMethod()) &&
                !DispatcherType.ERROR.equals(request.getDispatcherType())) {
            return null; // No implicit match for OPTIONS (we handle it)
        }
        return this;
    }
    // 非空,逐個匹配
    return matchRequestMethod(request.getMethod());
}

也就是說,沒有 RequestMethod 的條件,則一定匹配成功,且結果就是自身 RequestMethodsRequestCondition 物件

總結:就是根據配置的 @RequestMapping 注解,如果所有條件都滿足,則創建一個 RequestMappingInfo 物件回傳,如果某個條件不滿足則直接回傳 null,表示不匹配

compareTo

compareTo(RequestMappingInfo other, HttpServletRequest request)方法,比較優先級,方法如下:

@Override
public int compareTo(RequestMappingInfo other, HttpServletRequest request) {
    int result;
    // Automatic vs explicit HTTP HEAD mapping
    // 針對 HEAD 請求方法,特殊處理
    if (HttpMethod.HEAD.matches(request.getMethod())) {
        result = this.methodsCondition.compareTo(other.getMethodsCondition(), request);
        if (result != 0) {
            return result;
        }
    }
    /*
     * 依次比較 patternsCondition、paramsCondition、headersCondition、consumesCondition、
     * producesCondition、methodsCondition、customConditionHolder
     * 如果有一個不相等,則直接回傳比較結果
     */
    result = this.patternsCondition.compareTo(other.getPatternsCondition(), request);
    if (result != 0) {
        return result;
    }
    result = this.paramsCondition.compareTo(other.getParamsCondition(), request);
    if (result != 0) {
        return result;
    }
    result = this.headersCondition.compareTo(other.getHeadersCondition(), request);
    if (result != 0) {
        return result;
    }
    result = this.consumesCondition.compareTo(other.getConsumesCondition(), request);
    if (result != 0) {
        return result;
    }
    result = this.producesCondition.compareTo(other.getProducesCondition(), request);
    if (result != 0) {
        return result;
    }
    // Implicit (no method) vs explicit HTTP method mappings
    result = this.methodsCondition.compareTo(other.getMethodsCondition(), request);
    if (result != 0) {
        return result;
    }
    result = this.customConditionHolder.compareTo(other.customConditionHolder, request);
    if (result != 0) {
        return result;
    }
    return 0;
}

關于各種 RequestCondition 請求條件就不一一分析了

getMappingPathPatterns

getMappingPathPatterns(RequestMappingInfo info)方法,獲得 RequestMappingInfo 對應的請求路徑集合,代碼如下:

@Override
protected Set<String> getMappingPathPatterns(RequestMappingInfo info) {
	return info.getPatternsCondition().getPatterns();
}
  • 在 MappingRegistry 注冊表的 register 方法中的第 3 步會呼叫,將所有符合的請求路徑與該 RequestMappingInfo 物件進行映射保存

getMatchingMapping

getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) 方法,判斷請求是否匹配入參 RequestMappingInfo 物件,代碼如下:

@Override
protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) {
    return info.getMatchingCondition(request);
}
  • 在 AbstractHandlerMethodMapping 的 lookupHandlerMethod 獲取處理器方法的<1.1><1.2>會呼叫,遍歷所有的 Mapping 物件,獲取到該請求所匹配的 RequestMappingInfo 物件

handleMatch

handleMatch(RequestMappingInfo info, String lookupPath, HttpServletRequest request)方法,覆寫父類的方法,設定更多的屬性到請求中,代碼如下:

@Override
protected void handleMatch(RequestMappingInfo info, String lookupPath, HttpServletRequest request) {
    super.handleMatch(info, lookupPath, request);

    // 獲得 bestPattern 和 uriVariables
    String bestPattern; // 最佳路徑
    Map<String, String> uriVariables; // 路徑上的變數集合

    Set<String> patterns = info.getPatternsCondition().getPatterns();
    if (patterns.isEmpty()) {
        bestPattern = lookupPath;
        uriVariables = Collections.emptyMap();
    }
    else {
        bestPattern = patterns.iterator().next();
        uriVariables = getPathMatcher().extractUriTemplateVariables(bestPattern, lookupPath);
    }

    request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern);

    // 設定 MATRIX_VARIABLES_ATTRIBUTE 屬性,到請求中
    if (isMatrixVariableContentAvailable()) {
        Map<String, MultiValueMap<String, String>> matrixVars = extractMatrixVariables(request, uriVariables);
        request.setAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, matrixVars);
    }

    // 設定 URI_TEMPLATE_VARIABLES_ATTRIBUTE 屬性,到請求中
    Map<String, String> decodedUriVariables = getUrlPathHelper().decodePathVariables(request, uriVariables);
    request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, decodedUriVariables);

    // 設定 PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE 屬性,到請求中
    if (!info.getProducesCondition().getProducibleMediaTypes().isEmpty()) {
        Set<MediaType> mediaTypes = info.getProducesCondition().getProducibleMediaTypes();
        request.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypes);
    }
}

具體用途還不清楚??

handleNoMatch

handleNoMatch(Set<RequestMappingInfo> infos, String lookupPath, HttpServletRequest request) 方法,覆寫父類方法,處理無匹配 Mapping 的情況

主要用途是,給出為什么找不到 Mapping 的原因,代碼如下:

@Override
protected HandlerMethod handleNoMatch(Set<RequestMappingInfo> infos, String lookupPath, HttpServletRequest request) throws ServletException {

    // <1> 創建 PartialMatchHelper 物件,決議可能的錯誤
    PartialMatchHelper helper = new PartialMatchHelper(infos, request);
    if (helper.isEmpty()) {
        return null;
    }

    // <2> 方法錯誤
    if (helper.hasMethodsMismatch()) {
        Set<String> methods = helper.getAllowedMethods();
        if (HttpMethod.OPTIONS.matches(request.getMethod())) {
            HttpOptionsHandler handler = new HttpOptionsHandler(methods);
            return new HandlerMethod(handler, HTTP_OPTIONS_HANDLE_METHOD);
        }
        throw new HttpRequestMethodNotSupportedException(request.getMethod(), methods);
    }

    // <3> 可消費的 Content-Type 錯誤
    if (helper.hasConsumesMismatch()) {
        Set<MediaType> mediaTypes = helper.getConsumableMediaTypes();
        MediaType contentType = null;
        if (StringUtils.hasLength(request.getContentType())) {
            try {
                contentType = MediaType.parseMediaType(request.getContentType());
            }
            catch (InvalidMediaTypeException ex) {
                throw new HttpMediaTypeNotSupportedException(ex.getMessage());
            }
        }
        throw new HttpMediaTypeNotSupportedException(contentType, new ArrayList<>(mediaTypes));
    }

    // <4> 可生產的 Content-Type 錯誤
    if (helper.hasProducesMismatch()) {
        Set<MediaType> mediaTypes = helper.getProducibleMediaTypes();
        throw new HttpMediaTypeNotAcceptableException(new ArrayList<>(mediaTypes));
    }

    // <5> 引數錯誤
    if (helper.hasParamsMismatch()) {
        List<String[]> conditions = helper.getParamConditions();
        throw new UnsatisfiedServletRequestParameterException(conditions, request.getParameterMap());
    }

    return null;
}
  1. 核心代碼在 PartialMatchHelper 中實作,暫時忽略??

  2. 方法錯誤,這是一個非常常見的錯誤,例如說 POST user/login 存在,但是我們請求了 GET user/login

  3. 可消費的 Content-Type 錯誤

  4. 可生產的 Content-Type 錯誤

  5. 引數錯誤

RequestMappingHandlerMapping

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,實作 MatchableHandlerMapping、EmbeddedValueResolverAware 介面,繼承 RequestMappingInfoHandlerMapping 抽象類,基于@RequestMapping 注解來構建 RequestMappingInfo 物件

寫到這里有那么一點點感動,終于到最底層的實作類了??

構造方法

public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
		implements MatchableHandlerMapping, EmbeddedValueResolverAware {

	private boolean useSuffixPatternMatch = true;

	private boolean useRegisteredSuffixPatternMatch = false;

	private boolean useTrailingSlashMatch = true;

	private Map<String, Predicate<Class<?>>> pathPrefixes = new LinkedHashMap<>();

	private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();

	@Nullable
	private StringValueResolver embeddedValueResolver;

    /**
	 * RequestMappingInfo 的構建器
	 */
	private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();
}

afterPropertiesSet

因為父類 AbstractHandlerMethodMapping 實作了 InitializingBean 介面,在 Sping 初始化該 Bean 的時候,會呼叫該方法,完成一些初始化作業,方法如下:

@Override
public void afterPropertiesSet() {
    // 構建 RequestMappingInfo.BuilderConfiguration 物件
    this.config = new RequestMappingInfo.BuilderConfiguration();
    this.config.setUrlPathHelper(getUrlPathHelper());
    this.config.setPathMatcher(getPathMatcher());
    this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
    this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
    this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
    this.config.setContentNegotiationManager(getContentNegotiationManager());

    // 呼叫父類,初始化
    super.afterPropertiesSet();
}

isHandler

是否還記得 AbstractHandlerMethodMapping 的這個抽象方法?在它的 processCandidateBean 方法中,掃描 Spring 中所有 Bean 時會呼叫,判斷是否需要掃描這個 Bean 中的方法,方法如下:

@Override
protected boolean isHandler(Class<?> beanType) {
    // 判斷是否有 @Controller 或者 @RequestMapping 的注解
    return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
            AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

@Controller 或者 @RequestMapping 的注解的類才需要進行掃描,是不是很熟悉??

getMappingForMethod

是否還記得 AbstractHandlerMethodMapping 的這個抽象方法?在它的 detectHandlerMethods 方法中,用于獲取 Method 方法對應的 Mapping 物件,方法如下:

@Override
@Nullable
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
    // <1> 基于方法上的 @RequestMapping 注解,創建 RequestMappingInfo 物件
    RequestMappingInfo info = createRequestMappingInfo(method);
    if (info != null) {
        // <2> 基于類上的 @RequestMapping 注解,合并進去
        RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
        if (typeInfo != null) {
            info = typeInfo.combine(info);
        }
        // <3> 如果有前綴,則設定到 info 中
        String prefix = getPathPrefix(handlerType);
        if (prefix != null) {
            info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
        }
    }
    return info;
}
  1. 呼叫 createRequestMappingInfo(AnnotatedElement element) 方法,基于方法上的 @RequestMapping 注解,創建 RequestMappingInfo 物件

    @Nullable
    private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
        // <1> 獲得 @RequestMapping 注解
        RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
        // <2> 獲得自定義的條件,目前都是空方法,可以無視
        RequestCondition<?> condition = (element instanceof Class ? 
                                         getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
        // <3> 基于 @RequestMapping 注解,創建 RequestMappingInfo 物件
        return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
    }
    
    protected RequestMappingInfo createRequestMappingInfo(RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {
        // 創建 RequestMappingInfo.Builder 物件,設定對應屬性
        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);
        }
        // 創建 RequestMappingInfo 物件
        return builder.options(this.config).build();
    }
    
  2. 基于類上的 @RequestMapping 注解,合并進去

  3. 如果有前綴,則設定到 info

match

match(HttpServletRequest request, String pattern) 方法,執行匹配,代碼如下:

@Override
public RequestMatchResult match(HttpServletRequest request, String pattern) {
    // <1> 為 `pattern` 創建一個 RequestMappingInfo 物件
    RequestMappingInfo info = RequestMappingInfo.paths(pattern).options(this.config).build();
    // <2> 獲得請求對應的 RequestMappingInfo 物件
    RequestMappingInfo matchingInfo = info.getMatchingCondition(request);
    if (matchingInfo == null) { // <3> 沒有匹配的 RequestMappingInfo 物件回傳空
        return null;
    }
    // <4> 獲得請求匹配到的路徑
    Set<String> patterns = matchingInfo.getPatternsCondition().getPatterns();
    // <5> 獲取請求路徑
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    // <6> 創建 RequestMatchResult 結果
    return new RequestMatchResult(patterns.iterator().next(), lookupPath, getPathMatcher());
}

總結

在 Spring MVC 處理請求的程序中,需要通過 HandlerMapping 組件會為請求找到合適的 HandlerExecutionChain 處理器執行鏈,包含處理器(handler)和攔截器們(interceptors),該組件體系結構如下:

本文就紅色框中的內容進行了分析,基于 Method 進行匹配,例如,我們所熟知的 @RequestMapping 等注解的方式

在將紅色框中的類注入到 Spring 背景關系時,會進行一些初始化作業,掃描 @Controller 或者 @RequestMapping 注解標注的 Bean 物件,會將帶有 @RequestMapping 注解(包括其子注解)決議成 RequestMappingInfo 物件,接下來,會將 RequestMappingInfo該方法物件該方法所在類物件MappingRegistry 注冊表進行注冊,其中會生成 HandlerMethod 處理器(方法的所有資訊)物件保存起來,當處理某個請求時,HandlerMapping 找到該請求對應的 HandlerMethod 處理器物件后,就可以通過反射呼叫相應的方法了

這部分內容包含了我們常用到 @Controller@RequestMapping 注解,算是 HandlerMapping 組件的核心內容,看完之后有種茅塞頓開的感覺

回到以前的 Servlet 時代,我們需要撰寫許多的 Servlet 去處理請求,然后在 web.xml 中進行配置,而 Spring MVC 讓你通過只要在類和方法上面添加 @Controller 或者 @RequestMapping 注解這種方式,就可以處理請求,因為所有的請求都交給了 DispatcherServlet 去處理,這樣是不是簡化了你的作業量,讓你專注于業務開發,

參考文章:芋道原始碼《精盡 Spring MVC 原始碼分析》

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/235861.html

標籤:Java

上一篇:C/C++編程筆記:C陣列、字串常量和指標!三分鐘弄懂它

下一篇:網路 IO 模型簡單介紹

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more