SpringMVC源碼系列:AbstractUrlHandlerMapping

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;
}
複製代碼

四個參數:

  • rawHandler 原始處理程序
  • bestMatchingPattern 最佳匹配模式
  • pathWithinMapping 在執行Handler以前公開的路徑
  • uriTemplateVariables 若是沒有找到變量,URI模板變量能夠是{null}

從代碼註釋翻譯及代碼內容能夠了解到,buildPathExposingHandler的做用就是給已經查找到的handler註冊兩個攔截器

  • ExposingHandlerInterceptor
  • UriTemplateVariablesHandlerInterceptor

這兩個類均是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中了。

  • 「/」:setRootHandler(resolvedHandler);
  • 「/*」:setDefaultHandler(resolvedHandler);

OK,到這AbstractUrlHandlerMapping這個類就分析完了,其實AbstractUrlHandlerMapping作的事情就是定義了一個框子,子類只要完成對Map的初始化就能夠了。關於AbstractUrlHandlerMapping的子類後續再談。

相關文章
相關標籤/搜索