上一篇介绍了通过ClassPathResource类加载资源文件,在Spring里还有一个比较重要的加载资源文件的类DefaultResourceLoader,这一篇介绍下DefaultResourceLoader的使用,该类的继承关系很简单,只继承了ResourceLoader接口,不再粘图说明,新建一个测试用例
@Test
public void testDefaultResourceLoader1() throws IOException {
Resource resource = new DefaultResourceLoader().getResource
("org/springframework/beans/factory/xml/simplePropertyNamespaceHandlerWithExpressionLanguageTests.xml");
print(resource);
}
测试用例很简单,创建DefaultResourceLoader对象并调用其getResource方法读取资源文件
DefaultResourceLoader的源码:
public class DefaultResourceLoader implements ResourceLoader {
//类加载器
@Nullable
private ClassLoader classLoader;
//协议解析器的集合
private final Set<ProtocolResolver> protocolResolvers = new LinkedHashSet<>(4);
//资源缓存
private final Map<Class<?>, Map<Resource, ?>> resourceCaches = new ConcurrentHashMap<>(4);
/**
* 默认构造函数,优先使用线程上下文类加载器进行
* Create a new DefaultResourceLoader.
* <p>ClassLoader access will happen using the thread context class loader
* at the time of this ResourceLoader's initialization.
*
* @see java.lang.Thread#getContextClassLoader()
*/
public DefaultResourceLoader() {
this.classLoader = ClassUtils.getDefaultClassLoader();
}
/**
* 构造函数
* Create a new DefaultResourceLoader.
*
* @param classLoader the ClassLoader to load class path resources with, or {@code null}
* for using the thread context class loader at the time of actual resource access
*/
public DefaultResourceLoader(@Nullable ClassLoader classLoader) {
this.classLoader = classLoader;
}
/**
* 设置classLoader
* Specify the ClassLoader to load class path resources with, or {@code null}
* for using the thread context class loader at the time of actual resource access.
* <p>The default is that ClassLoader access will happen using the thread context
* class loader at the time of this ResourceLoader's initialization.
*/
public void setClassLoader(@Nullable ClassLoader classLoader) {
this.classLoader = classLoader;
}
/**
* 返回ClassLoader,如果ClassLoader不存在则创建并返回
* Return the ClassLoader to load class path resources with.
* <p>Will get passed to ClassPathResource's constructor for all
* ClassPathResource objects created by this resource loader.
*
* @see ClassPathResource
*/
@Override
@Nullable
public ClassLoader getClassLoader() {
return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader());
}
/**
* 注册解析协议
* Register the given resolver with this resource loader, allowing for
* additional protocols to be handled.
* <p>Any such resolver will be invoked ahead of this loader's standard
* resolution rules. It may therefore also override any default rules.
*
* @see #getProtocolResolvers()
* @since 4.3
*/
public void addProtocolResolver(ProtocolResolver resolver) {
Assert.notNull(resolver, "ProtocolResolver must not be null");
this.protocolResolvers.add(resolver);
}
/**
* 返回协议解析集合
* Return the collection of currently registered protocol resolvers,
* allowing for introspection as well as modification.
*
* @since 4.3
*/
public Collection<ProtocolResolver> getProtocolResolvers() {
return this.protocolResolvers;
}
/**
* 根据valueType返回对应缓存资源
* Obtain a cache for the given value type, keyed by {@link Resource}.
*
* @param valueType the value type, e.g. an ASM {@code MetadataReader}
* @return the cache {@link Map}, shared at the {@code ResourceLoader} level
* @since 5.0
*/
@SuppressWarnings("unchecked")
public <T> Map<Resource, T> getResourceCache(Class<T> valueType) {
return (Map<Resource, T>) this.resourceCaches.computeIfAbsent(valueType, key -> new ConcurrentHashMap<>());
}
/**
* 清楚所有resourceCaches缓存
* Clear all resource caches in this resource loader.
*
* @see #getResourceCache
* @since 5.0
*/
public void clearResourceCaches() {
this.resourceCaches.clear();
}
/**
* 根据指定路径加载资源
* 1.URL位置资源,如”file:C:/test.dat”
* 2.ClassPath位置资源,如”classpath:test.dat”
* 3.相对路径资源,如”WEB-INF/test.dat”,此时返回的Resource实例根据实现不同而不同。
*
* 注意:调用此方法时并不意味着路径下一定存在对应的资源,可以先调用Resource.exists()来检查是否存在,然后再实际读取资源文件
*
* @param location the resource location
* @return
*/
@Override
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
//优先遍历协议解决器集,如果可以解决,则返回位置相应的资源
for (ProtocolResolver protocolResolver : this.protocolResolvers) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
}
//如果资源位置以"/"开头,则获取路径资源
if (location.startsWith("/")) {
return getResourceByPath(location);
}
//如果资源位置以"classpath:"开头,创建路径位置的的类路径资源ClassPathResource
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
} else {
try {
// Try to parse the location as a URL...
// 尝试将路径转换为URL资源
URL url = new URL(location);
return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
} catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
// 没有成功转换为URL资源,则将location视为资源路径并返回对应解析资源
return getResourceByPath(location);
}
}
}
/**
* Return a Resource handle for the resource at the given path.
* <p>The default implementation supports class path locations. This should
* be appropriate for standalone implementations but can be overridden,
* e.g. for implementations targeted at a Servlet container.
*
* @param path the path to the resource
* @return the corresponding Resource handle
* @see ClassPathResource
* @see org.springframework.context.support.FileSystemXmlApplicationContext#getResourceByPath
* @see org.springframework.web.context.support.XmlWebApplicationContext#getResourceByPath
*/
protected Resource getResourceByPath(String path) {
return new ClassPathContextResource(path, getClassLoader());
}
/**
* ClassPathResource that explicitly expresses a context-relative path
* through implementing the ContextResource interface.
*/
protected static class ClassPathContextResource extends ClassPathResource implements ContextResource {
public ClassPathContextResource(String path, @Nullable ClassLoader classLoader) {
super(path, classLoader);
}
@Override
public String getPathWithinContext() {
return getPath();
}
@Override
public Resource createRelative(String relativePath) {
String pathToUse = StringUtils.applyRelativePath(getPath(), relativePath);
return new ClassPathContextResource(pathToUse, getClassLoader());
}
}
}
1.创建DefaultResourceLoader对象
public DefaultResourceLoader() {
//获取classLoader对象
this.classLoader = ClassUtils.getDefaultClassLoader();
}
获取classLoader对象可查看上文讲解05–Spring IoC容器资源文件读取(一)
2.加载资源
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
//优先遍历协议解决器集,如果可以解决,则返回位置相应的资源
for (ProtocolResolver protocolResolver : this.protocolResolvers) {
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 {
// Try to parse the location as a URL...
// 尝试将路径转换为URL资源,例如file:C:/test.dat
URL url = new URL(location);
return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
} catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
// 没有通过上述策略加载资源,且没有成功转换为URL资源,则将location视为相对路径资源进行加载
return getResourceByPath(location);
}
}
}
可以看到,加载资源也是分步骤进行的
- 优先遍历协议解析器
- 获取/开头的相对路径资源
- 获取classpath类路径下资源
- 尝试获取URL资源
- 再次尝试获取相对路径下资源
3.获取InputStream
public InputStream getInputStream() throws IOException {
InputStream is;
//如果类对象新不为null,则使用类对象信息的getResourceAsStream获取输入流
if (this.clazz != null) {
is = this.clazz.getResourceAsStream(this.path);
}
//如果类加载器不为null,则使用类加载器的getResourceAsStream获取输入流
else if (this.classLoader != null) {
is = this.classLoader.getResourceAsStream(this.path);
}
else {
//否则使用ClassLoader类的getSystemResourceAsStream方法获取输入流
is = ClassLoader.getSystemResourceAsStream(this.path);
}
if (is == null) {
//以上三种方法都无法获取到输入流的话,那么说明文件不存在,抛出异常
throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
}
return is;
}
大部分的过程与上文相似,可结合上文来分析,
另外在DefaultResourceLoader新加了一个自定义协议解析器,再来分析下该解析器的使用
4.ProtocolResolver 自定义协议解析器
package com.lyc.cn;
import org.springframework.core.io.ProtocolResolver;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
public class MyProtocolResolver implements ProtocolResolver {
@Override
public Resource resolve(String location, ResourceLoader resourceLoader) {
if (location.startsWith("my")) {
return resourceLoader.getResource(location.replace("my", "classpath"));
}
return null;
}
}
/**
* 自定义Protocol加载资源文件
*/
@Test
public void test5() {
DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
resourceLoader.addProtocolResolver(new MyProtocolResolver());
Resource resource = resourceLoader.getResource("my:/resources/import-into-idea.md");
print(resource);
}
resourceLoader.getResource(location.replace("my", "classpath"));
自定义协议解析器只是在自定义方法里将”classpath”与自定义的”my”进行了替换,其他的解析过程,与我们之前分析的都是相同的
ClassPathResource和DefaultResourceLoader的使用就到此为止了