【Spring MVC】之 URL Patterns

Spring使用的是AntPathMatcher,进行模式匹配,遵循如下规则:

  • ? 匹配一个字符
  • * 匹配零或者多个字符
  • ** 匹配零或者多个目录
  • {spring:[a-z]+} 将正则表达式[a-z]+匹配到的值,赋值给名为spring的路径变量.

对阵AntPathMatcherTests的测试方法,中会有比较全面的测试用例,可以先参考

核心的匹配方法

这个方法,如果给我们普通人写,觉得是那种两行代码写出十个bug的,逻辑甚是复杂啊。

	@Override
	public boolean match(String pattern, String path) {
		return doMatch(pattern, path, true, null);
	}

	protected boolean doMatch(String pattern, @Nullable String path, boolean fullMatch,
			@Nullable Map<String, String> uriTemplateVariables) {

		// 如果path 为null返回false
		// 如果一个以"/"开头,一个不是,则发挥false
		if (path == null || path.startsWith(this.pathSeparator) 
			!= pattern.startsWith(this.pathSeparator)) {
			return false;
		}
		// 将给定的pattern,分组 比如path/we/{1}/* => ["path","we","{1}","*"]
		String[] pattDirs = tokenizePattern(pattern);
		//isPotentialMatch 判断是不是潜在匹配,也就是包含通配符,在WILDCARD_CHARS中
		if (fullMatch && this.caseSensitive && !isPotentialMatch(path, pattDirs)) {
			return false;
		}
		// 包含通配符,则进行通配符匹配,比如有一个请求/id/1/test,分解成 ["id","1","test"]
		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];
			// 如果pattern包含"**"直接过
			if ("**".equals(pattDir)) {
				break;
			}
			// 判断是否匹配,如果不匹配,直接返回
			if (!matchStrings(pattDir, pathDirs[pathIdxStart], uriTemplateVariables)) {
				return false;
			}
			pattIdxStart++;
			pathIdxStart++;
		}
		// 已经匹配完了, 比如 path= /aa/bb  pattern = /aa/bb
		// 除非是pattern后面是/* 或者 /** 否则返回false
		if (pathIdxStart > pathIdxEnd) {
			// Path is exhausted, only match if rest of pattern is * or **'s
			// 路径已用尽,仅当模式的其余部分为*或**'s时才匹配
			if (pattIdxStart > pattIdxEnd) {
				// 如果有一个@GetMapping("{id}/one/") 方法,
				// 则必须用xx/one/请求,才返回,用xx/one是不行的
				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;
			}
			// 如果剩余的,都不是** 则返回false,
			// 否则返回true @GetMapping("/one/*/**") 有一个请求  /one/
			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.
			// 字符串没有用尽,但模式是失败,来了一个请求 @GetMapping("/") 来了个/a/two/请求
			return false;
		}
		// 如果是 ** 开头  @GetMapping("**")
		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)) {
				// 路径:/1231/aa/bb/dd  @GetMapping("/**/aa/bb")
				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;
			}
		}
		// 比如 :/1231/aa/bb/d/aa/bb/和**/aa/bb/
		return true;
	}

当匹配多个的时候,如何选择

当多个模式与请求URI匹配时,必须将它们进行比较以找到最佳匹配。 通过找到最具体的匹配来完成。 计算分数以找到最具体的匹配项。 最低分获胜。 应用以下规则:

  • 单个通配符(*)被视为一分。
  • 双通配符(**)被视为两分。
  • 没有任何模式(例如/cars/ dealer)的URI路径计数为零分。一个’?'被计数为零分(例如/ car?/d??r的总计数为零)。
  • /card/{type}计为一分。
  • 如果只有/ **@RequestMapping("/**"),则始终在末尾进行匹配。在没有双通配符的模式之后,还会匹配/employee/**之类的patterns(前缀模式,以/ **结尾的模式)。
  • 在Mapping变量中带有*的正则表达式(例如/card/{id:n.*}也被视为一分。而其他正则表达式量词(+或?)零分。
  • 如果多个模式具有相等的分数,则选择更长的模式。此处的长度是通过计算的String#length()。对于Mapping,在计算长度之前,{}之间的内容会减少为单个#(例如,将/card/{var1}/{var2}更改为/card/#/#)。
  • 此时,如果两个模式的长度相同,则选择通配符(*)较少的模式。
  • 此时,如果两个模式具有相同数量的通配符(*),则选择模板变量数量较少的模式。
    以上逻辑是在AntPathStringMatcher#compare方法中实现的(此类是的嵌套类AntPathMatcher)。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值