Spring资源文件的读取是通过资源接口Resource的各个实现类提供的,Resource接口抽象了所有sping底层资源,如File,URL, classpath等,对于不同来源的资源文件都有相应的Resource实现如:文件系统资源 FileSystemResource,字节数组资源ByteArrayResource,描述性资源DescriptiveResource,输入流资源InputStreamResource,虚拟文件系统资源VfsResource,类路径资源ClassPathResource,Url资源UrlResource。
类图如下:
InputStreamResource接口只定义了一个getInputStream方法,所有的resource都实现了这个方法
Resource接口定义了3个判断当前状态的方法 是否存在exists(),是否可读isReadable(),是否处于打开状态isOpen()。还定义了资源转换为getURL() ,转化为URI的 getURI(),转化为文件的 getFile()。以及获取资源文件名称getFilename(),最后修改时间lastModified(),内容长度contentLength()等属性的方法,为了便于操作提供了创建相对资源的方法createRelative(String relativePath),在错误处理中需要详细的打印错误文件信息,Resource提供了getDescription()获取文件信息
AbstractResource提供一些方法的默认实现:
package org.springframework.core.io;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import org.springframework.core.NestedIOException;
import org.springframework.util.Assert;
import org.springframework.util.ResourceUtils;
public abstract class AbstractResource implements Resource {
/**
*判断文件是否存在
*如果不存在判断文件是否可以打开
*/
@Override
public boolean exists() {
// Try file existence: can we find the file in the file system?
try {
return getFile().exists();
}
catch (IOException ex) {
// Fall back to stream existence: can we open the stream?
try {
getInputStream().close();
return true;
}
catch (Throwable isEx) {
return false;
}
}
}
/**
* 直接返回true
*/
@Override
public boolean isReadable() {
return true;
}
/**
* 直接返回false
*/
@Override
public boolean isOpen() {
return false;
}
/**
* 直接抛异常
*/
@Override
public URL getURL() throws IOException {
throw new FileNotFoundException(getDescription() + " cannot be resolved to URL");
}
/**
* 先转化为URL,再将转化后的URL转化为URI
*/
@Override
public URI getURI() throws IOException {
URL url = getURL();
try {
return ResourceUtils.toURI(url);
}
catch (URISyntaxException ex) {
throw new NestedIOException("Invalid URI [" + url + "]", ex);
}
}
/**
* 直接抛异常
*/
@Override
public File getFile() throws IOException {
throw new FileNotFoundException(getDescription() + " cannot be resolved to absolute file path");
}
/**
* 文件内容长度
*/
@Override
public long contentLength() throws IOException {
InputStream is = getInputStream();
Assert.state(is != null, "Resource InputStream must not be null");
try {
long size = 0;
byte[] buf = new byte[256];
int read;
while ((read = is.read(buf)) != -1) {
size += read;
}
return size;
}
finally {
try {
is.close();
}
catch (IOException ex) {
}
}
}
/**
* 最后修改时间
*/
@Override
public long lastModified() throws IOException {
File fileToCheck = getFileForLastModifiedCheck();
long lastModified = fileToCheck.lastModified();
if (lastModified == 0L && !fileToCheck.exists()) {
throw new FileNotFoundException(getDescription() +
" cannot be resolved in the file system for checking its last-modified timestamp");
}
return lastModified;
}
/**
*获取用于计算时间戳的文件
*/
protected File getFileForLastModifiedCheck() throws IOException {
return getFile();
}
/**
* 直接抛出异常
*/
@Override
public Resource createRelative(String relativePath) throws IOException {
throw new FileNotFoundException("Cannot create a relative resource for " + getDescription());
}
/**
* 返回null
*/
@Override
public String getFilename() {
return null;
}
/**
*toString使用文件猫叔
*/
@Override
public String toString() {
return getDescription();
}
/**
* equals判断是否是同一个资源如果是返回true
* 如果不是判断文件描述是否相同 如果相同返回true
* 否则false
*/
@Override
public boolean equals(Object obj) {
return (obj == this ||
(obj instanceof Resource && ((Resource) obj).getDescription().equals(getDescription())));
}
/**
* hashCode返回描述的hashcode
*/
@Override
public int hashCode() {
return getDescription().hashCode();
}
}
一、文件系统资源 FileSystemResource
文件系统资源 FileSystemResource,资源以文件系统路径的方式表示,这个类除了继承自AbstractResource,还实现了WritableResource,WritableResource接口定义了 isWritable()和getOutputStream()两个方法,表示这个文件有可能可写,没有实现WritableResource的Resource实现类都不可写入操作。
定义了两个final成员变量
private final File file;
private final String path;
再构造函数中为其赋值:
public FileSystemResource(File file) {
Assert.notNull(file, "File must not be null");
this.file = file;
this.path = StringUtils.cleanPath(file.getPath());
}
public FileSystemResource(String path) {
Assert.notNull(path, "Path must not be null");
this.file = new File(path);
this.path = StringUtils.cleanPath(path);
}
getInputStream()的实现
@Override
public InputStream getInputStream() throws IOException {
return new FileInputStream(this.file);
}
其它方法实现比较简单,值得说明的是equals() 和 hashcode()是根据path来实现的:
@Override
public boolean equals(Object obj) {
return (obj == this ||
(obj instanceof FileSystemResource && this.path.equals(((FileSystemResource) obj).path)));
}
@Override
public int hashCode() {
return this.path.hashCode();
}
我们可以直接使用FileSystemResource读取硬盘上的文件:
String path = "E:/log.txt";
Resource r1= new FileSystemResource(path);
System.out.println("r1 filename: "+r1.getFilename());
System.out.println("r1 description: "+r1.getDescription());
InputStream i1 = r1.getInputStream();
File f = new File("E:/text.txt");
Resource r2= new FileSystemResource(f);
System.out.println("r2 filename "+r2.getFilename());
System.out.println("r2 description: "+r2.getDescription());
InputStream i2 = r1.getInputStream();
二、字节数组资源ByteArrayResource
字节数组资源ByteArrayResource,资源就是字节数组。
定义了两个final类型的成员变量:
private final byte[] byteArray;
private final String description;
在构造函数中赋值:
public ByteArrayResource(byte[] byteArray) {
this(byteArray, "resource loaded from byte array");
}
public ByteArrayResource(byte[] byteArray, String description) {
Assert.notNull(byteArray, "Byte array must not be null");
this.byteArray = byteArray;
this.description = (description != null ? description : "");
}
getInputStream()的实现:
@Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(this.byteArray);
}
其它方法实现比较简单,值得说明的是equals() 和 hashcode()是根据byteArray来实现的:
@Override
public boolean equals(Object obj) {
return (obj == this ||
(obj instanceof ByteArrayResource && Arrays.equals(((ByteArrayResource) obj).byteArray, this.byteArray)));
}
@Override
public int hashCode() {
return (byte[].class.hashCode() * 29 * this.byteArray.length);
}
若是读取字节数组资源,可使用这个资源类:
byte[] bytes = "aabbccdd".getBytes();
Resource r1= new ByteArrayResource(bytes);
System.out.println("r1 filename: "+r1.getFilename());
System.out.println("r1 description: "+r1.getDescription());
InputStream i1 = r1.getInputStream();
Resource r2= new ByteArrayResource(bytes,"aabbccdd");
System.out.println("r2 filename "+r2.getFilename());
System.out.println("r2 description: "+r2.getDescription());
InputStream i2 = r1.getInputStream();
三、输入流资源InputStreamResource
输入流资源InputStreamResource,是一个不可变InputStream的包装和一个不可变的描述字符串
InputStreamResource定义了3个成员变量,其中有一个read属性,主要用来控制不可重复性后去资源
private final InputStream inputStream;
private final String description;
private boolean read = false;
构造函数:
public InputStreamResource(InputStream inputStream) {
this(inputStream, "resource loaded through InputStream");
}
public InputStreamResource(InputStream inputStream, String description) {
if (inputStream == null) {
throw new IllegalArgumentException("InputStream must not be null");
}
this.inputStream = inputStream;
this.description = (description != null ? description : "");
}
getInputStream()的实现:
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;
}
equals() 和 hashcode()是根据InputStream来实现的:
@Override
public boolean equals(Object obj) {
return (obj == this ||
(obj instanceof InputStreamResource && ((InputStreamResource) obj).inputStream.equals(this.inputStream)));
}
@Override
public int hashCode() {
return this.inputStream.hashCode();
}
四、VFS资源——VfsResource
vfs是Virtual File System虚拟文件系统,也称为虚拟文件系统开关(Virtual Filesystem Switch).是Linux档案系统对外的接口。任何要使用档案系统的程序都必须经由这层接口来使用它。(摘自百度百科…)它能一致的访问物理文件系统、jar资源、zip资源、war资源等,VFS能把这些资源一致的映射到一个目录上,访问它们就像访问物理文件资源一样,而其实这些资源不存在于物理文件系统。
五、ClassPathResource
这个资源类表示的是类路径下的资源,资源以相对于类路径的方式表示。这个资源类有3个成员变量,分别是一个不可变的相对路径、一个类加载器、一个类对象
getInputStream()方法:
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;
}
这个我们可以使用它获取classpath下的路径:
String path = "logback.xml";
Resource r1= new ClassPathResource(path);
System.out.println("r1 filename: "+r1.getFilename());
System.out.println("r1 description: "+r1.getDescription());
InputStream i1 = r1.getInputStream();
六、Url资源UrlResource
UrlResource这个资源类封装了可以以URL表示的各种资源。这个资源类有3个属性,一个URI、一个URL,以及一个规范化后的URL,用于资源间的比较以及计算HashCode。
通过构造方法可以看到,这个资源类基本可以看作java.net.URL的封装。这个资源类的很多方法也都是通过URL或URI操作的。
若是操作URL资源,很明显,这个类比单纯的java.net.URL要好很多
getInputStream方法如下:
@Override
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;
}
}
我们可以使用它获取uri
String path = "https://www.baidu.com/";
Resource r1= new UrlResource(path);
System.out.println("r1 filename: "+r1.getFilename());
System.out.println("r1 description: "+r1.getDescription());
InputStream i1 = r1.getInputStream();
七、PathResource类
主要用来定义基于path的路径,该类注入了Path的引用。该类也是实现了WritableResource接口,也是具有可写能力的,和filesystem很相似,Java7及以上版本可用,
定义了一个成员变量:
private final Path path;
构造函数:
public PathResource(Path path) {
Assert.notNull(path, "Path must not be null");
this.path = path.normalize();
}
public PathResource(String path) {
Assert.notNull(path, "Path must not be null");
this.path = Paths.get(path).normalize();
}
public PathResource(URI uri) {
Assert.notNull(uri, "URI must not be null");
this.path = Paths.get(uri).normalize();
}
getInputStream方法如下:
public InputStream getInputStream() throws IOException {
if (!exists()) {
throw new FileNotFoundException(getPath() + " (no such file or directory)");
}
if (Files.isDirectory(this.path)) {
throw new FileNotFoundException(getPath() + " (is a directory)");
}
return Files.newInputStream(this.path);
}
我们可以使用读取一些路径下的资源:
String path = "D:/text.txt";
Resource r1= new PathResource(path);
System.out.println("r1 filename: "+r1.getFilename());
System.out.println("r1 description: "+r1.getDescription());
InputStream i1 = r1.getInputStream();
Resource r2= new PathResource(Paths.get(path));
System.out.println("r2 filename "+r2.getFilename());
System.out.println("r2 description: "+r2.getDescription());
InputStream i2 = r1.getInputStream();