在上一篇文章中的下载功能中,有提到FileSystemResource文件系统资源类,为什么说它是基于NIO操作的呢,到底是不是呢,现在一起看一下。
FileSystemResource,从字面意思可以理解为文件系统资源类,是一个资源管理类。
一、FileSystemResource源码解读
FileSystemResource继承了AbstractResource抽象类,实现了WritableResource接口。
1、私有成员变量
// 文件路径
private final String path;
// 文件对象
private final File file;
// Path对象
private final Path filePath;
三个私有成员变量,分别代表三种可操作的文件对象。
有一点需要注意,File是java.io包中的对象,Path是java.nio包中的对象,File和Path对象可以互相转换。
2、构造方法
第一个构造方法:
/**
* 通过文件路径创建一个新的 `FileSystemResource` 实例。
*/
public FileSystemResource(String path) {
// 对path进行了非空判断
Assert.notNull(path, "Path must not be null");
// 对路径的格式进行了处理
this.path = StringUtils.cleanPath(path);
// 创建File对象
this.file = new File(path);
// 通过File对象获取Path对象
this.filePath = this.file.toPath();
}
在这个构造函数中,我们可以通过文件路径创建一个FileSystemResource对象。
在这个构造函数中,首先对文件路径path进行了非空断言,path是不可以为空的。然后对路径进行了格式化处理。接着构建了一个file实例,最后通过file实例获取Path对象。
第二个构造方法:
在这个构造函数里,file实例是通过参数传递过来的,也就是说提前构建了File实例。
在这个构造函数里,同样对传递过来的参数进行了非空断言,将传递过来的参数file 赋给私有成员变量file,最后根据file获取Path对象。
第三个构造方法:
/**
* 通过 `Path` 对象创建一个新的 `FileSystemResource` 实例。
*/
public FileSystemResource(Path filePath) {
Assert.notNull(filePath, "Path must not be null");
this.path = StringUtils.cleanPath(filePath.toString());
this.file = null;
this.filePath = filePath;
}
在这个构造函数,filePath是通过参数传递过来的,直接复制给私有成员变量 filePath,这里并没有根据Path获取file对象,file 直接赋值为null。
第四个构造方法:
/**
* 通过 `FileSystem ` 对象和文件路径创建一个新的 `FileSystemResource` 实例。
*/
public FileSystemResource(FileSystem fileSystem, String path) {
Assert.notNull(fileSystem, "FileSystem must not be null");
Assert.notNull(path, "Path must not be null");
this.path = StringUtils.cleanPath(path);
this.file = null;
this.filePath = fileSystem.getPath(this.path).normalize();
}
在这个构造函数,通过传递过来的FileSystem对象和文件路径创建一个新的 FileSystemResource实例。这两个参数分别做了非空判断。最后使用FileSystem文件系统通过文件路径获取Path对象。
获取一个新的FileSystemResource实例,可以通过以上四个构造函数的任意一个。
3、成员方法
/**
* 获取文件路径
*/
public final String getPath() {
return this.path;
}
如果想获取文件路径,可以通过getPath()获取,这个在构造函数里就已经设置好了,直接返回私有成员变量的值。
/**
* 判断文件是否存在
* @see java.io.File#exists()
*/
@Override
public boolean exists() {
return (this.file != null ? this.file.exists() : Files.exists(this.filePath));
}
如果想判断文件是否存在,可调用 exists()获取判断结果。
在这个方法里,先查看 file对象是否为null,如果file不为null,优先使用file对象的exists()判断,否则就通过 java.nio 包中的工具类 Files.exists(this.filePath) 进行判断。
/**
* 判断文件是否可读
*/
@Override
public boolean isReadable() {
return (this.file != null ? this.file.canRead() && !this.file.isDirectory() :
Files.isReadable(this.filePath) && !Files.isDirectory(this.filePath));
}
如果想判断文件是否可读,可通过isReadable()获取判断结果。
在isReadable() 里,首先判断 file 对象是否为null。如果不为null,优先使用 file对象的canRead()能读判断和 isDirectory() 非路径来判断。否则就通过 java.nio 包中的工具类Files的两个方法来判断,既要可读,还不能是路径。
/**
* 获取输入流
*/
@Override
public InputStream getInputStream() throws IOException {
try {
return Files.newInputStream(this.filePath);
}
catch (NoSuchFileException ex) {
throw new FileNotFoundException(ex.getMessage());
}
}
如果想获取字节输入流,可通过getInputStream()获取。在这个方法里,通过 Files的newInputStream()方法获取。
/**
* 判断文件是否可写
*/
@Override
public boolean isWritable() {
return (this.file != null ? this.file.canWrite() && !this.file.isDirectory() :
Files.isWritable(this.filePath) && !Files.isDirectory(this.filePath));
}
如果想判断文件是否可写,可调用isWritable()获取判断结果,isWritable()和 isReadable()使用了同样的判断逻辑。
/**
* 获取文件输出流
*/
@Override
public OutputStream getOutputStream() throws IOException {
return Files.newOutputStream(this.filePath);
}
如果想获取字节输出流,可通过getOutputStream()获取。在这个方法里,通过Files的newOutputStream()方法获取。
如果想得到URL对象,调用getURL()获取。
在这个方法里,优先通过file获取,file为null时,再通过filePath获取。
/**
* 获取URI对象
*/
@Override
public URI getURI() throws IOException {
return (this.file != null ? this.file.toURI() : this.filePath.toUri());
}
获取URI的规则和获取URL的规则一样。
/**
* 判断是否是文件
*/
@Override
public boolean isFile() {
return true;
}
判断是否是文件,默认返回true。
/**
* 获取文件对象
*/
@Override
public File getFile() {
return (this.file != null ? this.file : this.filePath.toFile());
}
在最后两个构造函数里,file都为null,因此在需要获取file对象时,需要判断 file是否为null,不为 null时直接返回,否则通过filePath获取。
/**
* 使用文件通道以标准读操作的方式打开Path对象并返回
*/
@Override
public ReadableByteChannel readableChannel() throws IOException {
try {
return FileChannel.open(this.filePath, StandardOpenOption.READ);
}
catch (NoSuchFileException ex) {
throw new FileNotFoundException(ex.getMessage());
}
}
如果想获取文件通道FileChannel,而且是可读的,可以通过readableChannel() 获取。FileChannel也是nio包中的工具类。
/**
* 使用文件通道以标准写操作的方式打开Path对象并返回
*/
@Override
public WritableByteChannel writableChannel() throws IOException {
return FileChannel.open(this.filePath, StandardOpenOption.WRITE);
}
writableChannel()同上。对于 FileChannel 有机会再说。
/**
* 获取文件内容大小
*/
@Override
public long contentLength() throws IOException {
if (this.file != null) {
long length = this.file.length();
if (length == 0L && !this.file.exists()) {
throw new FileNotFoundException(getDescription() +
" cannot be resolved in the file system for checking its content length");
}
return length;
}
else {
try {
return Files.size(this.filePath);
}
catch (NoSuchFileException ex) {
throw new FileNotFoundException(ex.getMessage());
}
}
}
如果想获取文件大小,可调用contentLength()方法获取。
在这个方法里,依旧优先使用file获取文件大小。file为null时,再使用Files的 size()获取。
/**
* 返回文件最后一次修改的时间
*/
@Override
public long lastModified() throws IOException {
if (this.file != null) {
return super.lastModified();
}
else {
try {
return Files.getLastModifiedTime(this.filePath).toMillis();
}
catch (NoSuchFileException ex) {
throw new FileNotFoundException(ex.getMessage());
}
}
}
如果想获取文件的最后一次修改时间,可以调用lastModified()获取。
在这个方法里,我们可以看到两种获取方式。老规矩,如果file不为null时,调用父类的方法获取,否则通过Files的getListModifiedTime()获取。
/**
* 获取文件相对路径
*/
@Override
public Resource createRelative(String relativePath) {
String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);
return (this.file != null ? new FileSystemResource(pathToUse) :
new FileSystemResource(this.filePath.getFileSystem(), pathToUse));
}
如果想要创建文件的相对路径,调用createRelative()获取,同时你必须告诉它相对路径的地址。
在这个方法里,首先对传递的路径进通过StringUtils工具类的applyRelativePath() 加工处理,然后再创建相对路径。
/**
* 获取文件名称
*/
@Override
public String getFilename() {
return (this.file != null ? this.file.getName() : this.filePath.getFileName().toString());
}
想获取文件的名称,通过 getFilename()获取。如果file不为null,通过file的getName() 获取。否则通过 filePath的getFileName()获取。
/**
* 获取文件描述
*/
@Override
public String getDescription() {
return "file [" + (this.file != null ? this.file.getAbsolutePath() : this.filePath.toAbsolutePath()) + "]";
}
如果想要获取文件的描述信息,可以通过 getDescription()获取。如果 file 不为null,通过file的getAbsolutePath()获取。否则通过filePath的toAbsolutePath()获取。
二、FileSystemResource源码分析
在FileSystemResource源码里既有File对象,也有Path对象,可以通过四种方式创建一个FileSystemResource实例。
在成员方法里,有一个规矩,优先使用了file对象及其方法,然后才是filePath对象及其方法。File来自于java.io包,Path来自java.nio包,FileSystemResource对它们做了集成。
因此说FileSystemResource是基于 NIO 方式的操作,要看 NIO 怎么理解。
NIO的第一个意思为:New Input/Output,它是Java SE 1.4 引入的一组API,提供了更高效的I/O操作方式,即 java.nio包中的API。
NIO的第二个意思为:NON-Blocking IO非阻塞IO。
FileSystemResource类里有 java.nio包中的类,因此它是NIO的类。
FileSystemResource并没有非阻塞的实现,因此它不是NIO的类。
这一点确实让人有点失望,FileSystemResource不是非阻塞IO。
三、最后总结
FileSystemResource作为Spring框架的一个文件系统资源工具类,还是有很多作用的,它可以帮助我们做很多事情。
资源定位:FileSystemResource用于定位和加载文件系统中的资源,例如配置文件、模板文件等。它提供了对文件路径的抽象和封装,使得 Spring 应用可以通过统一的接口访问文件系统资源。
文件操作支持:除了读取文件内容外,FileSystemResource也提供了对文件相关属性(如文件大小、最后修改时间等)的访问支持,这些功能对于某些场景下的文件操作非常有用。
与其他资源实现的一致性:Spring还提供了其他类型的资源实现,如 ClassPathResource(用于类路径下的资源)、UrlResource(用于通过 URL 访问资源)等。FileSystemResource通过统一的 Resource 接口,与这些资源实现具有一致的操作方式,提供了灵活和统一的资源管理功能。
领取专属 10元无门槛券
私享最新 技术干货