經(jīng)過(guò)前面的鋪墊我們開(kāi)始分析SpringMVC的請(qǐng)求過(guò)程牺六,由于整個(gè)過(guò)程較為復(fù)雜戴陡,本篇我們只討論到請(qǐng)求尋址,后面的HandlerMethod調(diào)用及返回值處理下次再來(lái)分析惨撇。因?yàn)榫W(wǎng)上已經(jīng)有很多流程圖了胎撤,這里我就不再畫流程圖了晓殊,我們使用Spring官方描述的DispatcherServlet的處理流程來(lái)展開(kāi)分析。
我們將上圖我們對(duì)照源碼來(lái)看
DispatcherServlet在接收到請(qǐng)求之后會(huì)將WebApplicatinContext和當(dāng)前request綁定伤提,并且將localeResolver巫俺、themeResolver、themeSource(其實(shí)是WebApplicatinContext)綁定在request中肿男。接下來(lái)會(huì)判斷是否是轉(zhuǎn)發(fā)過(guò)來(lái)的請(qǐng)求介汹,如果是則會(huì)綁定FlashMap相關(guān)屬性。最后到達(dá)DispatcherServlet的核心doDispatch方法舶沛。我們來(lái)著重分析這個(gè)方法嘹承。核心代碼如下
/**
* Process the actual dispatching to the handler.
* <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
* The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
* to find the first that supports the handler class.
* <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
* themselves to decide which methods are acceptable.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception in case of any kind of processing failure
*/
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 判斷是否是上傳文件請(qǐng)求
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
// 請(qǐng)求尋址,返回HandlerExecutionChain
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
// 通過(guò)HandlerMapping尋找合適的適配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 執(zhí)行HandlerMapping中配置的攔截器的前置處理邏輯
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
// 根據(jù)request中的請(qǐng)求路徑匹配配置過(guò)的HandlerMapping并解析請(qǐng)求中的參數(shù)將其綁定在request中如庭,隨后與HandlerMethod中的方法綁定并調(diào)用HandlerMethod方法
// 得到ModelAndView以供后面解析(RequestBody等類型的返回結(jié)果會(huì)直接返回?cái)?shù)據(jù))
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 如果返回了視圖名稱叹卷,這里會(huì)去拼接視圖全路徑(prefix+viewName+suffix)
applyDefaultViewName(processedRequest, mv);
// 執(zhí)行HandlerMapping中配置的攔截器的后置處理邏輯
mappedHandler.applyPostHandle(processedRequest, response, mv);
..............................................
}
上面這段代碼粗略解釋了每個(gè)方法都做了什么,由于現(xiàn)在大多使用Restful風(fēng)格的接口坪它,所有在后面的分析中我們會(huì)以Restful風(fēng)格的請(qǐng)求url為例來(lái)分析骤竹。下面我們以http://localhost:8080/app/helloController/sayHello2/haha為例,Controller代碼如下
@RestController
@RequestMapping("/helloController")
public class HelloController {
@Autowired
private HelloService helloService;
@GetMapping("/sayHello")
public String sayHello(@RequestParam String guests){
helloService.sayHello(guests);
return "success";
}
@GetMapping("/sayHellos")
public String sayHello(@RequestParam String[] guests){
Arrays.asList(guests).forEach( guest -> {
helloService.sayHello(guest);
});
return "success";
}
@GetMapping("/sayHello2/{guest}")
public String sayHello2(@PathVariable String guest){
helloService.sayHello(guest);
return "success";
}
}
可以看出總共有3個(gè)方法往毡,其中前兩個(gè)為傳統(tǒng)的API風(fēng)格瘤载,最后一個(gè)是Restful風(fēng)格。本文將以第三個(gè)方法為例來(lái)分析請(qǐng)求過(guò)程卖擅。
當(dāng)用戶發(fā)起請(qǐng)求經(jīng)過(guò)DispatcherServlet的一系列處理后到達(dá)getHandlerAdapter方法,這個(gè)方法是整個(gè)處理過(guò)程中的關(guān)鍵,涉及到url尋址及請(qǐng)求路徑參數(shù)解析惩阶。關(guān)鍵代碼如下
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 這里的handlerMappings就是初始化時(shí)加入的BeanNameUrlHandlerMapping和RequestMappingHandlerMapping
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
// 之前說(shuō)過(guò)BeanNameUrlHandlerMapping是將Bean名稱與請(qǐng)求url做匹配挎狸,基本不會(huì)使用這個(gè)HandlerMapping,這里會(huì)通過(guò)RequestMappingHandlerMapping來(lái)處理
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 獲取Handler断楷,下面詳細(xì)分析
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
// 如果返回的是beanName锨匆,則通過(guò)IOC容器獲取bean
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
// 將HandlerMethod封裝成HandlerExecutionChain,后面會(huì)詳細(xì)分析
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
return executionChain;
}
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
// 處理url冬筒,主要是將配置的servletmapping前綴從url中去除恐锣,該方法比較簡(jiǎn)單,我們不詳細(xì)分析舞痰,只接用源碼的注釋來(lái)說(shuō)明
/**
* Return the path within the servlet mapping for the given request,
* i.e. the part of the request's URL beyond the part that called the servlet,
* or "" if the whole URL has been used to identify the servlet.
* <p>Detects include request URL if called within a RequestDispatcher include.
* <p>E.g.: servlet mapping = "/*"; request URI = "/test/a" -> "/test/a".
* <p>E.g.: servlet mapping = "/"; request URI = "/test/a" -> "/test/a".
* <p>E.g.: servlet mapping = "/test/*"; request URI = "/test/a" -> "/a".
* <p>E.g.: servlet mapping = "/test"; request URI = "/test" -> "".
* <p>E.g.: servlet mapping = "/*.test"; request URI = "/a.test" -> "".
* @param request current HTTP request
* @return the path within the servlet mapping, or ""
*/
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
this.mappingRegistry.acquireReadLock();
try {
// 這里就開(kāi)始真正的映射handlerMethod并解析url參數(shù)土榴,下面詳細(xì)分析
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
// 這里是從urlLookup中獲取RequestMappingInfo,一般來(lái)說(shuō)url不帶參數(shù)({xxx})的請(qǐng)求都可以從這里讀到
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
// 如果在urlLookup中已經(jīng)拿到了mapping响牛,直接進(jìn)行匹配操作
if (directPathMatches != null) {
// 下面詳細(xì)分析
addMatchingMappings(directPathMatches, matches, request);
}
// 如果沒(méi)有從urlLookup中得到玷禽,則通過(guò)mappingLookup來(lái)獲取mapping
if (matches.isEmpty()) {
// No choice but to go through all mappings...
// 下面詳細(xì)分析
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}
// 已經(jīng)匹配到的話會(huì)對(duì)能匹配到的mapping進(jìn)行排序,得到最佳匹配項(xiàng)
if (!matches.isEmpty()) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
matches.sort(comparator);
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
// 匹配項(xiàng)超過(guò)1個(gè)會(huì)做一次判斷呀打,默認(rèn)還是第一個(gè)結(jié)果
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 + "}");
}
}
// 將最佳匹配項(xiàng)的handlerMethod與request綁定
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
// 將最佳匹配項(xiàng)的mapping與request綁定
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
}
else {
return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}
}
private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
// 這里的mappings是在初始化過(guò)程中能和當(dāng)前url匹配的所有RequestMappingInfo矢赁,每個(gè)mapping會(huì)封裝有當(dāng)前mapping能處理的mappingName、params贬丛、methods撩银、paths等參數(shù)
for (T mapping : mappings) {
// 調(diào)用RequestMappingInfo中的getMatchingCondition來(lái)獲取RequestMappingInfo
T match = getMatchingMapping(mapping, request);
if (match != null) {
// 將RequestMappingInfo封裝成Match
matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
}
}
}
喝口水休息休息,本篇文章可能篇幅較長(zhǎng)豺憔,但此時(shí)已經(jīng)接近終點(diǎn)额获。希望大家堅(jiān)持一下,下面我們繼續(xù)焕阿。
public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
// 獲取當(dāng)前RequestMappingInfo的各種Condition屬性(初始化HandlerMapping的時(shí)候創(chuàng)建的)
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;
}
// 通過(guò)patternsCondition來(lái)匹配request咪啡。下面專門分析
PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request);
if (patterns == null) {
return null;
}
// 自定義的Condition,一般為空的
RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request);
if (custom == null) {
return null;
}
// 根據(jù)上面生成的patterns和custom以及其他默認(rèn)屬性創(chuàng)建新的RequestMappingInfo并返回
return new RequestMappingInfo(this.name, patterns,
methods, params, headers, consumes, produces, custom.getCondition());
}
public PatternsRequestCondition getMatchingCondition(HttpServletRequest request) {
// 和上面getHandlerInternal方法中一樣暮屡,是用來(lái)處理ServletMapping和請(qǐng)求路徑用的
String lookupPath = this.pathHelper.getLookupPathForRequest(request);
// 真正做匹配的地方撤摸,下面專門分析
List<String> matches = getMatchingPatterns(lookupPath);
return (!matches.isEmpty() ?
new PatternsRequestCondition(matches, this.pathHelper, this.pathMatcher,
this.useSuffixPatternMatch, this.useTrailingSlashMatch, this.fileExtensions) : null);
}
public List<String> getMatchingPatterns(String lookupPath) {
List<String> matches = new ArrayList<>();
// this.patterns就是RequestMapping中配置的url,本例中是/helloController/sayHello2/{guest}
for (String pattern : this.patterns) {
// 獲取匹配結(jié)果褒纲,下面單獨(dú)分析
String match = getMatchingPattern(pattern, lookupPath);
if (match != null) {
matches.add(match);
}
}
if (matches.size() > 1) {
matches.sort(this.pathMatcher.getPatternComparator(lookupPath));
}
return matches;
}
private String getMatchingPattern(String pattern, String lookupPath) {
if (pattern.equals(lookupPath)) {
return pattern;
}
// 這里會(huì)調(diào)用doMatch方法執(zhí)行真正的匹配邏輯准夷,通過(guò)這么久的學(xué)習(xí)過(guò)程我們應(yīng)該熟悉看到doXXX方法意味著什么!
if (this.pathMatcher.match(pattern, lookupPath)) {
return pattern;
}
if (this.useTrailingSlashMatch) {
if (!pattern.endsWith("/") && this.pathMatcher.match(pattern + "/", lookupPath)) {
return pattern +"/";
}
}
return null;
}
protected boolean doMatch(String pattern, String path, boolean fullMatch,
@Nullable Map<String, String> uriTemplateVariables) {
// 這里會(huì)將匹配模板切分成數(shù)組莺掠,本例中是["helloController","sayHello2","{guest}"]
String[] pattDirs = tokenizePattern(pattern);
// 這里將實(shí)際url切分成數(shù)組衫嵌,本例中是["helloController","sayHello2","haha"]
String[] pathDirs = tokenizePath(path);
int pattIdxStart = 0;
int pattIdxEnd = pattDirs.length - 1;
int pathIdxStart = 0;
int pathIdxEnd = pathDirs.length - 1;
// Match all elements up to the first **
while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
String pattDir = pattDirs[pattIdxStart];
if ("**".equals(pattDir)) {
break;
}
// 這里會(huì)進(jìn)行Ant風(fēng)格匹配。
// 注意后面在handlerAdapter處理時(shí)還會(huì)調(diào)用一次這個(gè)方法彻秆,在這個(gè)方法里面會(huì)解析url參數(shù)并將其綁定在request中
if (!matchStrings(pattDir, pathDirs[pathIdxStart], uriTemplateVariables)) {
return false;
}
pattIdxStart++;
pathIdxStart++;
}
// 循環(huán)結(jié)束后如果請(qǐng)求路徑數(shù)組全部結(jié)束說(shuō)明已經(jīng)匹配完了
if (pathIdxStart > pathIdxEnd) {
// Path is exhausted, only match if rest of pattern is * or **'s
// 模板數(shù)組也匹配結(jié)束楔绞,判斷模板和請(qǐng)求路徑是否均以'/'結(jié)尾结闸。不是返回true
// 此時(shí)得到的pattern是/helloController/sayHello2/{guest}
if (pattIdxStart > pattIdxEnd) {
return (pattern.endsWith(this.pathSeparator) == path.endsWith(this.pathSeparator));
}
if (!fullMatch) {
return true;
}
if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) {
return true;
}
for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
if (!pattDirs[i].equals("**")) {
return false;
}
}
return true;
}
// .....................后面還有很多.............................
return true;
}
至此已經(jīng)匹配到mapping就是/helloController/sayHello2/{guest},此時(shí)會(huì)從mappingLookup中獲取對(duì)應(yīng)的HandlerMethod酒朵,然后將其封裝成Match并返回HandlerMethod桦锄。最后在我們分析的第一個(gè)方法getHandler中會(huì)將得到的HandlerMethod封裝成HandlerExecutionChain,最后返回給DispatcherServlet蔫耽。
// 封裝過(guò)程很簡(jiǎn)單结耀,就是將handler保存下來(lái),就不展開(kāi)說(shuō)明了
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
現(xiàn)在我們分析完了url尋址獲取HandlerMethod的全過(guò)程匙铡,下篇文章我們來(lái)分析獲取HandlerAdapter和調(diào)用Handler的過(guò)程图甜。
由于本人能力有限,難免會(huì)有表述不清或錯(cuò)誤的地方鳖眼,還希望各位不吝指教黑毅,大家共同學(xué)習(xí),一起進(jìn)步具帮。