本文簡單的分析下spring對某個目錄下的class資源是如何作到所有的加載java
PathMatchingResourcePatternResolver
是ResourcePatternResolver
的實現類,主要實現的方法爲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)}; } } }
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()
函數
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.springframework
spa
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); } } }
classpath*:
表示查找classpath路徑下的全部符合條件的資源,包含jar、zip等資源;classpath:
表示優先在項目的資源目錄下查找,找不到纔去jar、zip等資源中查找遞歸該類能夠幫助spring查找到符合ant-style格式的全部資源,因此富有借鑑意義。附:ant-style指的是相似
*/?
此類的匹配字符