一、接口关系图如下:
InputStreamSource接口是Resource接口的父接口,Resource接口表示一个可读的资源对象的公共方法,包含的方法如下图所示。其中readableChannel方法返回的是NIO里面的ReadableByteChannel对象,其中createRelative(String path)方法表示创建一个基于当前路径的相对路径的资源对象,参考具体实现的测试用例。
AbstractResource是Resource接口的抽象实现类,提供了部分方法的默认实现,注意该类覆写了equals(Object other)和hashCode()方法,依据getDescription()返回值判断。
DescriptiveResource是一个Resource接口的空实现
ByteArrayResource表示字节数组资源
VfsResource表示Jboss VFS文件的资源,通过VfsUtils实现,由于Jboss VFS兼容性较差,在非jboss应用不推荐使用InputStreamResource 表示输入流的的资源
AbstractFileResolvingResource 提供了网络或者本地文件资源的抽象实现类,对网络文件资源通过设置请求方法为HEAD来获取文件是否可以访问,大小和最后一次修改时间
UrlResource继承自AbstractFileResolvingResource,重点重写了equals(Object other)方法和hashCode()方法,通过比较每个原始URL字符串对应的cleanUrl判断,该方法可以将windowns中的\\转换成/,将../ 和 ./ 翻译成实际的目录。参考如下测试用例:
@Test
public void testUrlResource3() throws IOException {
System.out.println(StringUtils.cleanPath("file:org/springframework/core/../core/io/./Resource.class"));
System.out.println(StringUtils.cleanPath("file:org/springframework/core/../asm/./Edge.class"));
System.out.println(StringUtils.cleanPath("file:org/springframework/core/../../../asm/./Edge.class"));
System.out.println(StringUtils.cleanPath("file:\\dir\\test.txt?argh"));
}
该用例的结果如下:
file:org/springframework/core/io/Resource.class
file:org/springframework/asm/Edge.class
file:asm/Edge.class
file:/dir/test.txt?argh
该方法的源码说明:
public static String cleanPath(String path) {
if (!hasLength(path)) {
return path;
}
//替换windows中的\\
String pathToUse = replace(path, WINDOWS_FOLDER_SEPARATOR, FOLDER_SEPARATOR);
int prefixIndex = pathToUse.indexOf(':');
String prefix = "";
if (prefixIndex != -1) {
prefix = pathToUse.substring(0, prefixIndex + 1);
if (prefix.contains(FOLDER_SEPARATOR)) {
prefix = "";
}
else {
pathToUse = pathToUse.substring(prefixIndex + 1);
}
}
if (pathToUse.startsWith(FOLDER_SEPARATOR)) {
prefix = prefix + FOLDER_SEPARATOR;
pathToUse = pathToUse.substring(1);
}
// 按路径分隔符/拆分成字符串组数
String[] pathArray = delimitedListToStringArray(pathToUse, FOLDER_SEPARATOR);
// 采用LinkedList是因为该List实现支持更灵活的插入和删除某个index的元素的操作
LinkedList pathElements = new LinkedList<>();
int tops = 0;
//此处从数组中最后一个元素开始遍历
for (int i = pathArray.length - 1; i >= 0; i--) {
String element = pathArray[i];
//当前路径元素./
if (CURRENT_PATH.equals(element)) {
// Points to current directory - drop it.
}
//上一级路径元素 ../
else if (TOP_PATH.equals(element)) {
// Registering top path found.
tops++;
}
else {
if (tops > 0) {
// Merging path element with element corresponding to top path.
//上一个元素是上一级路径,则该元素被合并了
tops--;
}
else {
// Normal path element found.
//每次添加都是添加到index为0的位置
pathElements.add(0, element);
}
}
}
// Remaining top paths need to be retained.
for (int i = 0; i < tops; i++) {
pathElements.add(0, TOP_PATH);
}
//个人提供的另一种更简洁的实现,正向遍历
// for (int i = 0; i
ClassPathResource类继承自AbstractFileResolvingResource抽象类,提供了基于Class对象和ClassLoader对象获取资源的方法,都不为空的情况下优先使用Class对象获取资源,两者都为空则使用SystemClassLoader获取资源,需要注意基于Class获取资源时如果没有带/是基于该Class所在的类路径,如果带/是基于应用的根类路径,基于ClassLoader获取资源不能带/,只能基于应用的根类路径,参考如下测试用例,test.txt文件在ResourceTests.java同目录下有一个,在resources文件夹下也有一个。
@Test
public void testClassPath() throws IOException {
System.out.println(ResourceTests.class.getResource("test.txt"));
System.out.println(ResourceTests.class.getResource("/test.txt"));
System.out.println(ResourceTests.class.getClassLoader().getResource("test.txt"));
System.out.println(ResourceTests.class.getClassLoader().getResource("/test.txt"));
}
该用例的输出如下:
file:/D:/git/spring-framework/spring-core/out/test/classes/org/springframework/core/io/test.txt
file:/D:/git/spring-framework/spring-core/out/test/resources/test.txt
file:/D:/git/spring-framework/spring-core/out/test/resources/test.txt
null
WritableResource接口继承自Resource接口,表示一个可写的资源,接口包含的方法如下:
PathResource继承自AbstractResource类并实现了WritableResource接口,是将文件路径或者URI转化成NIO的Path对象,借助NIO相关方法实现接口的。
FileSystemResource继承自AbstractResource类并实现了WritableResource接口,借助Path对象和File对象实现接口。
FileUrlResource继承自UrlResource并实现了WritableResource接口,借助Resource接口getFile()方法,通过File对象实现接口。