美文网首页
spring 路径查找代码解析 Day17 2018-12-05

spring 路径查找代码解析 Day17 2018-12-05

作者: Ernest_Chou | 来源:发表于2018-12-05 22:08 被阅读0次

2. maven项目中的classpath

2.1 maven项目中对应的classpath

maven项目分为src/main/java目录、src/main/resources目录,src/test/java目录,/peis-src/test/resources目录:

maven目录 编译文件目录
  • 其中src/main/java目录、src/main/resources目录对应到项目的target\classes目录,如果在src/main目录调用classpath,则class的根目录为target\classes
  • src/test/java目录,src/test/resources目录对应到test-classes目录,如果在src/test/java目录调用classpath,则class的根目录为target\test-classes

2.2 classpath*:classpath:比对结果

classpath:在整个目录下找配置确定的一个,且是第一个(资源使用通配符除外),使用资源使用通配符只在根目录查找

classpath*:只匹配根路径(包括jar包根路径,但不包括jar包测试路径test-resource),没有找子文件夹。

2.3 结果对照表

  • 下表src/main/resourcesresource代替
  • 下表src/test/resourcestest-resource代替
  • 测试代码位置位于src/test/java目录
location 查找内容
classpath:lyz.txt test-resource/lyz.txt:(测试代码位置位于src/test/java目录,则首要查找为test-resource目录,若无则去resource目录查找;再无则去jar目录查找)该路径查找的是整个目录(包括jar目录)中匹配到的第一文件
classpath:l*.txt test-resource/lyz.txt:只匹配test-resource根目录,没有找子文件夹与resource目录及jar包
classpath*:lyz.txt test-resource/lyz.txtresource/lyz.txtjar路径/resource/lyz.txt只匹配根路径(不包括jar包测试路径test-resource),没有找子文件夹
classpath*:l*.txt test-resource/lyz.txtresource/lyz.txtjar路径/resource/lyz.txt只匹配根路径(包括jar包根路径,但不包括jar包测试路径test-resource),没有找子文件夹
classpath*:/*/lyz.txt /resource/txt/lyz.txtjar路径/resource/txt/lyz.txt匹配根路径下子文件夹中的文件,(包括jar包根路径,但不包括jar包测试路径test-resource

2.4 Junit测试代码

@Test
    public void testPath() throws Exception {
        String location="classpath*:/*/lyz.txt";
        ResourcePatternResolver  resourceLoader = new PathMatchingResourcePatternResolver();
        Resource[] source = resourceLoader.getResources(location);
        System.out.println("source.size: "+source.length);
        for (int i = 0; i < source.length; i++) {
            Resource resource = source[i];
            System.out.println(resource);
        }
    }

3. spring 查找代码解析

3.1 原理(核心逻辑)

这是代码中的主类 PathMatchingResourcePatternResolver 类,位于包 org.springframework.core.io.support 下,该类中的 getResources 函数是逻辑的核心,如下:

@Override
    public Resource[] getResources(String locationPattern) throws IOException {
        Assert.notNull(locationPattern, "Location pattern must not be null");
        if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
            // a class path resource (multiple resources for same name possible)
            if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
                
            // case 1:如果以classpath*开头且包含?或者* ,例如查找: classpath*: applicationContext-*.xml
                return findPathMatchingResources(locationPattern);
            }
            else {
                
            // case 2: 不包含?或者*,直接全名查找,例如查找: classpath*: applicationContext-test.xml
                return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
            }
        }
        else {
            // 以 classpath:开头
            int prefixEnd = locationPattern.indexOf(":") + 1;
            if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
                // case 3: 如果不是以classpath*开头且包含?或者* ,例如查找: classpath: applicationContext-*.xml
                return findPathMatchingResources(locationPattern);
            }
            else {
                // case 4: 如果不是以classpath*开头且不包含?或者* ,例如查找: classpath: applicationContext-test.xml 
                return new Resource[] {getResourceLoader().getResource(locationPattern)};
            }
        }
    }

3.2 如果以classpath*开头且包含?或者*

例如查找: classpath*: applicationContext-*.xml ,使用findPathMatchingResources函数,看下该函数:

protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
        //函数determineRootDir函数是拿到能够确定的目录,如 classpath*:/aaa/bbb/applicationContext-*.xml 则返回classpath*:/aaa/bbb/
        //classpath*:/aaa/*/applicationContext-*.xml,则返回 classpath*:/aaa/ 
        String rootDirPath = determineRootDir(locationPattern);
        // 获取字符串locationPattern中后面不确定的内容
        String subPattern = locationPattern.substring(rootDirPath.length());
        // 递归加载已经确定的内容
        Resource[] rootDirResources = getResources(rootDirPath);
        Set<Resource> result = new LinkedHashSet<Resource>(16);
        for (Resource rootDirResource : rootDirResources) {
            rootDirResource = resolveRootDirResource(rootDirResource);
            URL rootDirURL = rootDirResource.getURL();
            if (equinoxResolveMethod != null) {
                if (rootDirURL.getProtocol().startsWith("bundle")) {
                    rootDirURL = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirURL);
                    rootDirResource = new UrlResource(rootDirURL);
                }
            }
            if (rootDirURL.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
                result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirURL, subPattern, getPathMatcher()));
            }
            else if (ResourceUtils.isJarURL(rootDirURL) || isJarResource(rootDirResource)) {
                result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirURL, subPattern));
            }
            else {
                result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
            }
        }
        if (logger.isDebugEnabled()) {
            logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result);
        }
        return result.toArray(new Resource[result.size()]);
    }

总体来说:该函数把locationPattern拆分成两部分:rootDirPathsubPatternrootDirPath是根目录路径,subPattern是子目录路径匹配规则字符串。遍历根目录下的所有子目录、并得到所有的子目录在doFindPathMatchingFileResources(rootDirResource, subPattern)方法中,再根据子目录逐个逐个去匹配subPattern

3.3如果以classpath*开头且不包含?或者*,直接全名查找

例如查找: classpath*: applicationContext-test.xml,使用findAllClassPathResources函数,看下该函数:

protected Resource[] findAllClassPathResources(String location) throws IOException {
        String path = location;
        if (path.startsWith("/")) {
            path = path.substring(1);
        }
        Set<Resource> result = doFindAllClassPathResources(path);
        if (logger.isDebugEnabled()) {
            logger.debug("Resolved classpath location [" + location + "] to resources " + result);
        }
        return result.toArray(new Resource[result.size()]);
    }

其实核心在doFindAllClassPathResources,该函数扫描该路径下的所有资料,其函数体如下:

protected Set<Resource> doFindAllClassPathResources(String path) throws IOException {
        Set<Resource> result = new LinkedHashSet<Resource>(16);
        ClassLoader cl = getClassLoader();
        Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path));
        while (resourceUrls.hasMoreElements()) {
            URL url = resourceUrls.nextElement();
            result.add(convertClassLoaderURL(url));
        }
        if ("".equals(path)) {
             // 如果路径为空的话,就找到所有jar包,加到result中(该函数不再深入)
            addAllClassLoaderJarRoots(cl, result);
        }
        return result;
    }

在该函数中重要:Enumeration resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path)); 即如果当前类加载器cl不为空的话,就调用cl.getResources(path)cl为空的话就使用系统类加载器去加载,事实上cl.getResources(path)内容使用的即是双亲委派模型(当前类加载器加载资源,如果存在父加载器,则用父加载器进行加载)。

3.4 如果不是以classpath*开头且包含?或者*

例如查找: classpath: applicationContext-*.xml,使用函数findPathMatchingResources,类似3.2情况。

3.5 如果不是以classpath*开头且不包含?或者*

例如查找: classpath: applicationContext-test.xml,直接getResourceLoader().getResource(locationPattern),即直接使用当然的资源加载器去加载,这里默认使用的是DefaultResourceLoader()

相关文章

网友评论

      本文标题:spring 路径查找代码解析 Day17 2018-12-05

      本文链接:https://www.haomeiwen.com/subject/sjuicqtx.html