概述
上一篇写到以ClassPathXmlApplicationContext为例进入spring,从它的UML图中我们可以看到接口实现包含哪些内容!
接口内容涉及较多先从资源加载策略开始!
即ClassPathXmlApplicationContext实现的顶级接口: ResourceLoader接口,它的说明以及 ResourceLoader的各个实现类扩展后的资源获取方法进行说明!所有的资源加载器都会将用户对应路径上的资源加载成为Resource对象进行返回!
先讲资源加载
我们做项目会涉及到很多资源涉及文件、图片、流、网络链接等等,而资源操作需要类似打开、读取、关闭等操作步骤繁琐,常见的有以下这些形式:
- 网络形式的资源
- 二进制形式的资源
- 文件形式的资源
- 字节流形式的资源
根据面向对象设计的开闭原则、依赖倒置原则、单一职责原则,将多种类型的资源做成统一的抽象,并且由不同的接口负责不同的职能spring将资源的描述和加载加以区分,即Resource和ResourceLoader。
Resource:资源描述的策略接口
ResourceLoader:加载资源的策略接口
Spring为我们提供了以上两个接口来描述和加载资源,也提供了一些默认的描述和加载的实现类,ResourceLoader和Resource的继承体系图如下:
Resource常用的类有:
- ClassPathResource:通过 ClassPathResource 以类路径的方式进行访问;
- FileSystemResource:通过 FileSystemResource 以文件系统绝对路径的方式进行访问;
- ServletContextResource:通过 ServletContextResource
以相对于Web应用根目录的方式进行访问 - 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(二)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复