Spring源码深度解析(郝佳)-学习-资源匹配-doMatch

20 篇文章 1 订阅
18 篇文章 0 订阅

        Spring中有一个doMatch的工具方法,在代码中很多地方都用到过,比如资源加载,Spring MVC中url匹配,那我们写一个main方法来测试一下这个方法。

public class SpringTest1_1 {
    //默认路径为/
    public static final String DEFAULT_PATH_SEPARATOR = "/";

    private static final int CACHE_TURNOFF_THRESHOLD = 65536;

    private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{[^/]+?\\}");

    private static volatile Boolean cachePatterns;

    public static boolean caseSensitive = true;

    private static boolean trimTokens = true;

    private static String pathSeparator = "/";

    private static final Map<String, String[]> tokenizedPatternCache = new ConcurrentHashMap<String, String[]>(256);

    public static final Map<String, AntPathStringMatcher> stringMatcherCache = new ConcurrentHashMap<String, AntPathStringMatcher>(256);

    public static void main(String[] args) {
        String pattern = "/**/a/**/b";
        String path = "/a/a/b";
        boolean a = doMatch(pattern, path, true, null);
        System.out.println(a);
    }

    protected static boolean doMatch(String pattern, String path, boolean fullMatch, Map<String, String> uriTemplateVariables) {
        if (path.startsWith(pathSeparator) != pattern.startsWith(pathSeparator)) {
            return false;
        }
        // 1.1. 分解模式字符串
        String[] pattDirs = tokenizePattern(pattern);
        // 1.2 分解路径字符串
        String[] pathDirs = tokenizePath(path);
        // pattern的可分配下标 pattIdxStart ~ pattIdxEnd
        // path的可分配下标	pathIdxStart ~ pathIdxEnd
        int pattIdxStart = 0;
        int pattIdxEnd = pattDirs.length - 1;
        int pathIdxStart = 0;
        int pathIdxEnd = pathDirs.length - 1;

        // Match all elements up to the first **
        // 2. 第一个while 循环, 用来判断绝对匹配的   /xxx/abc ==> /xxx/abc
        // 两个字符串都从下标0开始, 直到模式字符串遇到**结束
        while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
            String pattDir = pattDirs[pattIdxStart];
            if ("**".equals(pattDir)) {
                break;
            }
            if (!matchStrings(pattDir, pathDirs[pathIdxStart], uriTemplateVariables)) {
                return false;
            }
            pattIdxStart++;
            pathIdxStart++;
        }
        // pathIdxStart > pathIdEnd, 表示文件路径(path), 已经逐一的匹配到了
        if (pathIdxStart > pathIdxEnd) {
            // 数组中第一个值是pattern,第二个值是path ,后面所有注释都是如此 [/xxx/abc,/xxx/abc]  , [ /xxx/*,/xxx/abc]
            if (pattIdxStart > pattIdxEnd) {
                return (pattern.endsWith(pathSeparator) ? path.endsWith(pathSeparator) :
                        !path.endsWith(pathSeparator));
            }
            // [/xxx/abc/*,/xxx/abc] , [/xxx/abc/ddd,/xxx/abc] && fullMatch = false
            if (!fullMatch) {
                return true;
            }
            // [/*/*/,/abc/] , [/abc/*,/abc/]    &&   fullMatch == true
            if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(pathSeparator)) {
                return true;
            }
            for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
                // [/*/*/,/abc/] , [/abc/*,/abc ]    &&   fullMatch == true 注意 第二个数组中abc后面没有【/】
                if (!pattDirs[i].equals("**")) {
                    return false;
                }
            }
            // [/abc/**,/abc]  && fullMatch == true
            return true;
        }
        // [/abc/def , /abc/def/ccc ] , [/abc/* , /abc/def/ccc ]
        else if (pattIdxStart > pattIdxEnd) {
            return false;
        }
        // [/abc/** , /abc/def/ccc ] && fullMatch == false
        else if (!fullMatch && "**".equals(pattDirs[pattIdxStart])) {
            return true;
        }

        // 3. 两个字符串数组都从最后的下标开始匹配, 直到遇到pattDir为'**'时结束
        while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
            String pattDir = pattDirs[pattIdxEnd];
            if (pattDir.equals("**")) {
                break;
            }
            if (!matchStrings(pattDir, pathDirs[pathIdxEnd], uriTemplateVariables)) {
                // [/xxxx/abcd/**/ddd.class,/xxxx/abcd/xxx.class] && fullMatch == true
                return false;
            }
            pattIdxEnd--;
            pathIdxEnd--;
        }
        if (pathIdxStart > pathIdxEnd) {
            for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
                if (!pattDirs[i].equals("**")) {
                    // [/**/xxx/bb, /bb]  && fullMatch == true
                    return false;
                }
            }
            // 这里返回true 一般字符串为
            // [/xxxx/abcd/**/*.class, /xxxx/abcd/xxx.class]  && fullMatch == true
            // 即只有一个**, 而且**没发挥到什么作用
            return true;
        }
        // 4. 第3个while循环, 主要解决有多个'**'字符串.	/**/djdjdjd/**, /a/**/**/b/**/c/**/*.class等
        // 每次下标又从pattIdxStart+1开始
        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
            // patLength: 两个'**'之间的字符串的长度  /**/a/b/** = 2
            // strLength: 路径还剩多少个没匹配 	/a/b/c/d	如果/a/b都匹配了, 就只剩下/b/c = 2
            int patLength = (patIdxTmp - pattIdxStart - 1);
            int strLength = (pathIdxEnd - pathIdxStart + 1);
            int foundIdx = -1;

            strLoop:
            // 因为已经确定了有 /**/a/b/**这样的模式字符串存在, 中间2长度
            // 如果存在/q/a/b/c/d 有5个长度, 那么就要循环3次
            // 第一次匹配 /a/b => /q/a
            // 第二次	 /a/b => /a/b	=> 这里已经匹配到了, 所以就break了
            // 			 /a/b => /b/c
            // 			 /a/b => /c/d
            // 当然, 如果存在更复杂的如: /**/a/b/**/a/b/**/a/b/**, 外层的while循环就会做3次判断,
            // [/**/a/b/**/a/b/**/a/b/**,/q/q/q/a/b/q/q/q/a/b/q/q/q/a/b/q/q/q/a/b] &&  fullMatch == true
            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) {
                // [/**/a/b/c/**/c,/q/a/b/c ] &&  fullMatch == true
                return false;
            }

            pattIdxStart = patIdxTmp;
            pathIdxStart = foundIdx + patLength;
        }

        for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
            if (!pattDirs[i].equals("**")) {
                // 实在想不出哪种情况了。以后遇到再来补吧
                return false;
            }
        }
        // 如果上面的都没有返回值  ....就会在此处返回 [/**, /sdjdd/djkd/] &&  fullMatch == true
        return true;
    }

上面匹配有4个步骤

  1. 分解模式字符串, 分解路径字符串
  2. 第一个while 循环, 用来判断绝对匹配 /xxx/abc ==> /xxx/abc
  3. 第二个while循环两个字符串数组都从最后的下标开始匹配, 直到遇到pattDir为’**'时结束
  4. 第三个while循环, 主要解决有多个’**'字符串. /**/djdjdjd/**,/a/**/**/b/**/c/**/*.class等
    public static String[] tokenizePattern(String pattern) {
        String[] tokenized = null;
        Boolean cachePatterns = SpringTest1_1.cachePatterns;
        if (cachePatterns == null || cachePatterns.booleanValue()) {
            tokenized = tokenizedPatternCache.get(pattern);
        }
        if (tokenized == null) {
            tokenized = tokenizePath(pattern);
            if (cachePatterns == null && tokenizedPatternCache.size() >= 65536) {
                deactivatePatternCache();
                return tokenized;
            }
            if (cachePatterns == null || cachePatterns.booleanValue()) {
                tokenizedPatternCache.put(pattern, tokenized);
            }
        }
        return tokenized;
    }

    private static void deactivatePatternCache() {
        cachePatterns = false;
        tokenizedPatternCache.clear();
        stringMatcherCache.clear();
    }

    protected static String[] tokenizePath(String path) {
        return tokenizeToStringArray(path, "/", trimTokens, true);
    }


	public static String[] tokenizeToStringArray(
			String str, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens) {

		if (str == null) {
			return null;
		}
		StringTokenizer st = new StringTokenizer(str, delimiters);
		List tokens = new ArrayList();
		while (st.hasMoreTokens()) {
			String token = st.nextToken();
			if (trimTokens) {
				token = token.trim();
			}
			if (!ignoreEmptyTokens || token.length() > 0) {
				tokens.add(token);
			}
		}
		return toStringArray(tokens);
	}
	
    private static boolean matchStrings(String pattern, String str, Map<String, String> uriTemplateVariables) {
        return getStringMatcher(pattern).matchStrings(str, uriTemplateVariables);
    }

    protected static AntPathStringMatcher getStringMatcher(String pattern) {
        AntPathStringMatcher matcher = null;
        Boolean cachePatterns = SpringTest1_1.cachePatterns;
        if (cachePatterns == null || cachePatterns.booleanValue()) {
            matcher = stringMatcherCache.get(pattern);
        }
        if (matcher == null) {
            matcher = new AntPathStringMatcher(pattern, caseSensitive);
            if (cachePatterns == null && stringMatcherCache.size() >= CACHE_TURNOFF_THRESHOLD) {
                // Try to adapt to the runtime situation that we're encountering:
                // There are obviously too many different patterns coming in here...
                // So let's turn off the cache since the patterns are unlikely to be reoccurring.
                deactivatePatternCache();
                return matcher;
            }
            if (cachePatterns == null || cachePatterns.booleanValue()) {
                stringMatcherCache.put(pattern, matcher);
            }
        }
        return matcher;
    }

    protected static class AntPathStringMatcher {
        private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?\\}|[^/{}]|\\\\[{}])+?)\\}");

        private static final String DEFAULT_VARIABLE_PATTERN = "(.*)";

        private final Pattern pattern;

        private final List variableNames = new LinkedList();

        public AntPathStringMatcher(String pattern) {
            this(pattern, true);
        }

        public AntPathStringMatcher(String pattern, boolean caseSensitive) {
            StringBuilder patternBuilder = new StringBuilder();
            Matcher matcher = GLOB_PATTERN.matcher(pattern);
            int end = 0;
            while (matcher.find()) {
                patternBuilder.append(quote(pattern, end, matcher.start()));
                String match = matcher.group();
                if ("?".equals(match)) {
                    patternBuilder.append('.');
                }
                else if ("*".equals(match)) {
                    patternBuilder.append(".*");
                }
                else if (match.startsWith("{") && match.endsWith("}")) {
                    int colonIdx = match.indexOf(':');
                    if (colonIdx == -1) {
                        patternBuilder.append(DEFAULT_VARIABLE_PATTERN);
                        this.variableNames.add(matcher.group(1));
                    }
                    else {
                        String variablePattern = match.substring(colonIdx + 1, match.length() - 1);
                        patternBuilder.append('(');
                        patternBuilder.append(variablePattern);
                        patternBuilder.append(')');
                        String variableName = match.substring(1, colonIdx);
                        this.variableNames.add(variableName);
                    }
                }
                end = matcher.end();
            }
            patternBuilder.append(quote(pattern, end, pattern.length()));
            this.pattern = (caseSensitive ? Pattern.compile(patternBuilder.toString()) :
                    Pattern.compile(patternBuilder.toString(), Pattern.CASE_INSENSITIVE));
        }

        private String quote(String s, int start, int end) {
            if (start == end) {
                return "";
            }
            return Pattern.quote(s.substring(start, end));
        }

        public boolean matchStrings(String str, Map<String, String> uriTemplateVariables) {
            Matcher matcher = this.pattern.matcher(str);
            if (matcher.matches()) {
                if (uriTemplateVariables != null) {
                    if (this.variableNames.size() != matcher.groupCount()) {
                        throw new IllegalArgumentException("The number of capturing groups in the pattern segment " +
                                this.pattern + " does not match the number of URI template variables it defines, " +
                                "which can occur if capturing groups are used in a URI template regex. " +
                                "Use non-capturing groups instead.");
                    }
                    for (int i = 1; i <= matcher.groupCount(); i++) {
                        String name = this.variableNames.get(i - 1);
                        //使用正则进行匹配
                        String value = matcher.group(i);
                        uriTemplateVariables.put(name, value);
                    }
                }
                return true;
            }
            else {
                return false;
            }
        }
    }
}

        Spring对不同种类的字符串都做了适配,因此,从一个简单的工具类方法就可以看出Spring的博大精深。

本文的github地址是
[https://github.com/quyixiao/spring_tiny/blob/master/src/main/java/com/spring_1_100/test_1_10/test/SpringTest1_1.java]

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值