#####################更新:
SpringMVC的請求處理過程中的路徑匹配過程:
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#lookupHandlerMethod
(spring-webmvc-4.2.3.RELEASE)
路徑匹配的過程中有如下代碼:
List<Match> matches = new ArrayList<Match>();
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);
}
SpringMVC首先對HTTP請求中的path與已註冊的RequestMappingInfo(經解析的@RequestMapping)中的path進行一個完全匹配來查找對應的HandlerMethod,即處理該請求的方法,**這個匹配就是一個Map#get方法**。若找不到則會遍歷所有的RequestMappingInfo進行查找。這個查找是不會提前停止的,直到遍歷完全部的RequestMappingInfo。
這裏主要是springmvc在項目啓動時根據requestMapping中api的訪問路徑封裝了不同的map對象,對於純path請求就是從map中直接get,而對於使用了pathVariable註解的請求則需要去map對象中找到所有滿足條件的對象然後取最優解。
public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
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) {
if (CorsUtils.isPreFlightRequest(request)) {
methods = getAccessControlRequestMethodCondition(request);
if (methods == null || params == null) {
return null;
}
}
else {
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());
}
org.springframework.web.servlet.mvc.method.RequestMappingInfo#getMatchingCondition
在遍歷過程中,SpringMVC首先會根據@RequestMapping中的headers, params, produces, consumes, methods與實際的HttpServletRequest中的信息對比,剔除掉一些明顯不合格的RequestMapping。 如果以上信息都能夠匹配上,那麼SpringMVC會對RequestMapping中的path進行正則匹配,剔除不合格的。
Comparator comparator = new MatchComparator(getMappingComparator(request));
Collections.sort(matches, comparator);
接下來會對所有留下來的候選@RequestMapping進行評分並排序。最後選擇分數最高的那個作爲結果。 評分的優先級爲:
path pattern > params > headers > consumes > produces > methods
所以使用非RESTful風格的URL時,SpringMVC可以立刻找到對應的HandlerMethod來處理請求。但是當在URL中存在變量時,即使用了@PathVariable時,SpringMVC就會進行上述的複雜流程。
值得注意的是SpringMVC在匹配@RequestMapping中的path時是通過AntPathMatcher進行的,這段path匹配邏輯是從Ant中借鑑過來的。
####END
使用restful風格時,我們往往會在一個controller方法使用同一個路徑,然後定義不同的httpmethod,那麼問題來了,springmvc是怎麼做到的呢?
首先,看下我們的Controller層代碼
@SpringBootApplication
@EnableEurekaServer
@RestController
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
@GetMapping("/hi")
public String hi() {
StringBuilder sb = new StringBuilder();
sb.append("<div style=\"color:red;\">aaaaa</div>")
.append(ESAPI.encoder().encodeForHTML("<script>alert(1)</script>"));
String jsCode = "<div style=\"color:red;\">aaaaa</div><script>alert(1)</script>";
String jsEncoder = ESAPI.encoder().encodeForHTML(jsCode);
System.out.println("###########################################################");
System.out.println(sb.toString());
return sb.toString();
}
@GetMapping("/hi/{id}")
public void getId(@PathVariable String id, String name) {
System.out.println("hello, " + id + "name:" + name);
}
@PostMapping("/hi/{id}")
public void postId(@PathVariable String id, String name) {
System.out.println("hello, " + id + "name:" + name);
}
}
根據路徑"/hi/{id}"定義了一個get方法和post方法。
項目啓動時,springmvc會掃描所有類,將加了mapping的註解的方法和url綁定,存進一個全局的mappingLookup,這裏怎麼匹配的就不詳細介紹了。
createHandlerMethod 會幫我們創建一個HandlerMethod對象
/**
* Create an instance from a bean instance and a method.
*/
public HandlerMethod(Object bean, Method method) {
Assert.notNull(bean, "Bean is required");
Assert.notNull(method, "Method is required");
this.bean = bean;
this.beanFactory = null;
this.beanType = ClassUtils.getUserClass(bean);
this.method = method;
this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
this.parameters = initMethodParameters();
evaluateResponseStatus();
}
這個對象包含了我們的方法名稱 method 、方法參數 parameters 、controller對象 bean 等等
項目啓動完成後,mappingLookup包含了spring掃描到的所有路徑和對應的api.
這時,在瀏覽器訪問我們的路徑,http://localhost:8081/hi/2,此時springmvc攔截這個請求,在DispatcherServlet中執行doDispatch方法,
通過調用getHandler方法獲取代理對象,繼續進一步查看此方法:
這裏會循環遍歷七種mappinghandler獲取最終的handler對象
這七個handler都繼承了抽象類AbstractHandlerMapping,此抽象類中的方法getHandler用於獲取handler 對象
/**
* Look up a handler for the given request, falling back to the default
* handler if no specific one is found.
* @param request current HTTP request
* @return the corresponding handler instance, or the default handler
* @see #getHandlerInternal
*/
@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (logger.isTraceEnabled()) {
logger.trace("Mapped to " + handler);
}
else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
logger.debug("Mapped to " + executionChain.getHandler());
}
if (CorsUtils.isCorsRequest(request)) {
CorsConfiguration globalConfig = this.corsConfigurationSource.getCorsConfiguration(request);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
再看getHandlerInternal方法,不同的實現類會重寫此方法,由於我們用的requestMapping註解,所以來看它的具體實現
先從request對象中獲取我們的url訪問路徑,再調用lookupHandlerMethod找到對應的method對象
/**
* Look up the best-matching handler method for the current request.
* If multiple matches are found, the best match is selected.
* @param lookupPath mapping lookup path within the current servlet mapping
* @param request the current request
* @return the best-matching handler method, or {@code null} if no match
* @see #handleMatch(Object, String, HttpServletRequest)
* @see #handleNoMatch(Set, String, HttpServletRequest)
*/
@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
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()) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
matches.sort(comparator);
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
if (logger.isTraceEnabled()) {
logger.trace(matches.size() + " matching mappings: " + matches);
}
if (CorsUtils.isPreFlightRequest(request)) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
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);
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
}
else {
return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}
}
this.mappingRegistry.getMappings().keySet() 返回的就是在啓動時封裝的mappingLooku對象
繼續回到上面的lookupHandlerMethod方法,調用addMatchingMappings方法找到一個最合適的matcher
getMatchingMapping 再繼續深入這個方法,會執行getMatchingCondition方法生成一個RequestMappingInfo對象
/**
* Checks if all conditions in this request mapping info match the provided request and returns
* a potentially new request mapping info with conditions tailored to the current request.
* <p>For example the returned instance may contain the subset of URL patterns that match to
* the current request, sorted with best matching patterns on top.
* @return a new instance in case all conditions match; or {@code null} otherwise
*/
@Override
@Nullable
public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
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;
}
return new RequestMappingInfo(this.name, patterns,
methods, params, headers, consumes, produces, custom.getCondition());
}
通過查看RequestMappingInfo對象的屬性,能發現這個對象已經根據url找到對應的controller方法了,翻譯一下這段代碼的註釋
/**
* Checks if all conditions in this request mapping info match the provided request and returns
* a potentially new request mapping info with conditions tailored to the current request.
* <p>For example the returned instance may contain the subset of URL patterns that match to
* the current request, sorted with best matching patterns on top.
* @return a new instance in case all conditions match; or {@code null} otherwise
*/
/ **
*檢查此請求映射信息中的所有條件是否與提供的請求匹配
並返回具有針對當前請求量身定製潛在請求映射信息
- 例如,返回的實例可能包含與以下內容匹配的URL請求,在頂部以最佳匹配模式排序。
- @在所有條件匹配的情況下返回新實例; 或{@code null}否則
大概意思就是收集所有滿足條件的method方法。
再拿到所有滿足條件的method方法後,會對這些方法進行一個排序,然後取一個分數最高的method
排序實現的邏輯:
/**
* Compares "this" info (i.e. the current instance) with another info in the context of a request.
* <p>Note: It is assumed both instances have been obtained via
* {@link #getMatchingCondition(HttpServletRequest)} to ensure they have conditions with
* content relevant to current request.
*/
@Override
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;
}
拿到HandlerAdapter對象後會在doDispatch方法中調用handle方法進行參數的初始化和方法的調用。
/**
* This implementation expects the handler to be an {@link HandlerMethod}.
*/
@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}
invokeHandlerMethod 調用此方法返回modelandView對象
再次進入invokeHandlerMethod方法,最終會調用invocableMethod.invokeAndHandle(webRequest, mavContainer);
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(result, !traceOn);
return "Resume with async result [" + formatted + "]";
});
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}
invokeAndHandle方法中會執行invokeForRequest方法,在此方法中需關注
getMethodArgumentValues方法
getMethodArgumentValues會將request中的參數封裝進行初始化
resolveArgument方法:
@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional();
Object resolvedName = resolveStringValue(namedValueInfo.name);
if (resolvedName == null) {
throw new IllegalArgumentException(
"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
}
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
if (arg == null) {
if (namedValueInfo.defaultValue != null) {
arg = resolveStringValue(namedValueInfo.defaultValue);
}
else if (namedValueInfo.required && !nestedParameter.isOptional()) {
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
}
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
arg = resolveStringValue(namedValueInfo.defaultValue);
}
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
try {
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
}
catch (ConversionNotSupportedException ex) {
throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());
}
catch (TypeMismatchException ex) {
throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());
}
}
handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
return arg;
}
handleResolvedValue方法會將獲取的值賦值給正確的參數,至此,參數的封裝就完成了,在通過doInvoke動態調用api方法。