我是靠谱客的博主 可爱钥匙,最近开发中收集的这篇文章主要介绍spring源码-资源加载Resource和ResourceLoader(二),觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

上一篇写到以ClassPathXmlApplicationContext为例进入spring,从它的UML图中我们可以看到接口实现包含哪些内容!

在这里插入图片描述
接口内容涉及较多先从资源加载策略开始!
即ClassPathXmlApplicationContext实现的顶级接口: ResourceLoader接口,它的说明以及 ResourceLoader的各个实现类扩展后的资源获取方法进行说明!所有的资源加载器都会将用户对应路径上的资源加载成为Resource对象进行返回!

先讲资源加载

我们做项目会涉及到很多资源涉及文件、图片、流、网络链接等等,而资源操作需要类似打开、读取、关闭等操作步骤繁琐,常见的有以下这些形式:

  1. 网络形式的资源
  2. 二进制形式的资源
  3. 文件形式的资源
  4. 字节流形式的资源

根据面向对象设计的开闭原则、依赖倒置原则、单一职责原则,将多种类型的资源做成统一的抽象,并且由不同的接口负责不同的职能spring将资源的描述和加载加以区分,即Resource和ResourceLoader。

Resource:资源描述的策略接口
ResourceLoader:加载资源的策略接口

Spring为我们提供了以上两个接口来描述和加载资源,也提供了一些默认的描述和加载的实现类,ResourceLoader和Resource的继承体系图如下:
在这里插入图片描述
在这里插入图片描述

Resource常用的类有:

  1. ClassPathResource:通过 ClassPathResource 以类路径的方式进行访问;
  2. FileSystemResource:通过 FileSystemResource 以文件系统绝对路径的方式进行访问;
  3. ServletContextResource:通过 ServletContextResource
    以相对于Web应用根目录的方式进行访问
  4. UrlResource:通过java.net.URL来访问资源,当然它也支持File格式,如“file:”、“http:”。

ResourceLoader常用的类有:

public interface ResourceLoader {
	//从类路径加载的伪URL前缀:" classpath:"
	String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
	//从路径获取资源
	Resource getResource(String location);
	//获取类加载器
	@Nullable
	ClassLoader getClassLoader();
}

1. DefaultResourceLoader: 默认实现类,需要自定义解析器的话可以实现ProtocolResolver接口,默认资源加载器会遍历实现了ProtocolResolver接口的解析器对资源进行解析返回Resource对象!DefaultResourceLoader提供了addProtocolResolver方法用来添加用户自定义的解析器。

//DefaultResourceLoader的addProtocolResolver方法,ProtocolResolver(附件解析)接口的内容由用户实现!
	public void addProtocolResolver(ProtocolResolver resolver) {
		Assert.notNull(resolver, "ProtocolResolver must not be null");
		this.protocolResolvers.add(resolver);
	}
//ProtocolResolver接口类,提供resolve方法接收用户路径和ResourceLoader 对象,通过加载器对指定路径资源进行加载,包装为Resource 对象返回!
@FunctionalInterface
public interface ProtocolResolver {
	@Nullable
	Resource resolve(String location, ResourceLoader resourceLoader);
}

1.2 ClassRelativeResourceLoader: 扩展了DefaultResourceLoader的功能,它可以根据给定的class 所在包或者所在包的子包下加载资源。

//ClassRelativeResourceLoader的获取Resource 的方法,需要传入class 所在包的路径
	protected Resource getResourceByPath(String path) {
		return new ClassRelativeContextResource(path, this.clazz);
	}

2. ResourcePatternResolver:资源匹配解析的统一抽象接口!默认的资源加载器getResource方法只能返回一个Resource对象,而ResourcePatternResolver 支持根据指定的资源路径匹配模式每次返回多个 Resource 实例!同时新增了一种新的协议前缀 “classpath*:”,该协议前缀由其子类负责实现。

public interface ResourcePatternResolver extends ResourceLoader {
	//类路径匹配的前缀
	String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
	//获取资源的抽象方法,注意返回的是数组,即规定将类路径下的资源都加在为Resource对象
	Resource[] getResources(String locationPattern) throws IOException;
}

3. PathMatchingResourcePatternResolver :为 ResourcePatternResolver 最常用的实现类,它除了支持 ResourceLoader 以及 ResourcePatternResolver 新增的 “classpath*:” 前缀外,还支持 Ant 风格的路径匹配模式(类似于 “**/*.xml”)。具体源码解析见其他大佬的行文,本人的源码截图后续解析会跟上:https://www.cnblogs.com/question-sky/p/6959493.html

//路径匹配资源的样例解析类
public class PathMatchingResourcePatternResolver implements ResourcePatternResolver {
	//Equinox项目是Eclipse开源组织提供的OSGi框架的实现这个方法提供了具体解析
	private static Method equinoxResolveMethod;
	//资源加载器
	private final ResourceLoader resourceLoader;
	//Ant风格的路径匹配器
	private PathMatcher pathMatcher = new AntPathMatcher();
	//静态代码块:从固定路径加载解析OSGi 的方法,eclipse相关,非关注重点
	static {
		try {
			// Detect Equinox OSGi (e.g. on WebSphere 6.1)
			Class<?> fileLocatorClass = ClassUtils.forName("org.eclipse.core.runtime.FileLocator",
					PathMatchingResourcePatternResolver.class.getClassLoader());
			equinoxResolveMethod = fileLocatorClass.getMethod("resolve", URL.class);
			logger.trace("Found Equinox FileLocator for OSGi bundle URL resolution");
		}
		catch (Throwable ex) {
			equinoxResolveMethod = null;
		}
	}
	//默认构造器,初始化new resourceLoader 资源加载对象!默认使用DefaultResourceLoader进行加载
	// resourceLoader字段,pathMatcher字段的getter 和 setter 方法省略。。。
	
	
	//通过classloader来加载资源目录,返回资源数组
	protected Resource[] findAllClassPathResources(String location) throws IOException {
		String path = location;
		if (path.startsWith("/")) {
			path = path.substring(1);
		}
		Set<Resource> result = doFindAllClassPathResources(path);
		if (logger.isTraceEnabled()) {
			logger.trace("Resolved classpath location [" + location + "] to resources " + result);
		}
		return result.toArray(new Resource[0]);
	}


	//通过 ClassLoader 查找具有给定路径的所有类位置资源。 由findAllClassPathResources(String)调用。
	protected Set<Resource> doFindAllClassPathResources(String path) throws IOException {
		Set<Resource> result = new LinkedHashSet<>(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 (!StringUtils.hasLength(path)) {
			// The above result is likely to be incomplete, i.e. only containing file system references.
			// We need to have pointers to each of the jar files on the classpath as well...
			//加载jar协议的资源
			addAllClassLoaderJarRoots(cl, result);
		}
		return result;
	}

	//从url转换资源
	protected Resource convertClassLoaderURL(URL url) {
		return new UrlResource(url);
	}


	//加载jar协议的资源,由doFindAllClassPathResources调用
	protected void addAllClassLoaderJarRoots(@Nullable ClassLoader classLoader, Set<Resource> result) {
		if (classLoader instanceof URLClassLoader) {
			try {
				for (URL url : ((URLClassLoader) classLoader).getURLs()) {
					try {
						UrlResource jarResource = (ResourceUtils.URL_PROTOCOL_JAR.equals(url.getProtocol()) ?
								new UrlResource(url) :
								new UrlResource(ResourceUtils.JAR_URL_PREFIX + url + 	
										    ResourceUtils.JAR_URL_SEPARATOR));
						if (jarResource.exists()) {
							result.add(jarResource);
						}
					}
					catch (MalformedURLException ex) {
						if (logger.isDebugEnabled()) {
							logger.debug("Cannot search for matching files underneath [" + url +
									"] because it cannot be converted to a valid 'jar:' URL: " + ex.getMessage());
						}
					}
				}
			}
			catch (Exception ex) {
				if (logger.isDebugEnabled()) {
					logger.debug("Cannot introspect jar files since ClassLoader [" + classLoader +
							"] does not support 'getURLs()': " + ex);
				}
			}
		}

		if (classLoader == ClassLoader.getSystemClassLoader()) {
			// "java.class.path" manifest evaluation...
			addClassPathManifestEntries(result);
		}

		if (classLoader != null) {
			try {
				// Hierarchy traversal...
				addAllClassLoaderJarRoots(classLoader.getParent(), result);
			}
			catch (Exception ex) {
				if (logger.isDebugEnabled()) {
					logger.debug("Cannot introspect jar files in parent ClassLoader since [" + classLoader +
							"] does not support 'getParent()': " + ex);
				}
			}
		}
	}

	/**
	 * 通过 Ant 风格的 PathMatcher 查找与给定位置模式匹配的所有资源。 支持 jar 文件和 zip 文件以及文件系统中的资源。
	 * Find all resources that match the given location pattern via the
	 * Ant-style PathMatcher. Supports resources in jar files and zip files
	 * and in the file system.
	 * @param locationPattern the location pattern to match
	 * @return the result as Resource array
	 * @throws IOException in case of I/O errors
	 * @see #doFindPathMatchingJarResources
	 * @see #doFindPathMatchingFileResources
	 * @see org.springframework.util.PathMatcher
	 */
	protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
		//首先定位根目录路径,例如,将为模式“/WEB-INF/spring/*.xml”  返回 “/WEB-INF/”
		String rootDirPath = determineRootDir(locationPattern);
		//截取根路径后的子路径
		String subPattern = locationPattern.substring(rootDirPath.length());
		//递归根路径,getResources方法会调用 #{findPathMatchingResources} 当前方法递归遍历
		Resource[] rootDirResources = getResources(rootDirPath);
		Set<Resource> result = new LinkedHashSet<>(16);
		for (Resource rootDirResource : rootDirResources) {
			rootDirResource = resolveRootDirResource(rootDirResource);
			URL rootDirUrl = rootDirResource.getURL();
			//通过本类的静态代码块让当前类加载器去加载eclipse相关的FileLocator类通过调用该类的resolve方法去加载资源
			if (equinoxResolveMethod != null && rootDirUrl.getProtocol().startsWith("bundle")) {
				URL resolvedUrl = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirUrl);
				if (resolvedUrl != null) {
					rootDirUrl = resolvedUrl;
				}
				rootDirResource = new UrlResource(rootDirUrl);
			}
			//加载 vfs虚拟文件
			if (rootDirUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
				result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl, subPattern, 
				getPathMatcher()));
			}
			//判断是否是 "jar", "war, ""zip", "vfszip" or "wsjar".如果是则调用doFindPathMatchingJarResources 方法加载
			else if (ResourceUtils.isJarURL(rootDirUrl) || isJarResource(rootDirResource)) {
				result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirUrl, subPattern));
			}
			else {
				//根据路径匹配超找到文件类型的资源进行加载!文件会被加载成FileSystemResource类型
				result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
			}
		}
		if (logger.isTraceEnabled()) {
			logger.trace("Resolved location pattern [" + locationPattern + "] to resources " + result);
		}
		return result.toArray(new Resource[0]);
	}

	/**
	* 当接收字符串为"/WEB-INF/*.xml"时,返回"/WEB-INF/"
	*/
	protected String determineRootDir(String location) {
		int prefixEnd = location.indexOf(':') + 1;
		int rootDirEnd = location.length();
		while (rootDirEnd > prefixEnd && getPathMatcher().isPattern(location.substring(prefixEnd, rootDirEnd))) {
			rootDirEnd = location.lastIndexOf('/', rootDirEnd - 2) + 1;
		}
		if (rootDirEnd == 0) {
			rootDirEnd = prefixEnd;
		}
		return location.substring(0, rootDirEnd);
	}


	/**
	* ,用户可以重写此方法自定义资源!由findPathMatchingResources方法调用,解析指定资源,返回资源对象!
	*/
	protected Resource resolveRootDirResource(Resource original) throws IOException {
		return original;
	}
	/**
	*判断是否为jar类型的资源
	*/
	protected boolean isJarResource(Resource resource) throws IOException {
		return false;
	}


/**
*	从jar包中找寻相应的所有class文件,由findPathMatchingResources方法调用
*/
protected Set<Resource> doFindPathMatchingJarResources(Resource rootDirResource, URL rootDirURL, String subPattern)
			throws IOException {

		URLConnection con = rootDirURL.openConnection();
		JarFile jarFile;
		String jarFileUrl;
		String rootEntryPath;
		boolean closeJarFile;

		if (con instanceof JarURLConnection) {
			// Should usually be the case for traditional JAR files.
			JarURLConnection jarCon = (JarURLConnection) con;
			ResourceUtils.useCachesIfNecessary(jarCon);
			jarFile = jarCon.getJarFile();
			jarFileUrl = jarCon.getJarFileURL().toExternalForm();
			JarEntry jarEntry = jarCon.getJarEntry();
			rootEntryPath = (jarEntry != null ? jarEntry.getName() : "");
			closeJarFile = !jarCon.getUseCaches();
		}
		else {
			// No JarURLConnection -> need to resort to URL file parsing.
			// We'll assume URLs of the format "jar:path!/entry", with the protocol
			// being arbitrary as long as following the entry format.
			// We'll also handle paths with and without leading "file:" prefix.
			String urlFile = rootDirURL.getFile();
			try {
				int separatorIndex = urlFile.indexOf(ResourceUtils.WAR_URL_SEPARATOR);
				if (separatorIndex == -1) {
					separatorIndex = urlFile.indexOf(ResourceUtils.JAR_URL_SEPARATOR);
				}
				if (separatorIndex != -1) {
					jarFileUrl = urlFile.substring(0, separatorIndex);
					rootEntryPath = urlFile.substring(separatorIndex + 2);  // both separators are 2 chars
					jarFile = getJarFile(jarFileUrl);
				}
				else {
					jarFile = new JarFile(urlFile);
					jarFileUrl = urlFile;
					rootEntryPath = "";
				}
				closeJarFile = true;
			}
			catch (ZipException ex) {
				if (logger.isDebugEnabled()) {
					logger.debug("Skipping invalid jar classpath entry [" + urlFile + "]");
				}
				return Collections.emptySet();
			}
		}

		try {
			if (logger.isTraceEnabled()) {
				logger.trace("Looking for matching resources in jar file [" + jarFileUrl + "]");
			}
			if (StringUtils.hasLength(rootEntryPath) && !rootEntryPath.endsWith("/")) {
				// Root entry path must end with slash to allow for proper matching.
				// The Sun JRE does not return a slash here, but BEA JRockit does.
				rootEntryPath = rootEntryPath + "/";
			}
			Set<Resource> result = new LinkedHashSet<>(8);
			for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements();) {
				JarEntry entry = entries.nextElement();
				String entryPath = entry.getName();
				if (entryPath.startsWith(rootEntryPath)) {
					String relativePath = entryPath.substring(rootEntryPath.length());
					if (getPathMatcher().match(subPattern, relativePath)) {
						result.add(rootDirResource.createRelative(relativePath));
					}
				}
			}
			return result;
		}
		finally {
			if (closeJarFile) {
				jarFile.close();
			}
		}
	}

	/**
	 * Resolve the given jar file URL into a JarFile object.
	 * 解析给定的jar路径返回对应的jarFile对象
	 */
	protected JarFile getJarFile(String jarFileUrl) throws IOException {
		if (jarFileUrl.startsWith(ResourceUtils.FILE_URL_PREFIX)) {
			try {
				return new JarFile(ResourceUtils.toURI(jarFileUrl).getSchemeSpecificPart());
			}
			catch (URISyntaxException ex) {
				// Fallback for URLs that are not valid URIs (should hardly ever happen).
				return new JarFile(jarFileUrl.substring(ResourceUtils.FILE_URL_PREFIX.length()));
			}
		}
		else {
			return new JarFile(jarFileUrl);
		}
	}

	/**
	 * 加载非jar、zip包的项目资源
	 * Find all resources in the file system that match the given location pattern
	 * via the Ant-style PathMatcher.
	 * @param rootDirResource the root directory as Resource
	 * @param subPattern the sub pattern to match (below the root directory)
	 * @return a mutable Set of matching Resource instances
	 * @throws IOException in case of I/O errors
	 * @see #retrieveMatchingFiles
	 * @see org.springframework.util.PathMatcher
	 */
	protected Set<Resource> doFindPathMatchingFileResources(Resource rootDirResource, String subPattern)
			throws IOException {

		File rootDir;
		try {
			//获取绝对路径对应的file
			rootDir = rootDirResource.getFile().getAbsoluteFile();
		}
		catch (FileNotFoundException ex) {
			if (logger.isDebugEnabled()) {
				logger.debug("Cannot search for matching files underneath " + rootDirResource +
						" in the file system: " + ex.getMessage());
			}
			return Collections.emptySet();
		}
		catch (Exception ex) {
			if (logger.isInfoEnabled()) {
				logger.info("Failed to resolve " + rootDirResource + " in the file system: " + ex);
			}
			return Collections.emptySet();
		}
		//根据绝对路径进行解析
		return doFindMatchingFileSystemResources(rootDir, subPattern);
	}


	/**
	 * 根据文件和路径匹配资源集合,将查找到的资源包装为FileSystemResource对象返回
	 * Find all resources in the file system that match the given location pattern
	 * via the Ant-style PathMatcher.
	 * @param rootDir the root directory in the file system
	 * @param subPattern the sub pattern to match (below the root directory)
	 * @return a mutable Set of matching Resource instances
	 * @throws IOException in case of I/O errors
	 * @see #retrieveMatchingFiles
	 * @see org.springframework.util.PathMatcher
	 */
	protected Set<Resource> doFindMatchingFileSystemResources(File rootDir, String subPattern) throws IOException {
		if (logger.isTraceEnabled()) {
			logger.trace("Looking for matching resources in directory tree [" + rootDir.getPath() + "]");
		}
		//检索文件匹配,真实的调用方法
		Set<File> matchingFiles = retrieveMatchingFiles(rootDir, subPattern);
		Set<Resource> result = new LinkedHashSet<>(matchingFiles.size());
		for (File file : matchingFiles) {
			//对查找到的资源包装为FileSystemResource对象
			result.add(new FileSystemResource(file));
		}
		return result;
	}


	/**
	 * 加载见资源的方法,返回文件集合
	 * Retrieve files that match the given path pattern,
	 * checking the given directory and its subdirectories.
	 * @param rootDir the directory to start from
	 * @param pattern the pattern to match against,
	 * relative to the root directory
	 * @return a mutable Set of matching Resource instances
	 * @throws IOException if directory contents could not be retrieved
	 */
	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.isInfoEnabled()) {
				logger.info("Skipping [" + rootDir.getAbsolutePath() + "] because it does not denote a directory");
			}
			return Collections.emptySet();
		}
		//不可读?返回为空
		if (!rootDir.canRead()) {
			if (logger.isInfoEnabled()) {
				logger.info("Skipping 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 += "/";
		}
		fullPattern = fullPattern + StringUtils.replace(pattern, File.separator, "/");
		Set<File> result = new LinkedHashSet<>(8);
		//查找类型为.class文件
		doRetrieveMatchingFiles(fullPattern, rootDir, result);
		return result;
	}

	/**
	 * 递归调用查找所有的文件,将查找到的文件添加到传入的result集合中
	 * Recursively retrieve files that match the given pattern,
	 * adding them to the given result list.
	 * @param fullPattern the pattern to match against,
	 * with prepended root directory path
	 * @param dir the current directory
	 * @param result the Set of matching File instances to add to
	 * @throws IOException if directory contents could not be retrieved
	 */
	protected void doRetrieveMatchingFiles(String fullPattern, File dir, Set<File> result) throws IOException {
		if (logger.isTraceEnabled()) {
			logger.trace("Searching directory [" + dir.getAbsolutePath() +
					"] for files matching pattern [" + fullPattern + "]");
		}
		for (File content : listDirectory(dir)) {
			//获取匹配当前系统描述符的文件绝对路径
			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);
			}
		}
	}

	/**
	 * 给定File对象,返回文件路径的文件列表
	 * Determine a sorted list of files in the given directory.
	 * @param dir the directory to introspect
	 * @return the sorted list of files (by default in alphabetical order)
	 * @since 5.1
	 * @see File#listFiles()
	 */
	protected File[] listDirectory(File dir) {
		File[] files = dir.listFiles();
		if (files == null) {
			if (logger.isInfoEnabled()) {
				logger.info("Could not retrieve contents of directory [" + dir.getAbsolutePath() + "]");
			}
			return new File[0];
		}
		Arrays.sort(files, Comparator.comparing(File::getName));
		return files;
	}

}

ResourceLoader 小结
ResourceLoader 的实现类中,实现的各个getResource方法会将资源包装成不同的Resource接口实现对象进行返回,包括上面提到的常见的4类,ClassPathResource,FileSystemResource,ServletContextResource,UrlResource,以及Resource接口UML图中继承体系中的各个实现!

最后

以上就是可爱钥匙为你收集整理的spring源码-资源加载Resource和ResourceLoader(二)的全部内容,希望文章能够帮你解决spring源码-资源加载Resource和ResourceLoader(二)所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(43)

评论列表共有 0 条评论

立即
投稿
返回
顶部