該系列檔案是本人在學習 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 注冊表,詳細見下文 -
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 = "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
- 情況一,如果 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 = "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;
}
-
核心代碼在 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 原始碼分析》
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/235861.html
標籤:Java
上一篇:C/C++編程筆記:C陣列、字串常量和指標!三分鐘弄懂它
下一篇:網路 IO 模型簡單介紹
