Spring源碼情操陶冶-PathMatchingResourcePatternResolver路徑資源匹配溶解器

本文簡單的分析下spring對某個目錄下的class資源是如何作到所有的加載java

PathMatchingResourcePatternResolver#getResources

PathMatchingResourcePatternResolverResourcePatternResolver的實現類,主要實現的方法爲getResources(String locationPattern),具體代碼以下,以classpath*:com/question/**/*.class爲例spring

public Resource[] getResources(String locationPattern) throws IOException {
        Assert.notNull(locationPattern, "Location pattern must not be null");
        //判斷查找路徑是否以classpath*:開頭
        if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
            //判斷是查找多個文件仍是單個,即判斷是否含有*或者?
            if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
                // a class path resource pattern 即還須要獲取根目錄
                return findPathMatchingResources(locationPattern);
            }
            else {
                // all class path resources with the given name。找尋classpath路徑下的根目錄全路徑,包含jar、zip包
                //好比classpath*:com/question/
                return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
            }
        }
        else {
            //通常此處針對classpath:開頭的資源加載
            int prefixEnd = locationPattern.indexOf(":") + 1;
            if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
                // a file pattern 加載某個目錄
                return findPathMatchingResources(locationPattern);
            }
            else {
                // a single resource with the given name優先加載classpath路徑下的項目對應資源,找不到才查找jar、zip資源
                return new Resource[] {getResourceLoader().getResource(locationPattern)};
            }
        }
    }

PathMatchingResourcePatternResolver#findPathMatchingResources()

protected方法,查找指定路徑下的全部資源,同時支持zip、jar中資源的查找app

protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
        //首先定位根目錄路徑,例如classpath*:com/question/
        String rootDirPath = determineRootDir(locationPattern);
        //默認爲**/*.class
        String subPattern = locationPattern.substring(rootDirPath.length());
        //遞歸函數的調用,此處會調用PathMatchingResourcePatternResolver#findAllClassPathResources方法加載根目錄,找尋classpath路徑下的根目錄全路徑,包含jar、zip包
        Resource[] rootDirResources = getResources(rootDirPath);
        
        Set<Resource> result = new LinkedHashSet<Resource>(16);
        for (Resource rootDirResource : rootDirResources) {
            //判斷是否含有協議爲bundle的資源,沒有則返回原值
            rootDirResource = resolveRootDirResource(rootDirResource);
            //vfs協議
            if (rootDirResource.getURL().getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
                result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirResource, subPattern, getPathMatcher()));
            }
            //jar協議、zip協議、wsjar協議、vfszip協議
            else if (isJarResource(rootDirResource)) {
                //從jar包中找尋相應的全部class文件
                result.addAll(doFindPathMatchingJarResources(rootDirResource, subPattern));
            }
            else {
                //加載非jar、zip包的項目資源
                result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
            }
        }
        if (logger.isDebugEnabled()) {
            logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result);
        }
        return result.toArray(new Resource[result.size()]);
    }

爲了理解得更清楚,咱們再抽取必要的代碼進行分析,好比PathMatchingResourcePatternResolver#findAllClassPathResources()PathMatchingResourcePatternResolver#doFindPathMatchingFileResources()函數

  • PathMatchingResourcePatternResolver#findAllClassPathResources
    經過classloader來加載資源目錄,代碼以下
protected Resource[] findAllClassPathResources(String location) throws IOException {
        String path = location;
        //例如com/question/
        if (path.startsWith("/")) {
            path = path.substring(1);
        }
        //真實查找方法
        Set<Resource> result = doFindAllClassPathResources(path);
        return result.toArray(new Resource[result.size()]);
    }

進而看PathMatchingResourcePatternResolver#doFindAllClassPathResources()url

protected Set<Resource> doFindAllClassPathResources(String path) throws IOException {
        Set<Resource> result = new LinkedHashSet<Resource>(16);
        ClassLoader cl = getClassLoader();
        //經過classloader來加載資源目錄,這裏也會去找尋classpath路徑下的jar包或者zip包
        Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path));
        while (resourceUrls.hasMoreElements()) {
            URL url = resourceUrls.nextElement();
            //對找到的路徑保存爲UrlResource對象放入set集合中
            result.add(convertClassLoaderURL(url));
        }
        if ("".equals(path)) {
            //加載jar協議的資源
            addAllClassLoaderJarRoots(cl, result);
        }
        return result;
    }

Note:通常而言找到的結果爲一個,也就是file協議的項目工程資源目錄,不建議查找的base-package含有jar包的資源目錄,好比org.springframeworkspa

  • PathMatchingResourcePatternResolver#doFindPathMatchingFileResources()
    查找指定目錄下的全部文件,這裏特指class文件
protected Set<Resource> doFindPathMatchingFileResources(Resource rootDirResource, String subPattern)
            throws IOException {

        File rootDir;
        try {
            //獲取絕對路徑對應的file
            rootDir = rootDirResource.getFile().getAbsoluteFile();
        }
        catch (IOException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Cannot search for matching files underneath " + rootDirResource +
                        " because it does not correspond to a directory in the file system", ex);
            }
            //異常則返回空的集合
            return Collections.emptySet();
        }
        return doFindMatchingFileSystemResources(rootDir, subPattern);
    }

進而看真實的查找方法doFindMatchingFileSystemResources(),代碼以下debug

protected Set<Resource> doFindMatchingFileSystemResources(File rootDir, String subPattern) throws IOException {
        if (logger.isDebugEnabled()) {
            logger.debug("Looking for matching resources in directory tree [" + rootDir.getPath() + "]");
        }
        //真實的調用方法
        Set<File> matchingFiles = retrieveMatchingFiles(rootDir, subPattern);
        Set<Resource> result = new LinkedHashSet<Resource>(matchingFiles.size());
        for (File file : matchingFiles) {
            //對查找到的資源包裝爲FileSystemResource對象
            result.add(new FileSystemResource(file));
        }
        return result;
    }

繼續觀察真實加載文件資源的方法retriveMatchingFiles(),代碼以下code

protected Set<File> retrieveMatchingFiles(File rootDir, String pattern) throws IOException {
        //根目錄不存在?返回空集合
        if (!rootDir.exists()) {
            // Silently skip non-existing directories.
            if (logger.isDebugEnabled()) {
                logger.debug("Skipping [" + rootDir.getAbsolutePath() + "] because it does not exist");
            }
            return Collections.emptySet();
        }
        //不是目錄?返回爲空
        if (!rootDir.isDirectory()) {
            // Complain louder if it exists but is no directory.
            if (logger.isWarnEnabled()) {
                logger.warn("Skipping [" + rootDir.getAbsolutePath() + "] because it does not denote a directory");
            }
            return Collections.emptySet();
        }
        //不可讀?返回爲空
        if (!rootDir.canRead()) {
            if (logger.isWarnEnabled()) {
                logger.warn("Cannot search for matching files underneath directory [" + rootDir.getAbsolutePath() +
                        "] because the application is not allowed to read the directory");
            }
            return Collections.emptySet();
        }
        //轉換根目錄全路徑爲標準的查找路徑
        String fullPattern = StringUtils.replace(rootDir.getAbsolutePath(), File.separator, "/");
        if (!pattern.startsWith("/")) {
            fullPattern += "/";
        }
        //查找類型爲.class文件
        fullPattern = fullPattern + StringUtils.replace(pattern, File.separator, "/");
        Set<File> result = new LinkedHashSet<File>(8);
        doRetrieveMatchingFiles(fullPattern, rootDir, result);
        return result;
    }

接着瞧doRetriveMathingFiles的重載方法,代碼以下對象

protected void doRetrieveMatchingFiles(String fullPattern, File dir, Set<File> result) throws IOException {
        if (logger.isDebugEnabled()) {
            logger.debug("Searching directory [" + dir.getAbsolutePath() +
                    "] for files matching pattern [" + fullPattern + "]");
        }
        //從根目錄開始羅列文件集合
        File[] dirContents = dir.listFiles();
        if (dirContents == null) {
            //查找到沒有了則直接返回
            if (logger.isWarnEnabled()) {
                logger.warn("Could not retrieve contents of directory [" + dir.getAbsolutePath() + "]");
            }
            return;
        }
        //遍歷
        for (File content : dirContents) {
            //獲取當前文件路徑
            String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");
            //查找到的子文件還是目錄且以根目錄爲開頭
            if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) {
                if (!content.canRead()) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Skipping subdirectory [" + dir.getAbsolutePath() +
                                "] because the application is not allowed to read the directory");
                    }
                }
                else {
                    //遞歸調用查找全部的文件
                    doRetrieveMatchingFiles(fullPattern, content, result);
                }
            }
            //查看當前文件路徑是否知足**/*.class格式,知足則添加
            if (getPathMatcher().match(fullPattern, currPath)) {
                result.add(content);
            }
        }
    }

小結

  1. classpath*:表示查找classpath路徑下的全部符合條件的資源,包含jar、zip等資源;classpath:表示優先在項目的資源目錄下查找,找不到纔去jar、zip等資源中查找遞歸

  2. 該類能夠幫助spring查找到符合ant-style格式的全部資源,因此富有借鑑意義。附:ant-style指的是相似*/?此類的匹配字符

相關文章
相關標籤/搜索