該系列文件是本人在學習 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 的請求匹配的註解,體系結構如下:
關於這些註解,大家已經非常熟悉了,各自的屬性就不再進行講述了,可具體檢視原始碼:
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 登錄檔,詳細見下文 -
namingStrategy
:org.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 = "user/login")
註解的方法,它對應的 Mapping 的名字就是login
- 情況二,如果 Mapping 未配置名字,則使用使用類名大寫 +
"#"
+ 方法名。例如,@RequestMapping(value = "user/login")
註解的方法,假設它所在的類為 UserController ,對應的方法名為 login ,則它對應的 Mapping 的名字就是USERCONTROLLER#login
- 情況一,如果 Mapping 已經配置名字,則直接返回。例如,
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();
}
}
-
獲得寫鎖
-
新增相關對映至
Map<T, HandlerMethod> mappingLookup
屬性- 呼叫
createHandlerMethod(Object handler, Method method)
方法,建立 HandlerMethod 物件,詳情見下文 - 校驗當前 Mapping 是否存在對應的 HandlerMethod 物件,如果已存在但不是前一步建立 HandlerMethod 物件則丟擲異常,保證唯一性
- 將 Mapping 與 HandlerMethod 的對映關係儲存至
mappingLookup
- 呼叫
-
新增相關對映至
MultiValueMap<String, T> urlLookup
屬性-
呼叫
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}")
註解對應的路徑,不是直接路徑,因為不確定性
- 例如,
-
將 url 和 Mapping 的對映關係儲存至
urlLookup
-
-
新增相關對映至
Map<String, List<HandlerMethod>> nameLookup
屬性-
呼叫
HandlerMethodMappingNamingStrategy#getName(HandlerMethod handlerMethod, T mapping)
方法,獲得 Mapping 的名字 -
呼叫
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); }
和已有的進行合併,說明名稱不是唯一哦
-
-
初始化 CorsConfiguration 配置物件,暫時忽略
-
建立
MappingRegistration
物件,並和 Mapping 進行對映新增至registry
-
釋放寫鎖
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;
}
- 如果 handler 型別為 String, 說明對應一個 Bean 物件的名稱。例如 UserController 使用
@Controller
註解後,預設入參 handler 就是它的 beanName ,即userController
- 如果 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();
}
-
將
beanName
賦值給bean
屬性,說明beanFactory + bean
的方式,獲得handler
物件 -
初始化
beanType
屬性 -
初始化
method
、bridgedMethod
屬性 -
初始化
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; }
-
初始化
responseStatus
、responseStatusReason
屬性,通過@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());
}
-
呼叫
getCandidateBeanNames()
方法,獲取到 Spring 上下文中所有為Object
型別的 Bean 的名稱集合,然後進行遍歷,方法如下:protected String[] getCandidateBeanNames() { // 獲取上下文中所有的 Bean 的名稱 return (this.detectHandlerMethodsInAncestorContexts ? BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) : obtainApplicationContext().getBeanNamesForType(Object.class)); }
detectHandlerMethodsInAncestorContexts
:是否只掃描可訪問的 HandlerMethod 們,預設false
-
呼叫
processCandidateBean(String beanName)
方法,處理每個符合條件的 Bean 物件(例如有@Controller
或者@RequestMapping
註解的 Bean),解析這些 Bean 下面相應的方法,往MappingRegistry
註冊,詳情見下文 -
呼叫
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);
}
}
- 獲得 Bean 對應的 Class 物件
- 呼叫
isHandler(Class<?> beanType)
抽象方法,判斷 Bean 的型別是否需要處理,其RequestMappingHandlerMapping
子類的實現:有@Controller
或者@RequestMapping
註解的 Bean - 呼叫
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);
});
}
}
-
獲得 Bean 對應的 Class 物件
handlerType
-
呼叫
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; }
-
獲得匹配的方法和對應的 Mapping 物件,其中泛型
T
,也就是 Mapping 物件,需要通過getMappingForMethod(Method method, Class<?> handlerType)
抽象方法返回,其RequestMappingHandlerMapping
子類的實現,根據@RequestMapping
註解為方法建立RequestMappingInfo
物件所以這裡的 Mapping 物件就是
RequestMappingInfo
物件 -
遍歷方法(方法與 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
由於上面初始化涉及到內容有點多,先回到本文上面的回顧這一小節,通過 AbstractHandlerMapping 的
getHandler(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();
}
}
-
獲得請求路徑
-
獲得讀鎖
-
呼叫
lookupHandlerMethod(ServerWebExchange exchange)
方法,獲得請求對應的 HandlerMethod 處理器物件,詳情見下文 -
如果獲得到 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); }
-
釋放讀鎖
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);
}
}
-
定義 Match 陣列
matches
,儲存匹配上當前請求的結果(Mapping
+HandlerMethod
)- 優先,基於直接 URL (就是固定死的路徑,而非多個)的
Mapping
們,進行匹配 - 其次,掃描登錄檔的 Mapping 們,進行匹配
上述的
1.1
和1.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))); } } }
- 優先,基於直接 URL (就是固定死的路徑,而非多個)的
-
如果匹配到,則獲取最佳匹配的 Match 結果的
HandlerMethod
屬性- 建立 MatchComparator 物件,排序
matches
的結果,排序器 - 獲得首個 Match 結果,也就是最匹配的
- 處理存在多個 Match 的情況,則判斷第二匹配的和最匹配的是否“相同”,是的話就丟擲異常
- 處理最匹配的 Match,設定請求路徑
lookupPath
到請求屬性 - 返回最匹配的 Match 的
HandlerMethod
處理器物件
- 建立 MatchComparator 物件,排序
-
如果匹配不到,則處理不匹配的情況,呼叫
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
型別 -
設定父類
AbstractHandlerMethodMapping
的namingStrategy
屬性為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 = "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;
}
-
核心程式碼在 PartialMatchHelper 中實現,暫時忽略?
-
方法錯誤。這是一個非常常見的錯誤,例如說
POST user/login
存在,但是我們請求了GET user/login
-
可消費的 Content-Type 錯誤
-
可生產的 Content-Type 錯誤
-
引數錯誤
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;
}
-
呼叫
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(); }
-
基於類上的
@RequestMapping
註解,合併進去 -
如果有字首,則設定到
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 原始碼分析》