zoukankan      html  css  js  c++  java
  • 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指的是类似*/?此类的匹配字符

  • 相关阅读:
    【JVM学习笔记】打破双亲委托机制的例子
    《牛客网》——华为机试在线训练——汽水瓶
    《牛客网》——华为机试在线训练(4-6)
    《牛客网》——华为机试在线训练(1-3)
    《数据结构:邓俊辉版》——二叉树层次遍历
    《数据结构:邓俊辉版》——非递归后序遍历
    《数据结构:邓俊辉版》——非递归中序遍历
    《数据结构:邓俊辉版》——二叉树的非递归先序遍历
    《数据结构:邓俊辉版》——交换排序
    《数据结构:邓俊辉版》——插入排序
  • 原文地址:https://www.cnblogs.com/question-sky/p/6959493.html
Copyright © 2011-2022 走看看