SpringMVC源碼解析之Servlet
SpringMVC源碼解析之GenericServlet和HttpServlet
SpringMVC源碼解析之DispatcherServlet:生命週期init()和destroy()
SpringMVC源碼解析之DispatcherServlet:請求處理
SpringMVC源碼解析之RequestMappingHandlerMapping:getHandler
一、簡介
getHandler是HandlerMapping接口中的唯一方法,用於根據請求找到匹配的處理器。因此getHandler也是包括RequestMappingHandlerMapping在內的所有實現類中最核心的一個方法,其方法調用鏈如下:
二、AbstractHandlerMapping
1. getHandler
//AbstractHandlerMapping#getHandler
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//獲取handler的具體邏輯,留給子類實現
Object handler = getHandlerInternal(request);
//null handler,採用默認的handler,即屬性defaultHandler
if (handler == null) {
handler = getDefaultHandler();
}
//還是null handler,直接返回null
if (handler == null) {
return null;
}
//handler是beanName, 從容器裏獲取對應的bean
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
//根據handler和request獲取處理器鏈HandlerExecutionChain
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
//CORS請求的處理
if (CorsUtils.isCorsRequest(request)) {
//全局配置
CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
//handler的單獨配置
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
//handler的所有配置,全局配置+單獨配置
CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
//根據cors配置更新HandlerExecutionChain
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
(1)調用getHandlerInternal獲取匹配的處理器
(2)如果獲取失敗,獲取默認的處理器。如果沒有設置默認處理器,直接返回null。
(3)如果獲取到的處理器是字符串,將其作爲beanName從容器中獲取對應的對象作爲處理器。
(4)調用getHandlerExecutionChain封裝成HandlerExecutionChain
(5)針對CORS請求做特殊處理
2. getHandlerExecutionChain
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
//MappedInterceptor根據url判斷是否匹配
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
//其它類型的Interceptor對所有請求都有效
else {
chain.addInterceptor(interceptor);
}
}
return chain;
}
從請求中獲取路徑找到匹配的攔截器Interceptor集合,與Handler一起封裝成HandlerExecutionChain。
如上一篇博客中對onApplicationContext方法的介紹中提到的一樣,SpringMVC中配置的所有攔截器最終封裝成HandlerInterceptor保存在adaptedInterceptors中,其中MappedInterceptor類型的攔截器只針對特點的url有效,其它類型的攔截器對所有請求都有效。
三、AbstractHandlerMethodMapping
1. getHandlerInternal
//AbstractHandlerMethodMapping#getHandlerInternal
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
//請求的路徑
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
if (logger.isDebugEnabled()) {
logger.debug("Looking up handler method for path " + lookupPath);
}
this.mappingRegistry.acquireReadLock();
try {
//找到匹配的處理器HandlerMethod
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
if (logger.isDebugEnabled()) {
if (handlerMethod != null) {
logger.debug("Returning handler method [" + handlerMethod + "]");
}
else {
logger.debug("Did not find handler method for [" + lookupPath + "]");
}
}
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
主要功能是在讀寫鎖加鎖的情況下調用lookupHandlerMethod找到匹配的處理器
2. lookupHandlerMethod
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
//根據url獲取對應的匹配條件集
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
//如果沒有找到匹配條件,從整個匹配條件集中進行查找
if (matches.isEmpty()) {
// No choice but to go through all mappings...
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}
if (!matches.isEmpty()) {
//getMappingComparator抽獎方法,用於匹配條件T的比較
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
//排序
matches.sort(comparator);
if (logger.isTraceEnabled()) {
logger.trace("Found " + matches.size() + " matching mapping(s) for [" + lookupPath + "] : " + matches);
}
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
//CORS的預請求,返回new HandlerMethod(new EmptyHandler(), ClassUtils.getMethod(EmptyHandler.class, "handle"));
if (CorsUtils.isPreFlightRequest(request)) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
//確保最優的匹配條件T是唯一的,最優的匹配條件必須大於其它所有匹配條件,不能相同
Match secondBestMatch = matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.handlerMethod.getMethod();
Method m2 = secondBestMatch.handlerMethod.getMethod();
throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +
request.getRequestURL() + "': {" + m1 + ", " + m2 + "}");
}
}
//找到匹配合適的匹配條件後的處理,默認加入請求的屬性Attributes中
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
}
else {
//沒有找到合適的匹配條件T,handleNoMatch爲默認方案
return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}
}
(1)調用addMatchingMappings查找合適的匹配條件並加入到matches
首先根據url在對應的匹配集directPathMatches內進行查找,如果查找不到則進行全局查找。
(2)找到優先級最高的匹配條件bestMatch。
要求bestMatch的優先級必須大於matches剩餘的所有的匹配條件,如果存在相同的,則拋出錯誤。
(3)調用handleMatch進行匹配成功後的處理
(4)調用handleNoMatch進行匹配失敗後的處理
3. addMatchingMappings
//根據請求request從匹配集mappings篩選出適用的匹配條件
private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
for (T mapping : mappings) {
//抽象方法,判斷並返回適用的匹配條件
T match = getMatchingMapping(mapping, request);
if (match != null) {
matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
}
}
}
根據getMatchingMapping進行篩選,符合的加入集合matches
4. handleMatch
protected void handleMatch(T mapping, String lookupPath, HttpServletRequest request) {
request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, lookupPath);
}
默認的handleMatch爲在請求中添加屬性進行標誌
5. handleNoMatch
protected HandlerMethod handleNoMatch(Set<T> mappings, String lookupPath, HttpServletRequest request)
throws Exception {
return null;
}
默認的沒有找到處理器時不作處理,返回null。
四、RequestMappingInfoHandlerMapping
1. getMatchingMapping
protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) {
return info.getMatchingCondition(request);
}
檢查匹配條件info與請求是否匹配,不匹配返回null,匹配返回匹配的RequestMappingInfo 對象,具體邏輯委派RequestMappingInfo 的getMatchingCondition處理。
2. getMappingComparator
protected Comparator<RequestMappingInfo> getMappingComparator(final HttpServletRequest request) {
return (info1, info2) -> info1.compareTo(info2, request);
}
對於RequestMappingInfo大小的比較委派給RequestMappingInfo的compareTo方法處理。
3. handleMatch
protected void handleMatch(RequestMappingInfo info, String lookupPath, HttpServletRequest request) {
//父類AbstractHandlerMethodMapping中設置lookupPath爲請求屬性
super.handleMatch(info, lookupPath, request);
String bestPattern;
Map<String, String> uriVariables;
Map<String, String> decodedUriVariables;
Set<String> patterns = info.getPatternsCondition().getPatterns();
if (patterns.isEmpty()) {
bestPattern = lookupPath;
uriVariables = Collections.emptyMap();
decodedUriVariables = Collections.emptyMap();
}
else {
bestPattern = patterns.iterator().next();
uriVariables = getPathMatcher().extractUriTemplateVariables(bestPattern, lookupPath);
decodedUriVariables = getUrlPathHelper().decodePathVariables(request, uriVariables);
}
request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern);
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, decodedUriVariables);
if (isMatrixVariableContentAvailable()) {
Map<String, MultiValueMap<String, String>> matrixVars = extractMatrixVariables(request, uriVariables);
request.setAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, matrixVars);
}
if (!info.getProducesCondition().getProducibleMediaTypes().isEmpty()) {
Set<MediaType> mediaTypes = info.getProducesCondition().getProducibleMediaTypes();
request.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypes);
}
}
除了lookupPath外,還將更多的屬性設置到了請求中,方便後面的使用,與HandlerMapping本身並沒有作用。
4. handleNoMatch
protected HandlerMethod handleNoMatch(Set<RequestMappingInfo> infos, String lookupPath,
HttpServletRequest request) throws ServletException {
//記錄所有url匹配的匹配條件RequestMappingInfo集
PartialMatchHelper helper = new PartialMatchHelper(infos, request);
//沒有url匹配的RequestMappingInfo
if (helper.isEmpty()) {
return null;
}
//判斷url匹配的RequestMappingInfo集中最終匹配失敗的原因
//1. Methods不匹配(只有有一個匹配則返回false)
if (helper.hasMethodsMismatch()) {
Set<String> methods = helper.getAllowedMethods();
//OPTIONS請求HttpOptionsHandler做處理器,返回攜帶了所有允許的Methods的請求頭
if (HttpMethod.OPTIONS.matches(request.getMethod())) {
HttpOptionsHandler handler = new HttpOptionsHandler(methods);
return new HandlerMethod(handler, HTTP_OPTIONS_HANDLE_METHOD);
}
//非OPTIONS請求拋出Methods不支持的異常
throw new HttpRequestMethodNotSupportedException(request.getMethod(), methods);
}
//consumes不匹配,拋出MediaType不支持的異常
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));
}
//produces不匹配,拋出MediaType不支持的異常
if (helper.hasProducesMismatch()) {
Set<MediaType> mediaTypes = helper.getProducibleMediaTypes();
throw new HttpMediaTypeNotAcceptableException(new ArrayList<>(mediaTypes));
}
//params不匹配,拋出參數不匹配的異常
if (helper.hasParamsMismatch()) {
List<String[]> conditions = helper.getParamConditions();
throw new UnsatisfiedServletRequestParameterException(conditions, request.getParameterMap());
}
return null;
}
細化了找不到匹配的匹配條件的原因,url匹配但methods(OPTIONS除外)/produces/consumes/params不匹配拋出對應異常,其它的返回null。
針對RequestMappingInfo集進行迭代,查找url匹配的請求。如果有url匹配的匹配條件,根據其匹配失敗的原因(如methods不匹配,produces/consumes不匹配,params不匹配)拋出對應的異常。
五、RequestMappingInfo
1. getMatchingCondition
public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
//methodsCondition與請求的methods進行判斷,返回匹配的方法,下面的getMatchingCondition方法的同理
RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);
ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);
HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);
ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request);
ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);
if (methods == null || params == null || headers == null || consumes == null || produces == null) {
return null;
}
PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request);
if (patterns == null) {
return null;
}
RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request);
if (custom == null) {
return null;
}
return new RequestMappingInfo(this.name, patterns,
methods, params, headers, consumes, produces, custom.getCondition());
}
將RequestMappingInfo的methods等屬性與請求中的屬性進行匹配,只有有一個匹配失敗則認爲不匹配,返回false。
RequestMappingInfo的methods等屬性可能是一個列表,有多個值,但是最終匹配的只有一個,因此匹配成功需要新建一個RequestMappingInfo返回。
2. compareTo
public int compareTo(RequestMappingInfo other, HttpServletRequest request) {
int result;
// Automatic vs explicit HTTP HEAD mapping
if (HttpMethod.HEAD.matches(request.getMethod())) {
result = this.methodsCondition.compareTo(other.getMethodsCondition(), request);
if (result != 0) {
return result;
}
}
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;
}
按照METHODS(只針對HEAD請求),patterns,params,headers,consumes,produces,METHODS(HEAD以外的請求),自定義條件的順序進行比較確定優先級