AbstractUrlHandlerMapping
是經過url來進行匹配的,也就是說經過url與對應的Handler包存到一個Map中,而後在getHandlerInternal方法中使用url做爲key從Map中獲取咱們的handler。java
AbstractUrlHandlerMapping
實現了從url獲取handler的過程,具體的映射關係,也就是handlerMap則是交給具體子類來去完成的。AbstractUrlHandlerMapping
中定義了handlerMap用來維護映射關係,以下:app
private final Map<String, Object> handlerMap =
new LinkedHashMap<String, Object>();
複製代碼
除此以外,還有一個rootHandler,這個用於處理「/」請求。ide
在前面三篇文章中提到過,handler的獲取是經過getHandlerInternal方法完成的,下面看下具體的源碼,分析下handler的獲取和handlerMap的構建。ui
//查找給定請求的URL路徑的Handler。
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
//使用lookupPath從Map中查找handler
Object handler = lookupHandler(lookupPath, request);
if (handler == null) {
//臨時變量,保存原始的handler
Object rawHandler = null;
//是不是‘/’根路徑
if ("/".equals(lookupPath)) {
//獲取rootHandler
rawHandler = getRootHandler();
}
//若是rawHandler是null
if (rawHandler == null) {
//獲取默認的handler
rawHandler = getDefaultHandler();
}
//若是rawHandler不是null
if (rawHandler != null) {
// 若是是string類型,則到容器中查找具體的bean
if (rawHandler instanceof String) {
String handlerName = (String) rawHandler;
//容器中獲取
rawHandler = getApplicationContext().getBean(handlerName);
}
//校驗handler和request是否匹配
validateHandler(rawHandler, request);
//註冊攔截器
handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
}
}
//日誌debug
if (handler != null && logger.isDebugEnabled()) {
logger.debug("Mapping [" + lookupPath + "] to " + handler);
}
else if (handler == null && logger.isTraceEnabled()) {
logger.trace("No handler mapping found for [" + lookupPath + "]");
}
//返回handler
return handler;
}
複製代碼
在getHandlerInternal
方法中有幾個方法調用,像getLookupPathForRequest、getRootHandler、getDefaultHandler、lookupHandler、buildPathExposingHandler等。其中getLookupPathForRequest、getRootHandler、getDefaultHandler這幾個沒啥好說的;比較核心的就是lookupHandler、buildPathExposingHandler這兩個方法。this
lookupHandlerurl
lookupHandler使用getUrlPathHelper().getLookupPathForRequest(request)獲取到的lookupPath從Map中查找須要的Handler,一般狀況下是直接get不到的。爲何呢?緣由在於不少的handler都是使用了Pattern的匹配模式,好比說「/user/*」,星號表示匹配任意內容,並不是是指定url串中的字符。若是Pattern中包含了PathVariable,也不能直接從Map中獲取到。spa
除此以外,一個url還可能和多個Pattern相匹配,那麼這個時候我們確定就須要選擇最優的,因此說查找過程其實並非直接從map中獲取那麼簡單。那麼就來看下在lookupHandler中都幹了哪些事情:翻譯
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
// 直接匹配,直接從Map中獲取
Object handler = this.handlerMap.get(urlPath);
//取到了
if (handler != null) {
// 若是是string類型,則從容器中獲取Bean
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
//驗證是否匹配
validateHandler(handler, request);
//註冊攔截器
return buildPathExposingHandler(handler, urlPath, urlPath, null);
}
// Pattern 匹配,帶*號的模式與url進行匹配
List<String> matchingPatterns = new ArrayList<String>();
for (String registeredPattern : this.handlerMap.keySet()) {
if (getPathMatcher().match(registeredPattern, urlPath)) {
matchingPatterns.add(registeredPattern);
}
else if (useTrailingSlashMatch()) {
if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) {
matchingPatterns.add(registeredPattern +"/");
}
}
}
//獲取最佳匹配
String bestPatternMatch = null;
Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
if (!matchingPatterns.isEmpty()) {
Collections.sort(matchingPatterns, patternComparator);
if (logger.isDebugEnabled()) {
logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns);
}
bestPatternMatch = matchingPatterns.get(0);
}
//最佳匹配不爲null
if (bestPatternMatch != null) {
//從Map中看看是否有對應的Handler
handler = this.handlerMap.get(bestPatternMatch);
//若是Map中沒有
if (handler == null) {
//是否以/結尾
Assert.isTrue(bestPatternMatch.endsWith("/"));
//去除/以後再獲取一次
handler = this.handlerMap.get(bestPatternMatch.substring(0, bestPatternMatch.length() - 1));
}
// 若是是String類型,則從容器中獲取Bean?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
//驗證是否匹配
validateHandler(handler, request);
String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestPatternMatch, urlPath);
// 可能有多種最佳模式,讓咱們確保咱們有正確的URI模板變量(譯)
Map<String, String> uriTemplateVariables = new LinkedHashMap<String, String>();
for (String matchingPattern : matchingPatterns) {
if (patternComparator.compare(bestPatternMatch, matchingPattern) == 0) {
Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);
uriTemplateVariables.putAll(decodedVars);
}
}
if (logger.isDebugEnabled()) {
logger.debug("URI Template variables for request [" + urlPath + "] are " + uriTemplateVariables);
}
return buildPathExposingHandler(handler, bestPatternMatch, pathWithinMapping, uriTemplateVariables);
}
// No handler found...
return null;
}
複製代碼
上面代碼中,關於譯註的部分須要說一下;代碼以下:debug
Map<String, String> uriTemplateVariables = new LinkedHashMap<String, String>();
for (String matchingPattern : matchingPatterns) {
if (patternComparator.compare(bestPatternMatch, matchingPattern) == 0) {
Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
Map<String, String> decodedVars =
getUrlPathHelper().decodePathVariables(request, vars);
uriTemplateVariables.putAll(decodedVars);
}
}
複製代碼
以前是經過sort方法進行排序的,而後將第一個做爲bestPatternMatch,可是若是多個pattern的順序相同,也就是說sort返回的是0,存在多種最佳匹配,那就須要確保咱們有正確的URI模板變量。上面代碼就是處理這種狀況的。日誌
buildPathExposingHandler
這個方法在上面的兩段代碼中都頻繁出現,那麼這個方法到底有什麼做用呢?代碼中我註釋的是註冊攔截器,那麼註冊的又是什麼攔截器?帶着這兩個問題,咱們來看下代碼。
//buildPathExposingHandler爲給定的rawHandler構建一個Handler對象,並在執
//行處理程序以前暴露實際的處理程序PATH_WITHIN_HANDLER_MAPPING_ATTRIBUT
//E以及URI_TEMPLATE_VARIABLES_ATTRIBUTE。
//默認實現用一個特殊的攔截器構建一個HandlerExecutionChain,該攔截器暴露
//path屬性和uri模板變量。
protected Object buildPathExposingHandler(Object rawHandler, String bestMatchingPattern, String pathWithinMapping, Map<String, String> uriTemplateVariables) {
HandlerExecutionChain chain = new HandlerExecutionChain(rawHandler);
chain.addInterceptor(new PathExposingHandlerInterceptor(bestMatchingPattern, pathWithinMapping));
if (!CollectionUtils.isEmpty(uriTemplateVariables)) {
chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables));
}
return chain;
}
複製代碼
四個參數:
從代碼註釋翻譯及代碼內容能夠了解到,buildPathExposingHandler的做用就是給已經查找到的handler註冊兩個攔截器
這兩個類均是AbstractUrlHandlerMapping
的內部類,也就是兩個內部攔截器。這兩個攔截器的主要做用就是將與當前url實際匹配的pattern、匹配條件以及url模板參數等設置到request的屬性裏面去,這樣在後面的處理過程當中就能夠直接從request屬性中獲取。看下兩個內部類的定義:
private class PathExposingHandlerInterceptor extends HandlerInterceptorAdapter {
private final String bestMatchingPattern;
private final String pathWithinMapping;
public PathExposingHandlerInterceptor(String bestMatchingPattern, String pathWithinMapping) {
this.bestMatchingPattern = bestMatchingPattern;
this.pathWithinMapping = pathWithinMapping;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
exposePathWithinMapping(this.bestMatchingPattern,
this.pathWithinMapping, request);
//設置request屬性
request.setAttribute(HandlerMapping.INTROSPECT_TYPE_LEVEL_MAPPING, supportsTypeLevelMappings());
return true;
}
}
private class UriTemplateVariablesHandlerInterceptor extends HandlerInterceptorAdapter {
private final Map<String, String> uriTemplateVariables;
public UriTemplateVariablesHandlerInterceptor(Map<String, String> uriTemplateVariables) {
this.uriTemplateVariables = uriTemplateVariables;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
//這exposeUriTemplateVariables種設置request屬性
exposeUriTemplateVariables(this.uriTemplateVariables, request);
return true;
}
}
複製代碼
從內部類的代碼能夠看出,這兩個內部類是經過在preHandle方法中調用exposePathWithinMapping和exposeUriTemplateVariables完成屬性設置到request中的。
對於查找handler的關鍵其實就是維護url和handler的映射關係,也就是handlerMap的構建。在AbstractUrlHandlerMapping
中是經過registerHandler這個方法來構建handlerMap的。AbstractUrlHandlerMapping
提供了兩個registerHandler方法,下面就經過代碼來看下具體的實現。
protected void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException {
Assert.notNull(urlPaths, "URL path array must not be null");
for (String urlPath : urlPaths) {
registerHandler(urlPath, beanName);
}
}
複製代碼
第一個registerHandler是將多個url註冊到一個處理器。beanName其實就是我們處理器的名稱,能夠經過beanName到容器中去找到真正的處理器Bean。具體處理就是經過遍歷全部的url,而後再經過調用第二個registerHandler將handler註冊到handlerMap中。來看第二個:
protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
Assert.notNull(urlPath, "URL path must not be null");
Assert.notNull(handler, "Handler object must not be null");
Object resolvedHandler = handler;
// 若是的handler是string類型,而且不是lazyInitHandlers,則從SpringMV
//C容器中獲取handler
if (!this.lazyInitHandlers && handler instanceof String) {
String handlerName = (String) handler;
if (getApplicationContext().isSingleton(handlerName)) {
resolvedHandler = getApplicationContext().getBean(handlerName);
}
}
Object mappedHandler = this.handlerMap.get(urlPath);
if (mappedHandler != null) {
if (mappedHandler != resolvedHandler) {
//異常處理
}
}
else {
//是不是跟路徑
if (urlPath.equals("/")) {
if (logger.isInfoEnabled()) {
logger.info("Root mapping to " +
getHandlerDescription(handler));
}
setRootHandler(resolvedHandler);
}
//是不是*模式
else if (urlPath.equals("/*")) {
if (logger.isInfoEnabled()) {
logger.info("Default mapping to " +
getHandlerDescription(handler));
}
setDefaultHandler(resolvedHandler);
}
//加入到handlerMap中
else {
this.handlerMap.put(urlPath, resolvedHandler);
if (logger.isInfoEnabled()) {
logger.info("Mapped URL path [" + urlPath + "] onto " +
getHandlerDescription(handler));
}
}
}
}
複製代碼
這個裏面首先是看Map中是否有原來傳入的url,若是沒有就加入,若是有就看下原來保存的和當前註冊的handler是不是同一個,若是不是同一個就拋出異常。(同一個url不可能存在兩個不一樣的handler)。
在put以前,也作了一些「/」和「/*」的驗證處理,若是是這兩種路徑的話就不保存到handlerMap中了。
OK,到這AbstractUrlHandlerMapping
這個類就分析完了,其實AbstractUrlHandlerMapping
作的事情就是定義了一個框子,子類只要完成對Map的初始化就能夠了。關於AbstractUrlHandlerMapping
的子類後續再談。