Spring用Resource接口抽象所有的底层资源,包括File、ClassPath、URL等。ResourceLoader接口是Resource的加载器,根据资源的路径/路径模式获取Resource实例。
Resource
接口定义
Resource接口的定义如下:
public interface Resource extends InputStreamSource {
boolean exists();
default boolean isReadable() {
return exists();
}
default boolean isOpen() {
return false;
}
default boolean isFile() {
return false;
}
URL getURL() throws IOException;
URI getURI() throws IOException;
File getFile() throws IOException;
default ReadableByteChannel readableChannel() throws IOException {
return Channels.newChannel(getInputStream());
}
long contentLength() throws IOException;
long lastModified() throws IOException;
Resource createRelative(String relativePath) throws IOException;
@Nullable
String getFilename();
String getDescription();
}
它继承自InputStreamSource接口,这个接口只有一个方法:
public interface InputStreamSource {
InputStream getInputStream() throws IOException;
}
这里比较重要的几个方法有:
- getInputStream(),定位并且打开当前资源,返回该资源的一个InputStream。每次调用都会返回一个新的InputStream。
- exists(),返回该资源是否物理存在。
- getURL(),返回该资源的URL句柄。
- getURI(),返回该资源的URI句柄。
- isFile(),是否是文件。
- getFile(),返回该资源的File句柄。
Resource接口的实现
Spring提供了多种Resource实现,我们可以直接使用。
FileSystemResource
FileSystemResource是针对java.io.File的Resource实现类,其构造函数与getInputStream()方法如下:
public class FileSystemResource extends AbstractResource implements WritableResource {
private final String path;
@Nullable
private final File file;
private final Path filePath;
public FileSystemResource(String path) {
Assert.notNull(path, "Path must not be null");
this.path = StringUtils.cleanPath(path);
this.file = new File(path);
this.filePath = this.file.toPath();
}
//other constructors...
...
@Override
public InputStream getInputStream() throws IOException {
try {
return Files.newInputStream(this.filePath);
}
catch (NoSuchFileException ex) {
throw new FileNotFoundException(ex.getMessage());
}
}
...
}
可以看到它封装了java.io.File,在获取输入流时就是打开了该文件的NIO文件流。
ClassPathResource
ClassPathResource是类路径下资源的Resource实现。它通过ClassLoader或Class来加载资源。其构造函数和getInputStream()方法的实现如下:
public class ClassPathResource extends AbstractFileResolvingResource {
private final String path;
@Nullable
private ClassLoader classLoader;
@Nullable
private Class<?> clazz;
public ClassPathResource(String path, @Nullable ClassLoader classLoader) {
Assert.notNull(path, "Path must not be null");
String pathToUse = StringUtils.cleanPath(path);
if (pathToUse.startsWith("/")) {
pathToUse = pathToUse.substring(1);
}
this.path = pathToUse;
this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
}
//other constructors
...
public InputStream getInputStream() throws IOException {
InputStream is;
if (this.clazz != null) {
is = this.clazz.getResourceAsStream(this.path);
}
else if (this.classLoader != null) {
is = this.classLoader.getResourceAsStream(this.path);
}
else {
is = ClassLoader.getSystemResourceAsStream(this.path);
}
if (is == null) {
throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
}
return is;
}
...
}
可以看到它最终委托Class对象或ClassLoader对象来加载资源。如果创建该ClassPathResource时指定了用于加载资源的Class对象,它会通过该Class对象的getResourceAsStream来加载资源;否则,如果指定了ClassLoader,它会通过该ClassLoader对象的getResourceAsStream来加载资源;否则,通过系统ClassLoader来加载资源。
UrlResource
UrlResource是java.net.URL的Resource实现类,用来访问URL可以正常访问的任意对象。其内部封装了URL对象,因此,它支持http、https、file、ftp、jar等协议。其getInputStream()方法实现如下:
public InputStream getInputStream() throws IOException {
URLConnection con = this.url.openConnection();
ResourceUtils.useCachesIfNecessary(con);
try {
return con.getInputStream();
}
catch (IOException ex) {
// Close the HTTP connection (if applicable).
if (con instanceof HttpURLConnection) {
((HttpURLConnection) con).disconnect();
}
throw ex;
}
}
可以看到它是打开一个新的到该URL所引用的远程对象的连接,获取URLConnection对象,然后获取输入流。
InputStreamResource
InputStreamResource把一个InputStream封装为Resource,其内部实现大致如下:
public class InputStreamResource extends AbstractResource {
private final InputStream inputStream;
private final String description;
private boolean read = false;
public InputStreamResource(InputStream inputStream, @Nullable String description) {
Assert.notNull(inputStream, "InputStream must not be null");
this.inputStream = inputStream;
this.description = (description != null ? description : "");
}
...
@Override
public InputStream getInputStream() throws IOException, IllegalStateException {
if (this.read) {
throw new IllegalStateException("InputStream has already been read - " +
"do not use InputStreamResource if a stream needs to be read multiple times");
}
this.read = true;
return this.inputStream;
}
...
}
ByteArrayResource
ByteArrayResource把字节数组封装为Resource。其实现大致如下:
public class ByteArrayResource extends AbstractResource {
private final byte[] byteArray;
private final String description;
public ByteArrayResource(byte[] byteArray, @Nullable String description) {
Assert.notNull(byteArray, "Byte array must not be null");
this.byteArray = byteArray;
this.description = (description != null ? description : "");
}
...
@Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(this.byteArray);
}
...
}
ResourceLoader
接口定义
ResourceLoader的接口定义如下:
public interface ResourceLoader {
Resource getResource(String location);
@Nullable
ClassLoader getClassLoader();
}
其中,getResource(String)接口返回指定位置的资源,getClassLoader()接口返回该ResourceLoader所使用的ClassLoader。
ResourceLoader接口的实现
ApplicationContext
Spring应用上下文都实现了ResourceLoader接口,因此所有的应用上下文都可以通过getResource(String)方法获取Resource实例。
DefaultResourceLoader
DefaultResourceLoader是默认的ResourceLoader实现,同时它也是AbstractApplicationContext类的基类。它的实现大致如下:
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);
...
public DefaultResourceLoader(@Nullable ClassLoader classLoader) {
this.classLoader = classLoader;
}
...
@Override
@Nullable
public ClassLoader getClassLoader() {
return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader());
}
public void addProtocolResolver(ProtocolResolver resolver) {
Assert.notNull(resolver, "ProtocolResolver must not be null");
this.protocolResolvers.add(resolver);
}
public <T> Map<Resource, T> getResourceCache(Class<T> valueType) {
return (Map<Resource, T>) this.resourceCaches.computeIfAbsent(valueType, key -> new ConcurrentHashMap<>());
}
@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);
}
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 = new URL(location);
return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
return getResourceByPath(location);
}
}
}
...
}
可以看到,在创建DefaultResourceLoader的时候可以指定类加载器,如果没有指定,就会使用默认的类加载器。
同时,该类维护了一个ProtocolResolver集合,用来处理其他协议。ProtocolResolver是一个接口,其源码如下:
@FunctionalInterface
public interface ProtocolResolver {
@Nullable
Resource resolve(String location, ResourceLoader resourceLoader);
}
这是一个函数接口,其唯一的抽象方法resolve,第一个参数是资源路径,第二个参数是针对该类协议的资源的资源加载器。
然后我们看一下DefaultResourceLoader的getResource(String)方法,该方法首先通过用户自己注册的ProtocolResolver尝试解析和加载资源,如果成功则直接返回;若不成功,则判断资源路径是否以/开头,如果以/开头,则直接返回一个ClassPathContextResource,若不是以/开头,但是以classpath:开头,则直接返回一个ClassPathResource,否则,根据协议返回FileUrlResource或UrlResource。
ResourcePatternResolver
ResourcePatternResolver是继承自ResourceLoader接口的一个接口,用来解析路径模式,例如Ant风格的路径模式,其源码如下:
public interface ResourcePatternResolver extends ResourceLoader {
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
Resource[] getResources(String locationPattern) throws IOException;
}
其中,getResources(String)接口根据输入的路径模式,返回Resource数组。
ResourcePatternResolver的一个实现是PathMatchingResourcePatternResolver,它可以解析指定的资源位置路径到一个或多个匹配的Resource,这里,资源路径可以是一个指向某一个特定的资源的简单路径,也可以是包含classpath*:前缀的路径,路径里还可以使用Ant风格正则表达式。例如,你可以使用以下这些路径:
- file:C:/context.xml;
- classpath:/context.xml;
- /WEB-INF/context.xml;
- /WEB-INF/*-context.xml;
- file:C:/some/path/*-context.xml;
- com/mycompany/**/applicationContext.xml
- classpath:com/mycompany/**/applicationContext.xml
- classpath*:META-INF/*-beans.xml
Ant风格正则表达式的细节可以参见这篇文章:https://jinnianshilongnian.iteye.com/blog/1416322。