首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Spring框架FileSystemResource源码解读

在上一篇文章中的下载功能中,有提到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 接口,与这些资源实现具有一致的操作方式,提供了灵活和统一的资源管理功能。

  • 发表于:
  • 原文链接https://page.om.qq.com/page/OZMbSO9S7s-QMlRSGUHnHeLw0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券