Spring AntPathMatcher 实现原理

我们两清 提交于 2019-11-30 01:00:49

 

最近在看Spring 源码,源码中多次出现AntPatternMatcher 类, 此类作用用于实现对URL的匹配

 

 

 

关于此类的介绍,其中有些代码是从Apache Ant 那 "借"过来的

 

 

 

PathMatcher implementation for Ant-style path patterns. Examples are provided below.

Part of this mapping code has been kindly borrowed from Apache Ant.

The mapping matches URLs using the following rules:

  • ? matches one character
  • * matches zero or more characters
  • ** matches zero or more 'directories' in a path

Some examples:

  • com/t?st.jsp - matches com/test.jsp but also com/tast.jsp or com/txst.jsp
  • com/*.jsp - matches all .jsp files in the com directory
  • com/**/test.jsp - matches all test.jsp files underneath the com path
  • org/springframework/**/*.jsp - matches all .jsp files underneath the org/springframework path
  • org/**/servlet/bla.jsp - matches org/springframework/servlet/bla.jsp but also org/springframework/testing/servlet/bla.jsp and org/servlet/bla.jsp



看一下其核心的方法:


/**
	 * Actually match the given <code>path</code> against the given <code>pattern</code>.
	 * @param pattern the pattern to match against
	 * @param path the path String to test
	 * @param fullMatch whether a full pattern match is required (else a pattern match
	 * as far as the given base path goes is sufficient)
	 * @return <code>true</code> if the supplied <code>path</code> matched, <code>false</code> if it didn't
	 */
	protected boolean doMatch(String pattern, String path, boolean fullMatch,
			Map<String, String> uriTemplateVariables) {

		if (path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) {
			return false;
		}

		String[] pattDirs = StringUtils.tokenizeToStringArray(pattern, this.pathSeparator);
		String[] pathDirs = StringUtils.tokenizeToStringArray(path, this.pathSeparator);

		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 patDir = pattDirs[pattIdxStart];
			if ("**".equals(patDir)) {
				break;
			}
			if (!matchStrings(patDir, pathDirs[pathIdxStart], uriTemplateVariables)) {
				return false;
			}
			pattIdxStart++;
			pathIdxStart++;
		}

		if (pathIdxStart > pathIdxEnd) {
			// Path is exhausted, only match if rest of pattern is * or **'s
			if (pattIdxStart > pattIdxEnd) {
/**            /a/b/c  and /a/b/c/ ==>false                            **/
				return (pattern.endsWith(this.pathSeparator) ? path.endsWith(this.pathSeparator) :
						!path.endsWith(this.pathSeparator));
			}
			if (!fullMatch) {
				return true;
			}
/**  /a/b/c/*  and /a/b/c/  ==>true    /a/b/c/* and /a/b/c   ==> false **/
if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) {
				return true;
			} /**  /a/b/c/**/**/  and /a/b/c    ==> true      /a/b/c/**/a/   and /a/b/c  ==>false         **/
			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 patDir = pattDirs[pattIdxEnd];
			if (patDir.equals("**")) {
				break;
			}
			if (!matchStrings(patDir, pathDirs[pathIdxEnd], uriTemplateVariables)) {
				return false;
			}
			pattIdxEnd--;
			pathIdxEnd--;
		}
		if (pathIdxStart > pathIdxEnd) {  /**     /**/a/b/c and  /a/b/c  ==> true **/
			// String is exhausted
			for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
				if (!pattDirs[i].equals("**")) {
					return false;
				}
			}
			return true;
		}         /**             /a/b/c/d/**/**/e/f/g/h/**/i/j/k/**/l/m/n/**/o/p/q  and  /a/b/c/d/a/b/e/f/g/h/a/b/i/j/k/a/b/l/m/n/a/b/o/p/q ==> 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;
	}
 

其实现原理如下:

1) 先把Pattern 和要与之匹配的字符窜 Path 进行 转化为 String Array, 分隔符为  "/"

    例如: /a/**/b/c/d/  ==>["a","**","b","c","d"]

String[] pattDirs = StringUtils.tokenizeToStringArray(pattern, this.pathSeparator);
		String[] pathDirs = StringUtils.tokenizeToStringArray(path, this.pathSeparator);

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


2) 先匹配 Pattern 中"**" 前的元素 ,这里说的元素是指两数组的元素,

   // Match all elements up to the first **
while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
			String patDir = pattDirs[pattIdxStart];
			if ("**".equals(patDir)) {
				break;
			}
			if (!matchStrings(patDir, pathDirs[pathIdxStart], uriTemplateVariables)) {
				return false;
			}
			pattIdxStart++;
			pathIdxStart++;
		}

 

    跳出这个loop的条件是, 

 

     1.pattIdxStart>pattIdxEnd  (没有找到"**")

 

       说明在还没有找到有 "**"之前Pattern 在循环中溢出,即Pattern的长度比Path的长度要短,则结果肯定不匹配,应直接返回false

 

 

else if (pattIdxStart > pattIdxEnd) {
			// String not exhausted, but pattern is. Failure.
			return false;
		}
 
   2.pathIdxStart > pathIdxEnd (没有找到"**")
   
     则在还没有找到 "**" 之前 Path在循环中溢出, 即Path的长度比Pattern的短,或者两者长度一样,
    如果长度一样,则比较两者的最后一个字符是否相等,因为在转化为Array的时候,最后的字符'/' 会被 省略,所以,数组的元素匹配不一定说明最后的一个字符会相等,这时就需要判断是否一样,如果一样,则返回true,如果不一样,则返回false,

  第二种情况就是Pattern的长度比Path长, 那匹配成功的情况就只有两种, 一是Pattern以* 号或者是** 号结尾,其中如果
 
 是Pattern是以* 号结尾, Pattern 必须比Path只大一个元素,而且Path 的最后一样字符需为"/",因为 * 号只代表 0-n 个字符, 并没有 代表 "Directory" 的意思, Path和Pattern 的Directory 的深度必须一样, 所以判定结果如果 Path是以 "/" 结尾 并而且Pattern只比Path的元素少一个* 号,则返回true

 如果Pattern比Path多一个以上的元素, 则后面的元素必须为 "**" 号, 否则返回false

 
 
     if (pathIdxStart > pathIdxEnd) {
// Path is exhausted, only match if rest of pattern is * or **'s
			if (pattIdxStart > pattIdxEnd) {
				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;
		}
 
3) 然后后匹配 Pattern  "**" 之后的元素, 如果其中匹配不成功,则返回false
   这里有一个特殊的情况, 就是在没有找到 ** 号之前, Path 字符串提前退出,
   特例 /**/**/**/a/b/c  和  /a/b/c  ==>true
        /**/a/b/**/a/b/c   和 /a/b/c  ==> false
   则判断Pattern的pattIdxStart 到pattIdxEnd 之间是否全部为 ** 号, 如果不是 , 则返回false
    // up to last '**'
while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
			String patDir = pattDirs[pattIdxEnd];
			if (patDir.equals("**")) {
				break;
			}
			if (!matchStrings(patDir, 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;
		}
 

   第二种情况, Pattern和Path都没提前溢出退出循环, 则属于 这种情况

 /a/b/c/d/**/**/e/f/g/h/**/i/j/k/**/l/m/n/**/o/p/q and           /a/b/c/d/a/b/e/f/g/h/a/b/i/j/k/a/b/l/m/n/a/b/o/p/q ==> true 

 

这时候要处理多个 ** 号的, 分段处理, ** 号之间的字符匹配, 如果 ** 号之间的字符不匹配, 则返回false

 

第一段:

/a/b/c/d/**/**/e/f/g/h/**/

and  

a/b/e/f/g/h/a/b/i/j/k/a/b/l/m/n/a/b/o/p/q 

 

==>true

 

 

第二段:

/**/i/j/k/**/l

 

and 

 

 

/a/b/i/j/k/a/b/l/m/n/a/b/o/p/q

 

==>true

 

 

第三段:

 

/**/l/m/n/**/

 

 

/a/b/l/m/n/a/b/o/p/q 

 

==>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;
		}
 

 

 

 

 

 

 

 

转载于:https://my.oschina.net/iqoFil/blog/221620

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!