通过上次Factory中分享,我们使用 FileSystemXmlApplicationContext 举例,其中赋予Application具体加载资源的功能,是因为大部分 ApplicationContext 都继承了 AbstractApplicationContext,而 AbstractApplicationContext 继承实现了 DefaultResourceLoader 。DefaultResourceLoader 中包含了加载资源的功能方法。
下面就从 DefaultResourceLoader 的源码入手,看看其是如何支持扩展的:
public class DefaultResourceLoader implements ResourceLoader {
private ClassLoader classLoader;
/**
* 创建默认的资源加载器。其中 ClassLoader 默认是当前线程的ContextClassLaoder。
* @see java.lang.Thread#getContextClassLoader()
*/
public DefaultResourceLoader() {
this.classLoader = ClassUtils.getDefaultClassLoader();
}
/**
* 创建默认的资源加载器。 其中 ClassLoader 被指定。
*/
public DefaultResourceLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
public void setClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
public ClassLoader getClassLoader() {
return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader());
}
// 核心方法,获取资源。
// 如果是用“classpath:”开头的资源返回 ClassPathResource,其他情况返回URL资源。
// 如果URL资源创建失败,交给 getResourceByPath 方法供子类扩展。
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
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 = new URL(location);
return new UrlResource(url);
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
// 子类可以扩展这个方法进行资源获取的扩展
return getResourceByPath(location);
}
}
}
/**
* 通过指定的路径获取资源。
* 默认实现是使用类路径资源(ClassPathContextResource) 。
* 子类可以选择扩展这个方法达到新的资源加载的目的, 例如实现Servlet容器的资源加载。
* @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());
}
/**
* ClassPathContextResource 定义,继承了ClassPathResource,后面会相信说明这个类。
*/
private static class ClassPathContextResource extends ClassPathResource implements ContextResource {
public ClassPathContextResource(String path, ClassLoader classLoader) {
super(path, classLoader);
}
public String getPathWithinContext() {
return getPath();
}
@Override
public Resource createRelative(String relativePath) {
String pathToUse = StringUtils.applyRelativePath(getPath(), relativePath);
return new ClassPathContextResource(pathToUse, getClassLoader());
}
}
}
通过上面代码,我们可以出子类主要是通过 getResourceByPath(String)方法来实现自己的资源类型。默认支持的资源加载类型是类加载资源和URL资源。在上篇文章的分享中的 FileSystemXmlAppplicationContext 中就扩展了这个方法来支持文件方式(FileSystemResource)的加载资源文件。
下面在说说Spring中资源定义的体系。
Spring 是把所有的资源,包含类路径的、文件的、URL的等等都抽象成为 Resource 接口。关键接口的定义,可以查看 Spring的源码或G一下,网上有很多的介绍。我整理了一下他们类图,可以有个整体的认识,如下:
上图灰色的是内部类,但是他们都是我们常用的 ResourceLoader 中定义的。例如 ClassPathContextResource 是定义在 DefaultResourceLoader 中,FileSystemContextResource 定义在 FileSystemResourceLoader中。关于 BeanDefinitionResource,他是一个内部的 Resource,具体的使用方式在后期的分享中会详细的讨论到。
重点介绍几个Resource:
- FileSystemResource
继承AbstractResource同时实现了WritableResource。FileSystemResource 是需要指定一个文件,无论是java.io.File 还是文件路径,他都需要具体的文件存在。同时,其还实现了 WritableResource 来支持文件的写入操作。
下面的代码是 FileSystemResource 的具体实现:
/**
* This implementation opens a FileInputStream for the underlying file.
* @see java.io.FileInputStream
*/
public InputStream getInputStream() throws IOException {
return new FileInputStream(this.file);
}
/**
* This implementation returns a URL for the underlying file.
* @see java.io.File#toURI()
*/
@Override
public URL getURL() throws IOException {
return this.file.toURI().toURL();
}
/**
* This implementation returns a URI for the underlying file.
* @see java.io.File#toURI()
*/
@Override
public URI getURI() throws IOException {
return this.file.toURI();
}
/**
* This implementation returns the underlying File reference.
*/
@Override
public File getFile() {
return this.file;
}
/**
* This implementation opens a FileOutputStream for the underlying file.
* @see java.io.FileOutputStream
*/
public OutputStream getOutputStream() throws IOException {
return new FileOutputStream(this.file);
}
- ClassPathResource
ClassPathResource 重要的是使用 ClassLoader 来加载资源,至于 JVM 中 ClassLoader 的具体原理读者可以G一下或者看看API文档。ClassLoader 默认是从ClassUtils.getDefaultClassLoader()获得。其首先通过当前线程中获取ContextClassLoader,如果获取不成功,使用 ClassUtils 类加载的 ClassLoader。当 ClassPathResource 没有指定 ClassLoader 或者指定的 ClassLoader 为null时,同时没有启动使用指定Class的关联加载时,上述默认策略将启动。使用指定的 Class 关联的 ClassLoader 进行加载,只需要在创建 ClassPathResouce 时,传入需要关联的 Class 即可。是由指定的 ClassLoader 加载还是指定的 Class 进行关联性的加载只能二者选择一种。
下面是 ClassPathResource 的关键实现代码:
/**
* This implementation opens an InputStream for the given class path resource.
* @see java.lang.ClassLoader#getResourceAsStream(String)
* @see java.lang.Class#getResourceAsStream(String)
*/
public InputStream getInputStream() throws IOException {
InputStream is;
if (this.clazz != null) {
is = this.clazz.getResourceAsStream(this.path);
}
else {
is = this.classLoader.getResourceAsStream(this.path);
}
if (is == null) {
throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
}
return is;
}
/**
* This implementation returns a URL for the underlying class path resource.
* @see java.lang.ClassLoader#getResource(String)
* @see java.lang.Class#getResource(String)
*/
@Override
public URL getURL() throws IOException {
URL url;
if (this.clazz != null) {
url = this.clazz.getResource(this.path);
}
else {
url = this.classLoader.getResource(this.path);
}
if (url == null) {
throw new FileNotFoundException(getDescription() + " cannot be resolved to URL because it does not exist");
}
return url;
}
/**
* This implementation creates a ClassPathResource, applying the given path
* relative to the path of the underlying resource of this descriptor.
* @see org.springframework.util.StringUtils#applyRelativePath(String, String)
*/
@Override
public Resource createRelative(String relativePath) {
String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);
return new ClassPathResource(pathToUse, this.classLoader, this.clazz);
}
- ServletContextResource
ServletContextResource 需要使用到javax.servlet.ServletContext,其中 getURL 、 getPath、getInputStream 实现直接使用 ServletContext 提供的相关实现进行转换。
/**
* This implementation delegates to <code>ServletContext.getResourceAsStream</code>,
* but throws a FileNotFoundException if no resource found.
* @see javax.servlet.ServletContext#getResourceAsStream(String)
*/
public InputStream getInputStream() throws IOException {
InputStream is = this.servletContext.getResourceAsStream(this.path);
if (is == null) {
throw new FileNotFoundException("Could not open " + getDescription());
}
return is;
}
/**
* This implementation delegates to <code>ServletContext.getResource</code>,
* but throws a FileNotFoundException if no resource found.
* @see javax.servlet.ServletContext#getResource(String)
*/
@Override
public URL getURL() throws IOException {
URL url = this.servletContext.getResource(this.path);
if (url == null) {
throw new FileNotFoundException(
getDescription() + " cannot be resolved to URL because it does not exist");
}
return url;
}
/**
* This implementation resolves "file:" URLs or alternatively delegates to
* <code>ServletContext.getRealPath</code>, throwing a FileNotFoundException
* if not found or not resolvable.
* @see javax.servlet.ServletContext#getResource(String)
* @see javax.servlet.ServletContext#getRealPath(String)
*/
@Override
public File getFile() throws IOException {
URL url = this.servletContext.getResource(this.path);
if (url != null && ResourceUtils.isFileURL(url)) {
// Proceed with file system resolution...
return super.getFile();
}
else {
String realPath = WebUtils.getRealPath(this.servletContext, this.path);
return new File(realPath);
}
}
/**
* This implementation creates a ServletContextResource, applying the given path
* relative to the path of the underlying file of this resource descriptor.
* @see org.springframework.util.StringUtils#applyRelativePath(String, String)
*/
@Override
public Resource createRelative(String relativePath) {
String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);
return new ServletContextResource(this.servletContext, pathToUse);
}