Spring源码分析(二)资源加载利器Resource接口
- 本图:川西旅游中拍摄的(业余摄影)
- 官网:Home
- 参考书籍:Spring源码深度解析-郝佳编著-微信读书
上一篇文章我们对Spring的基本架构有了基本的了解,以及完成了源码分析的基本环境的搭建,接下来我们开始源码分析,以案例来驱动来学习源码的知识
参考文章:spring5 源码深度解析----- IOC 之 容器的基本实现
- 参考官网:Resources :: Spring Framework
1:本文章主要介绍一下Spring中对资源的定义,回想我们看Mybatis的源码分析是,一切的开始都是对资源的解析,加载,封装开始的,可以说这就是程序运行的地基,万丈高楼从地起,我们要熟悉这个思想
2:其实在官方文档中对资源的定义进行了详细的解释:springdoc.cn/spring/core…
一 准备工作
1.1 基本案例搭建
🔷先建测试包,我们就在源码项目中进行自己的测试用例的编写
🔷新建Spring-config.xm配置文件
🔷编写一个Bean,并配置Bean,测试是否可以管理我们的Bean对象
package org.springframework.shu; /** * @description: 测试Bean * @author: shu * @createDate: 2023/4/3 14:54 * @version: 1.0 */ public class MyTestBean { private String name = "EasonShu"; public MyTestBean(){ System.out.println("创建对象"); } public void setName(String name) { System.out.println("调用方法"); this.name = name; } public void sayHello(){ System.out.println("Hello!" + name); } public String getName() { return this.name; } }
🔷配置我们的Bean
🔷测试
package org.springframework.shu; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.core.io.ClassPathResource; /** * @description: 测试Bean * @author: shu * @createDate: 2023/4/3 14:56 * @version: 1.0 */ public class AppTest { @Test public void MyTestBeanTest() { BeanFactory bf = new XmlBeanFactory( new ClassPathResource("spring-config.xml")); MyTestBean myTestBean = (MyTestBean) bf.getBean("myTestBean"); System.out.println(myTestBean.getName()); } }
🔷测试结果
二 Resource接口
Java的标准 java.net.URL 类和各种URL前缀的标准处理程序,不幸的是,还不足以满足对低级资源的所有访问。
例如,没有标准化的 URL 实现可用于访问需要从classpath或相对于 ServletContext 获得的资源。
总结来说就是Java自带的资源库满足不了Spring的要求,而Spring自己封装了对Resource接口
2.1 资源的定义
资源粗略的可以分为(这里以Spring的分类为例)
JDK操纵底层资源基本就是java.net.URL 、java.io.File 、java.util.Properties这些:取资源基本是根据绝对路径或当前类的相对路径来取。从类路径或Web容器上下文中获取资源的时候也不方便。**若直接使用这些方法,需要编写比较多的额外代码,例如前期文件存在判断、相对路径变绝对路径,**而Spring提供的Resource接口提供了更强大的访问底层资源的能力,首先我们来看看Jdk方法
2.1.1 Class
public java.net.URL getResource(String name) { name = resolveName(name); ClassLoader cl = getClassLoader0(); if (cl==null) { // A system class. return ClassLoader.getSystemResource(name); } return cl.getResource(name); } public InputStream getResourceAsStream(String name) { name = resolveName(name); ClassLoader cl = getClassLoader0(); if (cl==null) { // A system class. return ClassLoader.getSystemResourceAsStream(name); } return cl.getResourceAsStream(name); } private String resolveName(String name) { if (name == null) { return name; } if (!name.startsWith("/")) { Class c = this; while (c.isArray()) { c = c.getComponentType(); } String baseName = c.getName(); int index = baseName.lastIndexOf('.'); if (index != -1) { name = baseName.substring(0, index).replace('.', '/') +"/"+name; } } else { name = name.substring(1); } return name; }
简单来说他就是依靠类加载器的能力来加载资源,并且是当前类的路径相关的,也是支持以/开头的绝对路径的,我们在框架的源码很容易看到他的身影
🌈🌈案例
/** * @description: Jdk资源加载测试 * @author: shu * @createDate: 2023/4/3 18:56 * @version: 1.0 */ public class JdkResourceTest { public static void main(String[] args) { // 依赖Jdk的Class进行资源加载 InputStream asStream = JdkResourceTest.class.getResourceAsStream("/spring-config.xml"); System.out.println(asStream); URL url = JdkResourceTest.class.getResource("/spring-config.xml"); System.out.println(url); URL resource = JdkResourceTest.class.getResource("/spring-config.xml"); System.out.println(resource); } }
2.1.2 ClassLoader
public static URL getSystemResource(String name) { ClassLoader system = getSystemClassLoader(); if (system == null) { return getBootstrapResource(name); } return system.getResource(name); } public static InputStream getSystemResourceAsStream(String name) { URL url = getSystemResource(name); try { return url != null ? url.openStream() : null; } catch (IOException e) { return null; } }
🌈🌈案例
package org.springframework.shu; import java.io.InputStream; import java.net.URL; /** * @description: 类加载器测试 * @author: shu * @createDate: 2023/4/3 19:03 * @version: 1.0 */ public class ClassLoaderTest { public static void main(String[] args) { URL url = ClassLoader.getSystemResource("spring-config.xml"); System.out.println(url); InputStream stream = ClassLoader.getSystemResourceAsStream("spring-config.xml"); System.out.println(stream);aa } }
需要注意的是:把java项目打包成jar包,如果jar包中存在资源文件需要访问,必须采取stream的形式访问。可以调用getResourceAsStream()方法,而不能采用路径的方式访问(文件已经被打到jar里面了,不符合路径的)。
2.1.3 File
这种方式我们应该非常熟悉,这里我就不多介绍了
package org.springframework.shu; import java.io.File; /** * @description: 文件测试 * @author: shu * @createDate: 2023/4/3 19:08 * @version: 1.0 */ public class FileTest { public static void main(String[] args) { File file = new File("D:\workspace\spring-framework\spring-framework-5.2.0.RELEASE\spring-core\src\main\java\org\springframework\core\io\AbstractFileResolvingResource.java"); System.out.println(file.exists()); } }
📌📌注意:
2.2 Resource接口
org.springframework.core.io.
包中的Spring Resource
接口,旨在成为一个更有能力的接口,用于抽象访问低级资源。
package org.springframework.core.io; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URL; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; import org.springframework.lang.Nullable; /** * 资源接口: * 在Spring Framework中,资源是一个接口,它抽象了对底层资源的访问,如文件系统、类路径、Web应用程序、 */ public interface Resource extends InputStreamSource { /** * 是否存在 * @return */ boolean exists(); /** * 是否可读 * @return */ default boolean isReadable() { return exists(); } /** * 是否打开 * @return */ default boolean isOpen() { return false; } /** * 是否是文件 * @return */ default boolean isFile() { return false; } /** * 获取URL * @return * @throws IOException */ URL getURL() throws IOException; /** * 获取URI * @return * @throws IOException */ URI getURI() throws IOException; /** * 获取文件 * @return * @throws IOException */ File getFile() throws IOException; /** * 获取可读字节通道 * @return * @throws IOException */ default ReadableByteChannel readableChannel() throws IOException { return Channels.newChannel(getInputStream()); } /** * 获取资源长度 * @return * @throws IOException */ long contentLength() throws IOException; /** * 上次修改时间 * @return * @throws IOException */ long lastModified() throws IOException; /** * 创建相对路径的资源 * @param relativePath * @return * @throws IOException */ Resource createRelative(String relativePath) throws IOException; /** * 获取文件名 * @return */ @Nullable String getFilename(); /** * 获取描述 * @return */ String getDescription(); }
正如 Resource 接口的定义所示,它扩展了 InputStreamSource 接口,也及时上层接口
package org.springframework.core.io; import java.io.IOException; import java.io.InputStream; /** * 用于获取文件输入流的接口 */ public interface InputStreamSource { /** * 获取文件输入流 * @return * @throws IOException */ InputStream getInputStream() throws IOException; }
我们可以看见他对InputStream进行了封装,可以转换成流数据
Resource 接口中最重要的一些方法是。
- getInputStream(): 定位并打开资源,返回一个用于读取资源的 InputStream。我们期望每次调用都能返回一个新的 InputStream。关闭该流是调用者的责任。
- exists(): 返回一个 boolean 值,表示该资源是否以物理形式实际存在。
- isOpen(): 返回一个 boolean,表示该资源是否代表一个具有开放流的句柄。如果为 true,InputStream 不能被多次读取,必须只读一次,然后关闭以避免资源泄漏。对于所有通常的资源实现,除了 InputStreamResource 之外,返回 false。
- getDescription(): 返回该资源的描述,用于处理该资源时的错误输出。这通常是全路径的文件名或资源的实际URL。
下面主要介绍他的几大分支结构
当涉及Spring Framework中的资源管理时,除了Resource
接口本身外,还有几个主要的子接口和实现类,用于特定类型的资源管理和访问。以下是这些接口和实现之间的区别:
AbstractResource
是一个抽象类,它提供了Resource
接口的基本实现,同时也可以作为其他自定义资源实现的基类。它处理了大部分资源操作的共通逻辑,但并没有提供直接的资源访问逻辑。这个类通常用于自定义资源实现时继承。ContextResource
接口是Resource
接口的子接口之一,用于表示基于Spring应用程序上下文的资源,通常指的是ApplicationContext
中定义的资源。它扩展了Resource
接口,添加了一些用于管理资源在应用程序上下文中的注册和访问的方法。HttpResource
接口是Resource
接口的子接口之一,用于表示HTTP资源,例如通过URL访问的远程资源。它通常用于访问Web上的文件,图像,API等。这个接口可以处理与HTTP相关的特定操作,如获取HTTP头信息等。WritableResource
接口也是Resource
接口的子接口之一,用于表示可写的资源,即可以通过它来写入数据。这个接口扩展了Resource
接口,添加了一些用于向资源写入数据的方法。这些接口和抽象类之间的关系可以总结如下:
AbstractResource
是一个抽象类,提供了Resource
接口的基本实现和通用逻辑。ContextResource
、HttpResource
、WritableResource
都是Resource
接口的子接口,它们在特定情境下扩展了Resource
接口,为特定类型的资源提供了更丰富的功能。
在Spring应用程序中,根据需要,您可以使用这些不同的资源接口和实现来处理不同类型的资源,从而更方便地进行资源的加载、访问和管理。
📌基本案例
package org.springframework.shu; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.core.io.ClassPathResource; /** * @description: 测试Bean * @author: shu * @createDate: 2023/4/3 14:56 * @version: 1.0 */ public class AppTest { @Test public void MyTestBeanTest() { BeanFactory bf = new XmlBeanFactory( new ClassPathResource("spring-config.xml")); MyTestBean myTestBean = (MyTestBean) bf.getBean("myTestBean"); System.out.println(myTestBean.getName()); } }
我们从代码中可以看到首先将我们编写的配置文件进行加载,我们来看看他是如何实现的,首先我们先来看看下面的接口
🔷**InputStreamSource:**对InputStream的包装,接口获取InputStream信息
public interface InputStreamSource { // 返回一个流数据 InputStream getInputStream() throws IOException; }
🔷**Resource:**定义了一些基本的文件操作方法
public interface Resource extends InputStreamSource { //返回Resource所指向的底层资源是否存在 boolean exists(); //返回当前Resource代表的底层资源是否可读 default boolean isReadable() { return true; } //返回Resource资源文件是否已经打开,**如果返回true,则只能被读取一次然后关闭以避免内存泄漏;**常见的Resource实现一般返回false default boolean isOpen() { return false; } //@since 5.0 参见:getFile() default boolean isFile() { return false; } //如果当前Resource代表的底层资源能由java.util.URL代表,则返回该URL,否则抛出IO异常 URL getURL() throws IOException; //如果当前Resource代表的底层资源能由java.util.URI代表,则返回该URI,否则抛出IO异常 URI getURI() throws IOException; //如果当前Resource代表的底层资源能由java.io.File代表,则返回该File,否则抛出IO异常 File getFile() throws IOException; //@since 5.0 用到了nio得Channel相关的 default ReadableByteChannel readableChannel() throws IOException { return Channels.newChannel(getInputStream()); } // 返回当前Resource代表的底层文件资源的长度,一般是值代表的文件资源的长度 long contentLength() throws IOException; //返回当前Resource代表的底层资源的最后修改时间 long lastModified() throws IOException; // 用于创建相对于当前Resource代表的底层资源的资源 // 比如当前Resource代表文件资源“d:/test/”则createRelative(“test.txt”)将返回表文件资源“d:/test/test.txt”Resource资源。 Resource createRelative(String relativePath) throws IOException; //返回当前Resource代表的底层文件资源的文件路径,比如File资源“file://d:/test.txt”将返回“d:/test.txt”,而URL资源http://www.javass.cn将返回“”,因为只返回文件路径。 @Nullable String getFilename(); //返回当前Resource代表的底层资源的描述符,通常就是资源的全路径(实际文件名或实际URL地址) String getDescription(); }
🔷**AbstractResource: **直接抽象类实现类子类的方法
public abstract class AbstractResource implements Resource { // File或者流 都从此处判断 // 这里属于通用实现,子类大都会重写这个方法的~~~~~~ @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 { InputStream is = getInputStream(); is.close(); return true; } catch (Throwable isEx) { return false; } } } // 默认都是可读得。大多数子类都会复写 @Override public boolean isReadable() { return true; } // 默认不是打开的。 比如InputStreamResource就会让他return true @Override public boolean isOpen() { return false; } // 默认不是一个File @Override public boolean isFile() { return false; } // 可议看到getURI方法一般都是依赖于getURL的 @Override public URL getURL() throws IOException { throw new FileNotFoundException(getDescription() + " cannot be resolved to URL"); } @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 ReadableByteChannel readableChannel() throws IOException { return Channels.newChannel(getInputStream()); } // 调用此方法,也相当于吧流的read了一遍,请务必注意 @Override public long contentLength() throws IOException { InputStream is = getInputStream(); try { long size = 0; byte[] buf = new byte[255]; 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 { long lastModified = getFileForLastModifiedCheck().lastModified(); if (lastModified == 0L) { throw new FileNotFoundException(getDescription() + " cannot be resolved in the file system for resolving its last-modified timestamp"); } return lastModified; } // 只有一个子类:`AbstractFileResolvingResource`覆盖了此方法 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()); } @Override @Nullable public String getFilename() { return null; } // 这些基础方法,很多子类也都有重写~~~~ 但是一般来说关系不大 @Override public String toString() { return getDescription(); } // 比较的就是getDescription() @Override public boolean equals(Object obj) { return (obj == this || (obj instanceof Resource && ((Resource) obj).getDescription().equals(getDescription()))); } // getDescription()的hashCode @Override public int hashCode() { return getDescription().hashCode(); } }
以0AbstractResource为主要分支,下面我们仔细来介绍一下他的子类,
2.3 主要分支
下面介绍主要的资源类,其余的需要读者自己去看源码,其实也比较简单,我们主要的是学习这种思想
2.3.1 FileSystemResource
这是 java.io 的 Resource 实现。文件句柄。它还支持 java.nio.file。路径句柄,应用 Spring 的标准基于 String 的路径转换,但是通过 java.nio.file 执行所有操作,文件 API。
对于纯 java.nio.path,基于路径的支持使用 PathResource,FileSystemResource 支持将解析作为文件和 URL。
代表java.io.File资源,对于getInputStream操作将返回底层文件的字节流,isOpen将永远返回false,从而表示可多次读取底层文件的字节流。
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(); } // 是否存在 @Override public boolean exists() { return (this.file != null ? this.file.exists() : 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)); } @Override public InputStream getInputStream() throws IOException { try { return Files.newInputStream(this.filePath); } catch (NoSuchFileException ex) { throw new FileNotFoundException(ex.getMessage()); } } @Override public boolean isWritable() { return (this.file != null ? this.file.canWrite() && !this.file.isDirectory() : Files.isWritable(this.filePath) && !Files.isDirectory(this.filePath)); } @Override public OutputStream getOutputStream() throws IOException { return Files.newOutputStream(this.filePath); } }
他的主要作用就是构建File,可以仔细查看源码
Demo:
package org.springframework.shu.resource; import org.springframework.core.io.FileSystemResource; /** * @description: * @author: shu * @createDate: 2023/4/3 19:53 * @version: 1.0 */ public class FileSystemResourceTest { public static void main(String[] args) { FileSystemResource fileSystemResource = new FileSystemResource("E:\Spring源码学习\integration-tests\src\test\java\org\springframework\shu\resource\FileSystemResourceTest.java"); System.out.println(fileSystemResource.getFile()); System.out.println(fileSystemResource.getFilename()); System.out.println(fileSystemResource.getDescription()); System.out.println(fileSystemResource.exists()); } }
2.3.2 InputStreamResource
InputStreamResource 是给定 InputStream 的 Resource 实现。只有在没有特定的 Resource 实现可用的情况下才应该使用它。
特别是,在可能的情况下,更喜欢 ByteArrayResource 或任何基于文件的 Resource 实现。
InputStreamResource代表java.io.InputStream字节流,对于“getInputStream ”操作将直接返回该字节流,因此只能读取一次该字节流,即“isOpen”永远返回true。
public class InputStreamResource extends AbstractResource { private final InputStream inputStream; private final String description; private boolean read = false; @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; } }
这个也比较简单就是把他转换成InputStream
Demo
package org.springframework.shu.resource; import org.springframework.core.io.InputStreamResource; import java.io.*; import java.nio.file.Files; /** * @description: * @author: shu * @createDate: 2023/4/3 20:01 * @version: 1.0 */ public class InputStreamResourceTest { public static void main(String[] args) throws IOException { File file = new File("E:\Spring源码学习\integration-tests\src\test\java\org\springframework\shu\resource\InputStreamResourceTest.java"); InputStream inputStream = Files.newInputStream(file.toPath()); InputStreamResource inputStreamResource = new InputStreamResource(inputStream); System.out.println(inputStreamResource.getInputStream()); } }
2.3.3 BeanDefinitionResource
这就是把配置文件转成我们熟悉的Bean,BeanDefinition描述了一个bean实例,它具有属性值、构造函数参数值以及具体实现提供的进一步信息,关于这一块我们后面会详细介绍,这里先有个印象
class BeanDefinitionResource extends AbstractResource { private final BeanDefinition beanDefinition; /** * Create a new BeanDefinitionResource. * @param beanDefinition the BeanDefinition object to wrap */ public BeanDefinitionResource(BeanDefinition beanDefinition) { Assert.notNull(beanDefinition, "BeanDefinition must not be null"); this.beanDefinition = beanDefinition; } /** * Return the wrapped BeanDefinition object. */ public final BeanDefinition getBeanDefinition() { return this.beanDefinition; } @Override public boolean exists() { return false; } @Override public boolean isReadable() { return false; } @Override public InputStream getInputStream() throws IOException { throw new FileNotFoundException( "Resource cannot be opened because it points to " + getDescription()); } @Override public String getDescription() { return "BeanDefinition defined in " + this.beanDefinition.getResourceDescription(); } /** * This implementation compares the underlying BeanDefinition. */ @Override public boolean equals(@Nullable Object other) { return (this == other || (other instanceof BeanDefinitionResource && ((BeanDefinitionResource) other).beanDefinition.equals(this.beanDefinition))); } /** * This implementation returns the hash code of the underlying BeanDefinition. */ @Override public int hashCode() { return this.beanDefinition.hashCode(); } }
我们可以看到其实就是将我们配置的Bean属性转换成Bean实例,后面详细介绍
2.2.4 DescriptiveResource
简单资源实现,保存资源描述,但不指向实际可读的资源。
public class DescriptiveResource extends AbstractResource { private final String description; /** * Create a new DescriptiveResource. * @param description the resource description */ public DescriptiveResource(@Nullable String description) { this.description = (description != null ? description : ""); } @Override public boolean exists() { return false; } @Override public boolean isReadable() { return false; } @Override public InputStream getInputStream() throws IOException { throw new FileNotFoundException( getDescription() + " cannot be opened because it does not point to a readable resource"); } @Override public String getDescription() { return this.description; } /** * This implementation compares the underlying description String. */ @Override public boolean equals(@Nullable Object other) { return (this == other || (other instanceof DescriptiveResource && ((DescriptiveResource) other).description.equals(this.description))); } /** * This implementation returns the hash code of the underlying description String. */ @Override public int hashCode() { return this.description.hashCode(); } }
这个就不介绍了,其实很简单
2.2.5 ByteArrayResource
这是给定字节数组的 Resource 实现。它为给定字节数组创建 ByteArrayInputStream。
ByteArrayResource代表byte[]数组资源,对于“getInputStream”操作将返回一个ByteArrayInputStream。
public class ByteArrayResource extends AbstractResource { private final byte[] byteArray; private final String description; /** * Create a new {@code ByteArrayResource}. * @param byteArray the byte array to wrap */ public ByteArrayResource(byte[] byteArray) { this(byteArray, "resource loaded from byte array"); } /** * Create a new {@code ByteArrayResource} with a description. * @param byteArray the byte array to wrap * @param description where the byte array comes from */ public ByteArrayResource(byte[] byteArray, @Nullable String description) { Assert.notNull(byteArray, "Byte array must not be null"); this.byteArray = byteArray; this.description = (description != null ? description : ""); } /** * Return the underlying byte array. */ public final byte[] getByteArray() { return this.byteArray; } /** * This implementation always returns {@code true}. */ @Override public boolean exists() { return true; } /** * This implementation returns the length of the underlying byte array. */ @Override public long contentLength() { return this.byteArray.length; } /** * This implementation returns a ByteArrayInputStream for the * underlying byte array. * @see java.io.ByteArrayInputStream */ @Override public InputStream getInputStream() throws IOException { return new ByteArrayInputStream(this.byteArray); } /** * This implementation returns a description that includes the passed-in * {@code description}, if any. */ @Override public String getDescription() { return "Byte array resource [" + this.description + "]"; } /** * This implementation compares the underlying byte array. * @see java.util.Arrays#equals(byte[], byte[]) */ @Override public boolean equals(@Nullable Object other) { return (this == other || (other instanceof ByteArrayResource && Arrays.equals(((ByteArrayResource) other).byteArray, this.byteArray))); } /** * This implementation returns the hash code based on the * underlying byte array. */ @Override public int hashCode() { return (byte[].class.hashCode() * 29 * this.byteArray.length); } }
它可多次读取数组资源,即isOpen()永远返回false
ByteArrayResource因为入参可以是byte[]类型,所以用途比较广泛,可以把从网络或者本地资源都转换为byte[]类型,然后用ByteArrayResource转化为资源
2.2.6 PathResource
这是 java.nio.file 的 Resource 实现,路径句柄,通过路径 API 执行所有操作和转换。它支持作为 File 和 URL 的解析,并且还实现了扩展的 WritableResource 接口。
PathResource 实际上是一个纯 java.nio.path。具有不同创建相对行为的基于路径的 FileSystemResource 替代方案。
它是基于@since 4.0,也是基于JDK7提供的java.nio.file.Path的。实现原理也非常的简单,更像是对java.nio.file.Path进行了包装(java.nio.file.Files)
public class PathResource extends AbstractResource implements WritableResource { private final Path path; public PathResource(Path path) { Assert.notNull(path, "Path must not be null"); this.path = path.normalize(); } }
2.2.7 AbstractFileResolvingResource
它复写了AbstractResource
大多数方法,是一个比较重要的分支。有不少非常好用的实现类
public abstract class AbstractFileResolvingResource extends AbstractResource { @Override public boolean exists() { try { URL url = getURL(); if (ResourceUtils.isFileURL(url)) { // Proceed with file system resolution return getFile().exists(); } else { // Try a URL connection content-length header URLConnection con = url.openConnection(); customizeConnection(con); HttpURLConnection httpCon = (con instanceof HttpURLConnection ? (HttpURLConnection) con : null); if (httpCon != null) { int code = httpCon.getResponseCode(); if (code == HttpURLConnection.HTTP_OK) { return true; } else if (code == HttpURLConnection.HTTP_NOT_FOUND) { return false; } } if (con.getContentLengthLong() > 0) { return true; } if (httpCon != null) { // No HTTP OK status, and no content-length header: give up httpCon.disconnect(); return false; } else { // Fall back to stream existence: can we open the stream? getInputStream().close(); return true; } } } catch (IOException ex) { return false; } } @Override public boolean isReadable() { try { return checkReadable(getURL()); } catch (IOException ex) { return false; } } boolean checkReadable(URL url) { try { if (ResourceUtils.isFileURL(url)) { // Proceed with file system resolution File file = getFile(); return (file.canRead() && !file.isDirectory()); } else { // Try InputStream resolution for jar resources URLConnection con = url.openConnection(); customizeConnection(con); if (con instanceof HttpURLConnection) { HttpURLConnection httpCon = (HttpURLConnection) con; int code = httpCon.getResponseCode(); if (code != HttpURLConnection.HTTP_OK) { httpCon.disconnect(); return false; } } long contentLength = con.getContentLengthLong(); if (contentLength > 0) { return true; } else if (contentLength == 0) { // Empty file or directory -> not considered readable... return false; } else { // Fall back to stream existence: can we open the stream? getInputStream().close(); return true; } } } catch (IOException ex) { return false; } } @Override public boolean isFile() { try { URL url = getURL(); if (url.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) { return VfsResourceDelegate.getResource(url).isFile(); } return ResourceUtils.URL_PROTOCOL_FILE.equals(url.getProtocol()); } catch (IOException ex) { return false; } } /** * This implementation returns a File reference for the underlying class path * resource, provided that it refers to a file in the file system. * @see org.springframework.util.ResourceUtils#getFile(java.net.URL, String) */ @Override public File getFile() throws IOException { URL url = getURL(); if (url.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) { return VfsResourceDelegate.getResource(url).getFile(); } return ResourceUtils.getFile(url, getDescription()); } /** * This implementation determines the underlying File * (or jar file, in case of a resource in a jar/zip). */ @Override protected File getFileForLastModifiedCheck() throws IOException { URL url = getURL(); if (ResourceUtils.isJarURL(url)) { URL actualUrl = ResourceUtils.extractArchiveURL(url); if (actualUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) { return VfsResourceDelegate.getResource(actualUrl).getFile(); } return ResourceUtils.getFile(actualUrl, "Jar URL"); } else { return getFile(); } } /** * This implementation returns a File reference for the given URI-identified * resource, provided that it refers to a file in the file system. * @since 5.0 * @see #getFile(URI) */ protected boolean isFile(URI uri) { try { if (uri.getScheme().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) { return VfsResourceDelegate.getResource(uri).isFile(); } return ResourceUtils.URL_PROTOCOL_FILE.equals(uri.getScheme()); } catch (IOException ex) { return false; } } /** * This implementation returns a File reference for the given URI-identified * resource, provided that it refers to a file in the file system. * @see org.springframework.util.ResourceUtils#getFile(java.net.URI, String) */ protected File getFile(URI uri) throws IOException { if (uri.getScheme().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) { return VfsResourceDelegate.getResource(uri).getFile(); } return ResourceUtils.getFile(uri, getDescription()); } /** * This implementation returns a FileChannel for the given URI-identified * resource, provided that it refers to a file in the file system. * @since 5.0 * @see #getFile() */ @Override public ReadableByteChannel readableChannel() throws IOException { try { // Try file system channel return FileChannel.open(getFile().toPath(), StandardOpenOption.READ); } catch (FileNotFoundException | NoSuchFileException ex) { // Fall back to InputStream adaptation in superclass return super.readableChannel(); } } @Override public long contentLength() throws IOException { URL url = getURL(); if (ResourceUtils.isFileURL(url)) { // Proceed with file system resolution File file = getFile(); long length = file.length(); if (length == 0L && !file.exists()) { throw new FileNotFoundException(getDescription() + " cannot be resolved in the file system for checking its content length"); } return length; } else { // Try a URL connection content-length header URLConnection con = url.openConnection(); customizeConnection(con); return con.getContentLengthLong(); } } @Override public long lastModified() throws IOException { URL url = getURL(); boolean fileCheck = false; if (ResourceUtils.isFileURL(url) || ResourceUtils.isJarURL(url)) { // Proceed with file system resolution fileCheck = true; try { File fileToCheck = getFileForLastModifiedCheck(); long lastModified = fileToCheck.lastModified(); if (lastModified > 0L || fileToCheck.exists()) { return lastModified; } } catch (FileNotFoundException ex) { // Defensively fall back to URL connection check instead } } // Try a URL connection last-modified header URLConnection con = url.openConnection(); customizeConnection(con); long lastModified = con.getLastModified(); if (fileCheck && lastModified == 0 && con.getContentLengthLong()