• 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

  • 相关阅读:
    Building workspace has encountered a proble
    Eclipse异常关闭,IDE Exception Handler has encountered a problem An internal error has occurred
    jsp中写java代码的方法
    如何在jsp里面写java代码
    jsp中在java里面怎么调文本框里面的值?
    Typescript基本认识
    运行flutter run 报错Could not determine the dependencies of task ':app:compileDebugJavaWithJavac'.
    H5+原生webview实现APP的JavascriptBridge的使用
    vue mounted里使用window.onresize报错问题
    关于elmentui 抽屉 el-drawer 的slot插入的内容无法通过ref访问的问题
  • 原文地址:https://www.cnblogs.com/lvbinbin2yujie/p/10604478.html
Copyright © 2020-2023  润新知