Spring @Pathvariable


先記錄下@PathVariable的用法吧:

 @RequestMapping("/demo/{id}")
    @ResponseBody
    public User getUser(@PathVariable("id")Integer id, HttpServletRequest request){
        System.out.println(request.getAttribute(RequestMappingHandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE));
        List<User> list=new ArrayList<>();
        list.add(new User(0,"A"));
        list.add(new User(1,"B"));
        list.add(new User(2,"C"));
        list.add(new User(3,"D"));
        User user = list.get(id);
        return user;
}

使用方式一:就像上面那樣{}代表占位符,匹配URL中/ /兩個之間的內容,通過@PathVariable進行解析

使用方式二:通過request的RequestMappingHandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE這個屬性獲取到一個Map,然后根據上面的key進行取值

 

 

有時候很好奇Spring @Pathvariable怎么解析的,好像無論多少個 {} 都能正確的映射,看起來好像沒那么難。 但是我腦子不太行,嘗試分析分析看看吧。

就像以前做筆試題:正確答案在下面,雖然我肯定寫不出來,但是能看懂也挺為難我哈哈哈哈。

image

 

就像給定兩個輸入, pattern是標准路徑 ,就像 /url/{id} , 而lookupPath就是請求路徑,就像/url/19 ;

如果pattern和lookupPath一樣,就直接返回,這個不難理解,常規URL映射都是這么映射的;

考慮到實際情況以及簡化分析,useSuffixPatternMatch 默認為 true , fileExtensions 默認為空 ,以不帶后綴名形式分析 ,那就會進入AntPathMatcher分析;

protected boolean doMatch(String pattern, String path, boolean fullMatch, Map<String, String> uriTemplateVariables) {
         //pattern為標准路徑 /url/{id}   path為請求request路徑/url/19  
         //fullMatch默認為true ; uriTemplateVariables默認為null
         //pathSeparator默認為 / 
        //path和pattern剛開始都是/ 開頭, 肯定是false ; 這一步算是規則校驗  路徑不以/開頭的 直接返回false
	if (path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) {
		return false;
	}
         //標准路徑/url/{id}分隔成字符串數組 pattDirs 
	String[] pattDirs = tokenizePattern(pattern);
         //isPotentialMatch方法:
         //請求路徑 和 @RequestMapping路徑匹配 從頭匹配剛開始就不相等直接返回false 
         //解析過程前面相等遇到 { * ?類型返回true 這里邏輯等等再具體描述
	if (fullMatch && this.caseSensitive && !isPotentialMatch(path, pattDirs)) {
		return false;
	}
         //請求路徑拆分成字符串數組pathDirs 
	String[] pathDirs = tokenizePath(path);

	int pattIdxStart = 0;
	int pattIdxEnd = pattDirs.length - 1;
	int pathIdxStart = 0;
	int pathIdxEnd = pathDirs.length - 1;

	//循環遍歷是否 請求路徑字符數組 和 @RequestMapping路徑數組 正則匹配
        //{id}的正則表達式被解析為 (.*) 肯定可以匹配上
        //字符數組只要有一個元素沒匹配上就返回false
	while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
		String pattDir = pattDirs[pattIdxStart];
		if ("**".equals(pattDir)) {
			break;
		}
		if (!matchStrings(pattDir, pathDirs[pathIdxStart], uriTemplateVariables)) {
			return false;
		}
		pattIdxStart++;
		pathIdxStart++;
	}
         //上面如果匹配完成,pathIdxStart=pathIdxEnd+1  pattIdxStart=pattIdxEnd+1
	if (pathIdxStart > pathIdxEnd) {
		// Path is exhausted, only match if rest of pattern is * or **'s
		if (pattIdxStart > pattIdxEnd) {
                         // /url/{id}   /url/19 就匹配上了到這里 返回true
			return (pattern.endsWith(this.pathSeparator) ? path.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;
	}
	else if (pattIdxStart > pattIdxEnd) {
		// String not exhausted, but pattern is. Failure.
		return false;
	}
	else if (!fullMatch && "**".equals(pattDirs[pattIdxStart])) {
		// Path start definitely matches due to "**" part in pattern.
		return true;
	}

	// up to last '**'
	while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
		String pattDir = pattDirs[pattIdxEnd];
		if (pattDir.equals("**")) {
			break;
		}
		if (!matchStrings(pattDir, pathDirs[pathIdxEnd], uriTemplateVariables)) {
			return false;
		}
		pattIdxEnd--;
		pathIdxEnd--;
	}
	if (pathIdxStart > pathIdxEnd) {
		// String is exhausted
		for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
			if (!pattDirs[i].equals("**")) {
				return false;
			}
		}
		return true;
	}

	while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) {
		int patIdxTmp = -1;
		for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) {
			if (pattDirs[i].equals("**")) {
				patIdxTmp = i;
				break;
			}
		}
		if (patIdxTmp == pattIdxStart + 1) {
			// '**/**' situation, so skip one
			pattIdxStart++;
			continue;
		}
		// Find the pattern between padIdxStart & padIdxTmp in str between
		// strIdxStart & strIdxEnd
		int patLength = (patIdxTmp - pattIdxStart - 1);
		int strLength = (pathIdxEnd - pathIdxStart + 1);
		int foundIdx = -1;

		strLoop:
		for (int i = 0; i <= strLength - patLength; i++) {
			for (int j = 0; j < patLength; j++) {
				String subPat = pattDirs[pattIdxStart + j + 1];
				String subStr = pathDirs[pathIdxStart + i + j];
				if (!matchStrings(subPat, subStr, uriTemplateVariables)) {
					continue strLoop;
				}
			}
			foundIdx = pathIdxStart + i;
			break;
		}

		if (foundIdx == -1) {
			return false;
		}

		pattIdxStart = patIdxTmp;
		pathIdxStart = foundIdx + patLength;
	}

	for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
		if (!pattDirs[i].equals("**")) {
			return false;
		}
	}

	return true;
}

 

isPotentialMatch:進一步的過濾規則

private boolean isPotentialMatch(String path, String[] pattDirs) {
       // path 請求路徑 pattDirs標准路徑以 / 分隔出來的字符數組
	if (!this.trimTokens) {
                 //請求路徑轉成char數組
		char[] pathChars = path.toCharArray();
		int pos = 0;
		for (String pattDir : pattDirs) {
                  //請求路徑中第一次從0開始找到/的位置,下次就從上次找到的位置開始找下一個/
			int skipped = skipSeparator(path, pos, this.pathSeparator);
			pos += skipped;
                  //skipSegment從pathChars找出跳過pattDir的長度
			skipped = skipSegment(pathChars, pos, pattDir);
                 //skipped最理想情況等於pattDir的長度 但是通常通配符形式這里都是小於
  //比如映射中包含demo,路徑為do,這時候skipped也是2 也會返回true,但是之后的正則表達式校驗無法通過      
			if (skipped < pattDir.length()) {
				if (skipped > 0) {
					return true;
				}
                          return (pattDir.length() > 0) && isWildcardChar(pattDir.charAt(0));
			}
                 //skipped ==pattDir長度,全匹配上直接匹配下一個/之后內容
			pos += skipped;
		}
	}
	return true;
}

//函數作用  待匹配路徑字符數組  pos 代表 /所在的下一個位置  prefix標准路徑
//標准路徑包含wildcardChar { ? *  返回skipped 其他都會返回0 
private int skipSegment(char[] chars, int pos, String prefix) {
	int skipped = 0;
	for (char c : prefix.toCharArray()) {
		if (isWildcardChar(c)) {
			return skipped;
		}
		else if (pos + skipped >= chars.length) {
			return 0;
		}
		else if (chars[pos + skipped] == c) {
			skipped++;
		}
	}
	return skipped;
}

 

Spring考慮的很全面,最簡單的 /url/{id}   /url/19這種類型匹配完成了;

轉換完成以后,存入request域:以HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE作為KEY存儲

代碼位置:org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping#handleMatch

image

 

另外還一種特殊寫法:{name:正則表達式校驗}  比如我只希望URL中id為純數字 \d*即可

image

補充:如果正則匹配不了,拋出的錯誤是404頁面找不到.    帶:與不帶:的區別在於,不帶:就默認使用 .* 匹配,其他用法沒差別.

image

 

 

差點忘記記錄Spring如何解析@PathVariable注解?

Spring專門的接口HandlerMethodArgumentResolver用來解析方法入參,而PathVariableMethodArgumentResolver就是用來解析 @PathVariable注解的。

image

而請求參數綁定到 方法入參的方式:

可以看到也是從request屬性域HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE中取值,key就是@PathVariable(“yourname”)中的yourname取值.

image


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM