Spring源码导读之资源加载

本文深入探讨了Spring框架中资源加载的机制,包括AntPathMatcher的模式匹配原理,ResourceLoader和ResourcePatternResolver的角色及其实现,以及PathMatchingResourcePatternResolver如何通过递归和AntPathMatcher加载多个资源。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Spring源码导读

 

目录

AntPathMatcher

ResourceLoader

DefaulResourceLoader

ResourcePatternResolver


 

资源加载

AbstractApplication 实现了ResourceLoader和ResourcePatternResolver。ResourceLoader是加载单个资源的。而时候我们的资源路径可能是以”classpath*“ (代表加载其他jar包资源)开头的,也有可能是带有通配符的,这两种是加载多个资源,这种是通过ResourcePatternResolver加载,ResourcePatternResolver的实现类其实是PathMatchingResourcePatternResolver,它通过递归并使用AntMathMatcher这个工具对模式和找到的路径进行匹配,然后将匹配到的各个资源的路径委托给其成员变量ResourceLoader resourceLoader = DefaultResourceLoader()进行加载。

 

 

AntPathMatcher

  Spring 在加载我们配置的文件模式符的时候,会将模式符按照 / 分割,然后与我们工程的路径进行匹配如
将 "/aa/bb/cc/dd/ee/ff/1.txt" 分割为 {aa, bb, cc, dd, ee, ff, 1.txt}

 ? 匹配一个字符
 * 匹配0个或多个字符
 ** 匹配 0 个或多个目录
 可以待正则匹配的变量 如 {path1:[a-z]+} 变量名为 path1 匹配 正则式[a-z]+
public class AntPathMatcherTest {

    public static void test1() {
        AntPathMatcher antPathMatcher = new AntPathMatcher();
        // 精准匹配
        boolean ret = antPathMatcher.match("/aa/bb/cc/dd/ee/ff/1.txt", "/aa/bb/cc/dd/ee/ff/1.txt");
        System.out.println(ret);

        // 使用 ? 匹配
        ret = antPathMatcher.match("/a?/bb/cc/dd/ee/ff/1.txt", "/aa/bb/cc/dd/ee/ff/1.txt");
        System.out.println(ret);

        // 使用 * 匹配
        ret = antPathMatcher.match("/*/bb/cc/dd/ee/ff/1.txt", "/aa/bb/cc/dd/ee/ff/1.txt");
        System.out.println(ret);

        // 使用 ** 匹配
        ret = antPathMatcher.match("/**/bb/**/dd/ee/ff/1.txt", "/aa/bb/cc/dd/ee/ff/1.txt");
        System.out.println(ret);

        // 使用 ** 匹配
        ret = antPathMatcher.match("/**/bb/**/dd/ee/ff/1.txt", "/aa/bb/cc/dd/ee/ff/1.txt");
        System.out.println(ret);

        // 模式提取
        Map<String, String> map = antPathMatcher.extractUriTemplateVariables("/aa/bb/{path1}/{path2}/ee/ff/1.txt"
                , "/aa/bb/cc/dd/ee/ff/1.txt");
        System.out.println(map);

        map = antPathMatcher.extractUriTemplateVariables("/aa/bb/{path1:\\w+}/{path2:\\d+}/ee/ff/1.txt"
                , "/aa/bb/cc/12/ee/ff/1.txt");
        System.out.println(map);
    }

    final static Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{[^/]+?\\}");
    public static void test2() {
        // path == aa, patter = "a?" => "a."
        // path == aa, patter = "a+" => "a.+"
        String pattern = "{path1:dd}";
        Matcher matcher = GLOB_PATTERN.matcher(pattern);

        StringBuilder patternBuilder = new StringBuilder();
        List<String> variableNames = new ArrayList<>();

        int end = 0;
        while (matcher.find()) {
//            System.out.println("在[" + matcher.start() + ", " + matcher.end()
//                    + ")的位置匹配到" + matcher.group())
            String match = matcher.group();
            patternBuilder.append(quote(pattern, end, matcher.start()));

            if ("?".equals(match)) {
                patternBuilder.append(".");
            }
            if ("*".equals(match)) {
                patternBuilder.append(".*");
            }
            if (match.startsWith("{") && match.endsWith("}")) {

                int index = match.indexOf(":");
                if (index < 0) {
                    variableNames.add(match.substring(1, match.length() - 1));
                    patternBuilder.append("(.*)");
                } else {
                    variableNames.add(match.substring(1, index));
                    patternBuilder.append("(");
                    patternBuilder.append(match.substring(index + 1, match.length() -1));
                    patternBuilder.append(")");
                }

            }
            end = matcher.end();
        }
        patternBuilder.append(quote(pattern, end, pattern.length()));

        String theRegPattern = patternBuilder.toString();
        System.out.println("使用正则替换了原来的路径统配符之后的结果为" + theRegPattern);

        Pattern reg = Pattern.compile(theRegPattern);

        String target = "dd";
        Matcher matcher1 = reg.matcher(target);
        if (matcher1.matches()) {
            System.out.println(pattern + " 匹配到了 " + target);
            for (int i = 0; i < matcher1.groupCount(); i++) {
                String name = variableNames.get(i);
                String value = matcher1.group(i + 1);
                System.out.println(String.format(("name = %s, value = %s"), name, value));
            }
        } else {
            System.out.println(pattern + " 无法匹配 " + target);
        }
    }

    public static void main(String[] args) {
        test2();
    }

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

 

ResourceLoader

DefaulResourceLoader

ResourceLoader的最终实现是DefaulResourceLoader:

org.springframework.core.io.DefaultResourceLoader#getResource的代码

 

public Resource getResource(String location) {
		Assert.notNull(location, "Location must not be null");

		for (ProtocolResolver protocolResolver : this.protocolResolvers) {
			Resource resource = protocolResolver.resolve(location, this);
			if (resource != null) {
				return resource;
			}
		}

		if (location.startsWith("/")) {
			return getResourceByPath(location);
		}
		else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
			return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
		}
		else {
			try {
				// Try to parse the location as a URL...
				URL url = new URL(location);
				return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
			}
			catch (MalformedURLException ex) {
				// No URL -> resolve as resource path.
				return getResourceByPath(location);
			}
		}
	}

org.springframework.core.io.DefaultResourceLoader#getClassLoader

public ClassLoader getClassLoader() {
   return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader());
}

org.springframework.util.ClassUtils#getDefaultClassLoader

	public static ClassLoader getDefaultClassLoader() {
		ClassLoader cl = null;
		try {
			cl = Thread.currentThread().getContextClassLoader();
		}
		catch (Throwable ex) {
			// Cannot access thread context ClassLoader - falling back...
		}
		if (cl == null) {
			// No thread context class loader -> use class loader of this class.
			cl = ClassUtils.class.getClassLoader();
			if (cl == null) {
				// getClassLoader() returning null indicates the bootstrap ClassLoader
				try {
					cl = ClassLoader.getSystemClassLoader();
				}
				catch (Throwable ex) {
					// Cannot access system ClassLoader - oh well, maybe the caller can live with null...
				}
			}
		}
		return cl;
	}

 

ResourcePatternResolver

其实现类是PathMatchingResourcePatternResolver,案例:

public class ResourcePatternResolverTest {
    public static void main(String[] args) throws IOException {
        String path = ResourceLoaderTest.class.getName().replace(".", "/");
        String dirs = path.substring(0, path.length() - ResourceLoaderTest.class.getSimpleName().length());
        // 意图去匹配 当前目录下 所有类名带 Resource 的 class文件
        String pattern = "classpath:" + dirs + "/*Resource*.class";
        // ResourcePatternResolver 可以通过模式匹配加载多个符合模式的文件, 这种匹配就是通过AntPathMatcher完成的,其单个文件加载是通过ResourceLoader完成的
        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(new DefaultResourceLoader());
        Resource[] resources = resourcePatternResolver.getResources(pattern);
        for (Resource resource : resources) {
            if (resource.exists()) {
                System.out.println(resource.getURL());
            }
        }
    }
}

org.springframework.core.io.support.PathMatchingResourcePatternResolver#getResources 源码

public Resource[] getResources(String locationPattern) throws IOException {
		Assert.notNull(locationPattern, "Location pattern must not be null");
		// 是否 "classpath*:" 开头的
		if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
			// 检测其是否带有 * ? { } 判断是否需要用antMathMatcher进行模式匹配
			if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
				// 用antMathMatcher进行模式匹配
				return findPathMatchingResources(locationPattern);
			}
			else {
				// 直接通过路径进行查找资源
				return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
			}
		}
		else {
			// Generally only look for a pattern after a prefix here,
			// and on Tomcat only after the "*/" separator for its "war:" protocol.
			int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 :
					locationPattern.indexOf(':') + 1);
			// 去掉前缀后再判断
			if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
				// 如果是模式匹配的使用模式匹配
				return findPathMatchingResources(locationPattern);
			}
			else {
				// 直接交给 resourceLoader进行单个资源加载
				return new Resource[] {getResourceLoader().getResource(locationPattern)};
			}
		}
	}

他有三个走向:

findPathMatchingResources 按照模式匹配进行解析,它是一个递归的过程

findAllClassPathResources 按照全路径解析

new Resource[] {getResourceLoader().getResource(locationPattern)} 单个资源解析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值