Spring资源加载

1 资源接口,继承关系,类简介

1.1 资源接口

Resource接口为应用提供了底层资源访问的能力。该接口的主要作用是描述实际的资源。

public interface Resource extends InputStreamSource {
	//资源是否存在
	boolean exists();
	//资源是否可读
	default boolean isReadable() {return true;}
	//资源是否被打开
	default boolean isOpen() {return false;}
	//确定此资源是否代表文件系统中的文件
	default boolean isFile() {return false;}
	//返回资源的URL句柄
	URL getURL() throws IOException;
	//返回资源的URI句柄
	URI getURI() throws IOException;
	//返回资源的文件对象
	File getFile() throws IOException;
	//根据相对路径创建资源
	Resource createRelative(String relativePath) throws IOException;
	//资源名称
	String getFilename();
	//资源描述
	String getDescription();
}

继承父类接口方法InputStream getInputStream() throw IOError:返回资源对应的输入流

1.2 继承关系


1.3 类简介

ByteArrayResource:用于从任何给定的字节数组中加载内容,特别适用于从本地内容创建字节数组。因为isOpen()返回false。所有ByuteArrayResource可以多次使用。当然该资源也可以通过代码直接创建。

InputStreamResource:java.io.InputStream字节流,对于“getInputStream ”操作将直接返回该字节流,因此只能读取一次该字节流,即“isOpen”永远返回true

FileSystemResource:java.io.File资源,对于“getInputStream ”操作将返回底层文件的字节流,“isOpen”将永远返回false,从而表示可多次读取底层文件的字节流

ClassPathResource:类路径下的资源,资源以相对类路径的方式表示。该资源可以存放在如下的位置:1.类路径下的文件系统2.类路径下的jar中。

isOpen返回false,表示可以多次读取。ClassPathResource替代了Class类和ClassLoader类的getReesource(String name)和getResourceAsStream(String name)加载类路径资源的方式。提供一致的访问方式。构造函数如下:

public ClassPathResource(String path):使用默认的ClassLoader加载资源

ublic ClassPathResource(String path,ClassLoader classLoader):采用自定义的ClassLoader加载资源

ublic ClassPathResource(String path,Class<?> class):使用指定类加载器加载资源,加载的是相对于当前类的路径的资源

如果加载的是jar包中的资源可以采用getURL()获得资源路径,因为资源不在文件系统中所以无法使用getFile

WritableResource:可写资源接口,获取资源后,可以对该资源进行写操作。可以通过编程的方式动态的改变资源

FileSystemResource:文件系统资源

ServletContextResource:为web容器上下文中的资源服务。负责从相对于web根目录的路径下加载资源。支持以流和URL的方式访问。在war解压的情况下可以通过File的方式访问,该类也可以从jar中加载资源

UrlResource:是对java.net.URL的封装,可以访问任何以URL表示的资源。eg文件系统资源,FTP资源,HTTP资源。

PathResource:该类封装了java.net.URL,java.nio.file.path,文件系统资源,也就说凡是可以采用UrlResource和FileSystemResource表示的资源,也可以采用该类表示

2 资源地址标识

前缀示例说明备注
classpath:classpath:aa/bb/cc/bean.xml类路径下的资源,资源可以在文件系统,也可以在jar/zip档案中classpath: 和classpath:/是等价的
file:file:aa/bb/cc/bean.xml表示文件系统中资源,使用UrlResource表示 
http://http://http://mp.blog.csdn.net/bean.xml使用UrlResource从网络上的资源 
ftp://ftp://http://mp.blog.csdn.net/bean.xml使用UrlResource从FTP上的资源 
没有a/bb/cc/bean.xml根据ApplicationContext的具体实现类采用对应类型的Resource如果是web应用,采用ServletContextResource,否则采用calsspath:a/bb/cc/bean.xml
    

3 地址通配符

通配符示例说明备注
a/bb/c?/bean?.xml匹配单个字符 
*a/bb/*/bean*.xml匹配一个或者多个字符 

**

a/bb/**/bean**.xml匹配一个或者多个路径bean**.xml中**按照*处理
    

?和*是针对于字符匹配而**是针对于路径匹配

4 资源加载接口,继承关系以及类说明

4.1 资源加载接口

ResourceLoader接口用于实现不同的Resource加载策略,即将不同Resource实例的创建交给ResourceLoader,该接口只支持以classpath:不支持classpath*:如果想要支持后者的接口是ResourcePatternResolver

public interface ResourceLoader {

	/** Pseudo URL prefix for loading from the class path: "classpath:" */
	String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;


	/**
	 * 按照给定的路径创建资源,支持如下几种模式
	 * 1.以file前缀描述的资源 eg:file:/work/bean.xml
	 * 2.以classpath前缀描述的资源 eg:classpath:/aa/bb/bean.xml
	 * 3.相对路径描述的资源 /WEB-INF/bean.xml<p>
	 * 返回的资源并不一定存在需要调用Resource#exists判断
	Resource getResource(String location);

	/**
	 * 获得ResourceLoader的类加载器,在创建ClassPathResource时被传入
	 * 在某些需要使用加载器的应用中可以直接通过该方法获得而不是采用thread context ClassLoader
	 * 在某些情况下两个ClassLoader并不一样
	 */
	@Nullable
	ClassLoader getClassLoader();

}
public interface ResourcePatternResolver extends ResourceLoader {
	String CLASSPATH_ALL_URL_PREFIX = "classpath*:";

	/**
	 * 根据给定路径,获取该路径下的所有的Resource
	 */
	Resource[] getResources(String locationPattern) throws IOException;

}

4.2 继承关系


4.2 类说明

4.2.1 DefaultResourceLoader

是ResourceLoader提供的默认实现,具体流程如下

1.判断liocation是否定义了协议:file,zip,jar,war...如果定义了直接返回该资源

2.是否以/开始,如果是就在类路径的上下文中加载该资源

3.判断是否以classpath:开始,如果是创建ClassPathResource

4.尝试创建URLResource

5.创建UrlResource失败后,就在类路径的上下文中加载该资源

@Override
public Resource getResource(String location) {
	Assert.notNull(location, "Location must not be null");

	for (ProtocolResolver protocolResolver : this.protocolResolvers) {
		//资源是否定义了协议eg:file,zip,http
		Resource resource = protocolResolver.resolve(location, this);
		if (resource != null) {
			return resource;
		}
	}
	//判断是否为相对路径
	if (location.startsWith("/")) {
		return getResourceByPath(location);
	}
	//是否以classpath:
	else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
		return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
	}
	else {
		try {
			// 创建URLResource
			URL url = new URL(location);
			return new UrlResource(url);
		}
		catch (MalformedURLException ex) {
			//在文件系统中加载资源,getResourceByPath会在FileSystemResourceLoader中重写
			return getResourceByPath(location);
		}
	}
}

protected Resource getResourceByPath(String path) {
	return new ClassPathContextResource(path, getClassLoader());
}

4.2.2 FileSystemResourceLoader

加载文件系统中的资源

	protected Resource getResourceByPath(String path) {
		if (path.startsWith("/")) {
			path = path.substring(1);
		}
		return new FileSystemContextResource(path);
	}

4.2.3 ServletContextResourceLoader

从Servlet上下文路径中加载资源它实现了自己的getResourceByPath方法,这里的path即使以”/”开头,也是相对ServletContext的路径,而不是绝对路径,要使用绝对路径,需要添加”file:”前缀

protected Resource getResourceByPath(String path) {
    return new ServletContextResource(this.servletContext, path);
}

4.2.4 PathMatchingResourcePatternResolver

匹配模式下的资源加载,持有ResourceLoader接口,不含有地址通配符的情况下要么采用默认的方式,要么采用具体的实现方式加载对应的资源

        private final ResourceLoader resourceLoader;	
        public ResourceLoader getResourceLoader() {
		return this.resourceLoader;
	}
	public Resource getResource(String location) {
		return getResourceLoader().getResource(location);
	}

含有地址通配符查找主要分如下几种情况

case 1:以classpath*开始且路径中含有通配符

case 2:以classpath*开始且路径中不含有通配符

case 3:不以classpath*开始且路径中含有通配符

case 4:不以classpath*开始且路径中不含有通配符

其中case 1和case 3执行的逻辑一致,case 4直接采用ClassLoader加载资源比较简单

public Resource[] getResources(String locationPattern) throws IOException {
	Assert.notNull(locationPattern, "Location pattern must not be null”);
	//是否以classpath*:开始
	if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
		// a class path resource (multiple resources for same name possible)
		if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
			// a class path resource pattern
			// case 1:以classpath*开始且路径中含有通配符
			return findPathMatchingResources(locationPattern);
		}
		else {
			// all class path resources with the given name
			//case 2: 以classpath*开始且路径中不含通配符
			return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
		}
	}
	else {
		// Generally only look for a pattern after a prefix here,
		// and on Tomcat only after the "*/" separator for its "war:" protocol.
		int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 :
				locationPattern.indexOf(":") + 1);
		
		if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
			// a file pattern
			//case 3: 不以classpath*开始且路径中含有通配符和case 1处理的逻辑一致
			return findPathMatchingResources(locationPattern);
		}
		else {
			// a single resource with the given name
			//case 4: 不以classpath*开始且路径中不含通配符
			return new Resource[] {getResourceLoader().getResource(locationPattern)};
		}
	}
}

case 1:以classpath*开始且路径中含有通配符

protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
	//确定根路径,该路径中不含有任何的通配符的最长路径
	String rootDirPath = determineRootDir(locationPattern);
  	//rootDirPath之后的包含模式匹配字符的路径信pattern
	String subPattern = locationPattern.substring(rootDirPath.length());
        //递归查找该根路径下匹配的资源
	Resource[] rootDirResources = getResources(rootDirPath);
	Set<Resource> result = new LinkedHashSet<>(16);
	for (Resource rootDirResource : rootDirResources) {
		rootDirResource = resolveRootDirResource(rootDirResource);
		URL rootDirUrl = rootDirResource.getURL();
		if (equinoxResolveMethod != null) {
			if (rootDirUrl.getProtocol().startsWith("bundle")) {
				URL resolvedUrl = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirUrl);
				if (resolvedUrl != null) {
					rootDirUrl = resolvedUrl;
				}
				//创建UrlResource
				rootDirResource = new UrlResource(rootDirUrl);
			}
		}
		if (rootDirUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
			result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl, subPattern, getPathMatcher()));
		}
		//jar中资源
		else if (ResourceUtils.isJarURL(rootDirUrl) || isJarResource(rootDirResource)) {
			result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirUrl, subPattern));
		}
		else {
			//非jar中的资源
			result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
		}
	}
	if (logger.isDebugEnabled()) {
		logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result);
	}
	return result.toArray(new Resource[result.size()]);
}

从代码分析case 1分为3种case

    1.查找并创建VfsResource

    2.在jar中查找Resource,步骤如下

         1.计算当前ResourceJar文件中的根路径

        2.判断路径

        3.创建资源并添加到结果集中

protected Set<Resource> doFindPathMatchingJarResources(Resource rootDirResource, URL rootDirURL, String subPattern)
		throws IOException {
		……
		//计算当前Resource在Jar文件中的根路径
		if (!"".equals(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();
			//以rootEntryPath开头
			if (entryPath.startsWith(rootEntryPath)) {
				String relativePath = entryPath.substring(rootEntryPath.length());
				//相对路径和subPattern匹配
				if (getPathMatcher().match(subPattern, relativePath)) {
					//创建一个Resource。将新创建的Resource添加入结果集中
					result.add(rootDirResource.createRelative(relativePath));
				}
			}
		}
		return result;
	}
}

2.在非jar中查找Resource,步骤如下

    1.获取资源根路径的绝对路径

    2.获得该路径下的所有文件

    3.路径匹配

protected Set<Resource> doFindPathMatchingFileResources(Resource rootDirResource, String subPattern)
		throws IOException {

	File rootDir;
	try {
		//获得资源绝根路径的对路径
		rootDir = rootDirResource.getFile().getAbsoluteFile();
	}
	
	return doFindMatchingFileSystemResources(rootDir, subPattern);
}
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<>(matchingFiles.size());
	for (File file : matchingFiles) {
		//创建资源并添加到结果集
		result.add(new FileSystemResource(file));
	}
	return result;
}

protected Set<File> retrieveMatchingFiles(File rootDir, String pattern) throws IOException {
	….
	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);

	doRetrieveMatchingFiles(fullPattern, rootDir, result);
	return result;
}
protected void doRetrieveMatchingFiles(String fullPattern, File dir, Set<File> result) throws IOException {
	File[] dirContents = dir.listFiles();
	Arrays.sort(dirContents);
	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);
			}
		}
		if (getPathMatcher().match(fullPattern, currPath)) {
			result.add(content);
		}
	}
}

case 2: 以classpath*开始且路径中不含通配符,步骤如下

    1.采用ClassLoader#getResources(String) 获取资源URL

    2.遍历URL创建资源

    3.如果截取后的path=“”,根据ClassLoaer的类型创建资源,

    4.如果ClassLoader类型不匹配,就采用父加载器查找并创建资源

protected Resource[] findAllClassPathResources(String location) throws IOException {
	……
	Set<Resource> result = doFindAllClassPathResources(path);
	……
	return result.toArray(new Resource[result.size()]);
}
protected Set<Resource> doFindAllClassPathResources(String path) throws IOException {
	ClassLoader cl = getClassLoader();
	//采用ClassLoaer获取资源
	Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path));
	while (resourceUrls.hasMoreElements()) {
		URL url = resourceUrls.nextElement();
		//创建Resource
		result.add(convertClassLoaderURL(url));
	}
	if ("".equals(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;
}
protected Resource convertClassLoaderURL(URL url) {
	return new UrlResource(url);
}
protected void addAllClassLoaderJarRoots(@Nullable ClassLoader classLoader, Set<Resource> result) {
	if (classLoader instanceof URLClassLoader) {
		try {
			for (URL url : ((URLClassLoader) classLoader).getURLs()) {
				try {
					//创建Resource
					UrlResource jarResource = new UrlResource(
							ResourceUtils.JAR_URL_PREFIX + url.toString() + ResourceUtils.JAR_URL_SEPARATOR);
					if (jarResource.exists()) {
						result.add(jarResource);
					}
				}
				……
			}
		}
		……
	}

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

	if (classLoader != null) {
		try {
			// Hierarchy traversal...
			//从父加载器中查找资源
			addAllClassLoaderJarRoots(classLoader.getParent(), result);
		}
		……
	}
}
protected void addClassPathManifestEntries(Set<Resource> result) {
	try {
		String javaClassPathProperty = System.getProperty("java.class.path");
		for (String path : StringUtils.delimitedListToStringArray(
				javaClassPathProperty, System.getProperty("path.separator"))) {
			try {
				File file = new File(path);
				UrlResource jarResource = new UrlResource(ResourceUtils.JAR_URL_PREFIX +
						ResourceUtils.FILE_URL_PREFIX + file.getAbsolutePath() +
						ResourceUtils.JAR_URL_SEPARATOR);
				if (jarResource.exists()) {
					result.add(jarResource);
				}
			}
			……
		}
	}
	……
}

case 3: 不以classpath*开始且路径中含有通配符

return findPathMatchingResources(locationPattern);

case 4: 不以classpath*开始且路径中不含通配符

直接采用ClassLoader加载资源

// a single resource with the given name
return new Resource[] {getResourceLoader().getResource(locationPattern)};

classpath下的资源,相同名字的资源可能存在多个,如果使用”classpath*:”作为前缀,表明需要找到classpath下所有该名字资源,因而需要调用findClassPathResources方法查找classpath下所有该名称的Resource,对非classpath下的资源,对于不存在模式字符的location,一般认为一个location对应一个资源,因而直接调用ResourceLoader.getResource()方法即可(对classpath下没有以”classpath*:”开头的location也适用)

无论是classpath还是classpath*都可以加载整个classpath下(包括jar包里面)的资源文件。
classpath只会返回第一个匹配的资源,查找路径是优先在项目中查找资源文件,再查找jar包。
文件名字包含通配符资源,如果根目录为"", classpath加载不到任何资源, 而classpath*则可以加载到classpath中可以匹配的目录中的资源,但是不能加载到jar包中的资源

4.2.5 ClassPathXmlApplicationContext

    默认将通过classpath进行加载返回ClassPathResource

4.2.6 FileSystemXmlApplicationContext

    将加载“configLocation”位置的资源,可以是相对路径也可以是绝对路径

4.2.7 ServletContextResourcePatternResolver

重写了父类的查找逻辑,它使用ServletContext.getResourcePaths()方式来查找参数目录下的文件,而不是File.listFiles()方法

	protected Set<Resource> doFindPathMatchingFileResources(Resource rootDirResource, String subPattern)
			throws IOException {

		if (rootDirResource instanceof ServletContextResource) {
			ServletContextResource scResource = (ServletContextResource) rootDirResource;
			ServletContext sc = scResource.getServletContext();
			String fullPattern = scResource.getPath() + subPattern;
			Set<Resource> result = new LinkedHashSet<>(8);
			doRetrieveMatchingServletContextResources(sc, fullPattern, scResource.getPath(), result);
			return result;
		}
		else {
			return super.doFindPathMatchingFileResources(rootDirResource, subPattern);
		}
	}
protected void doRetrieveMatchingServletContextResources(
			ServletContext servletContext, String fullPattern, String dir, Set<Resource> result)
			throws IOException {

		Set<String> candidates = servletContext.getResourcePaths(dir);
}

5 Note

war/jar/zip中的资源如果使用resource.getFile()会抛出FileNotFoundError.此时需要使用Resource.getInputStream()读取资源。

6 参考

1. Spring源码

2.https://www.cnblogs.com/doit8791/p/5774743.html

3.https://yq.aliyun.com/articles/46894


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值