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


本文簡單的分析下spring對某個目錄下的class資源是如何做到全部的加載

PathMatchingResourcePatternResolver#getResources

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

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中資源的查找

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()

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.springframework

  • 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(),代碼如下

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(),代碼如下

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指的是類似*/?此類的匹配字符


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM