spring-core 资源管理- Resource 接口讲解
Spring Framework 是一个典型的分层架构,其源码由多个模块组成,每个模块都承担着清晰的职责。如果你要系统地学习和讲解 Spring Framework 的源码,最合理的切入顺序是按照依赖层次从“底层 → 上层”进行讲解和剖析。
今天我们来从最底层 spring-core 进行讲解和剖析。本次讲解的spring-framework组件版本 6.2.7。
一,Resource 资源接口讲解
Resource 是 spring 统一标准访问各种资源的核心接口,比如文件系统资源,类路径资源,URL资源等。它提供了一组统一的方法,比如getInputStream(),exists(),isOpen()等,让不同类型的资源可以以一致的方式被处理。详细的继承结构如下:
1.1 InputStreamSource 接口
在Resource 接口上面,还继承一个也是由Spring定义一个InputStream的输入流标准接口。代码如下:
package org.springframework.core.io;import java.io.IOException;
import java.io.InputStream;/*** @author Juergen Hoeller* @since 20.01.2004*/
@FunctionalInterface
public interface InputStreamSource {/*** 该接口定义了:返回底层资源内容的 InputStream。通常预期每个此类调用都会创建一个新流。* 也就是说,接口定义不允许返回 InputStream 为 null。原因为面对一些需要重复读取流,比如 JavaMail 该 API 需要能够多次读取流。* 对于此类用例,要求每个 getInputStream() 调用都返回一个新流。* @return 底层资源的输入流(不能为 null)* @throws java.io.FileNotFoundException 如果底层资源不存在* @throws IOException 如果无法打开内容流* @see Resource#isReadable()* @see Resource#isOpen()*/InputStream getInputStream() throws IOException;
}
Spring中的 InputStreamSource 定义其底层资源输入流(InputStream)获取标准,其中最重要的一条就是绝对不能返回为空,否则报异常:FileNotFoundException。这也是面对需要重复读取流数据的处理方式。直接返回一条新的流。
1.2 Resource 资源接口
代码如下:
package org.springframework.core.io;import org.springframework.lang.Nullable;
import org.springframework.util.FileCopyUtils;/*** @author Juergen Hoeller* @author Arjen Poutsma* @since 28.12.2003*/
public interface Resource extends InputStreamSource {/** 判断此资源是否在物理形式上实际存在 */boolean exists();/** 表示此资源的非空内容是否可以通过 {@link #getInputStream()} 读取。 */default boolean isReadable() {return exists();}/*** 表示此资源是否代表一个打开的流。* 如果为 {@code true},则 InputStream 不可以被多次读取,* 必须读取并关闭以避免资源泄漏。*/default boolean isOpen() {return false;}/*** 判断此资源是否表示文件系统中的一个文件。* @since 5.0*/default boolean isFile() {return false;}/*** 返回此资源的 URL* @throws IOException 如果资源不能解析为 URL,即资源不可用作为描述符*/URL getURL() throws IOException;/*** 返回此资源的 URI * @throws IOException 如果资源不能解析为 URI,即资源不可用作为描述符*/URI getURI() throws IOException;/*** 返回此资源的 File * @throws java.io.FileNotFoundException 如果资源不能解析为绝对路径文件,* 即资源在文件系统中不可用* @throws IOException 在解析/读取失败的情况下*/File getFile() throws IOException;/*** 返回一个 {@link ReadableByteChannel}。* <p>期望每次调用都创建一个新的通道。* <p>默认实现返回的是通过 {@link Channels#newChannel(InputStream)} * 使用 {@link #getInputStream()} 的结果创建的通道。* @return 底层资源的字节通道(必须不是 {@code null})* @throws java.io.FileNotFoundException 如果底层资源不存在* @throws IOException 如果内容通道无法打开* @since 5.0*/default ReadableByteChannel readableChannel() throws IOException {return Channels.newChannel(getInputStream());}/*** 将此资源的内容作为字节数组返回* @return 此资源的内容作为字节数组* @throws java.io.FileNotFoundException 如果资源不能解析为绝对路径文件,* 即资源在文件系统中不可用* @throws IOException 在解析/读取失败的情况下* @since 6.0.5*/default byte[] getContentAsByteArray() throws IOException {return FileCopyUtils.copyToByteArray(getInputStream());}/*** 使用指定的字符集将此资源的内容作为字符串返回。* @param charset 要用于解码的字符集* @return 此资源的内容作为字符串* @throws java.io.FileNotFoundException 如果资源不能解析为绝对路径文件,* 即资源在文件系统中不可用* @throws IOException 在解析/读取失败的情况下* @since 6.0.5*/default String getContentAsString(Charset charset) throws IOException {return FileCopyUtils.copyToString(new InputStreamReader(getInputStream(), charset));}/*** 确定此资源的内容长度。* @throws IOException 如果资源无法被解析(如不在文件系统或未知物理资源类型)*/long contentLength() throws IOException;/*** 确定此资源的最后修改时间戳。* @throws IOException 如果资源无法被解析(如不在文件系统或未知物理资源类型)*/long lastModified() throws IOException;/*** 创建相对于此资源的新资源。* @param relativePath 相对路径(相对于当前资源)* @return 相对资源的资源句柄* @throws IOException 如果相对资源无法被确定*/Resource createRelative(String relativePath) throws IOException;/*** 获取此资源的文件名 —— 通常是路径的最后一部分。例如,{@code "myfile.txt"}。* 如果此类资源没有文件名,则返回 {@code null}。* 建议实现类返回未编码的文件名。*/@NullableString getFilename();/*** 返回此资源的描述信息, 在处理资源时用于错误输出。* 建议实现类也在其 {@code toString} 方法中返回此值。*/String getDescription();}
Resource
接口通过统一抽象,简化了资源访问逻辑,是 Spring 中资源管理的核心。理解其方法含义和实现类特性,有助于高效处理文件、网络、类路径等资源。实际开发中,优先使用 ResourceLoader
(如 ApplicationContext
)获取资源,而非手动创建具体实现类。
1.3 UrlResource URL获取资源案例
案例准备:小编在spring-framework 源码里面单独建立一个spring-learn 模块,专门用于学习
UrlResource的作用 :表示通过 URL 访问的资源,当 URL 协议为 file://
时支持写入。
/*** @author liuwq* @time 2025/6/12* @remark*/
public class UrlResourceExample {public static void main(String[] args) throws Exception {// 指定远程 URL 资源,获取 B站 首页资源Resource resource = new UrlResource("https://www.bilibili.com/");if (resource.exists()) {System.out.println("资源描述: " + resource.getDescription());try (InputStream is = resource.getInputStream();BufferedReader reader = new BufferedReader(new InputStreamReader(is))) {String line;while ((line = reader.readLine()) != null) {System.out.println(line);}}} else {System.out.println("资源无法访问或不存在!");}}
}
输出结果:B 站首页返回肯定的HTML内容
> Task :spring-learn:run
资源描述: URL [https://www.bilibili.com/]
<!DOCTYPE html><html><head>
<!-- Dejavu Release Version 64940-->
<script>
window._BiliGreyResult = {method: "direct",versionId: "64940",
}
</script><meta charset="UTF-8"><title>验证码_哔哩哔哩</title><meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1,maximum-scale=1,minim
um-scale=1,viewport-fit=cover"><meta name="spm_prefix" content="333.1291"><script type="text/javascript" src="//www.bilibili.com/gentleman/polyfill.js?features=Promi
se%2CObject.assign%2CString.prototype.includes%2CNumber.isNaN"></script><script>window._riskdata_ = { 'v_voucher': 'voucher_9864655f-74b6-4614-baf4-e655eddbe721' }</
script><script type="text/javascript" src="//s1.hdslb.com/bfs/seed/log/report/log-reporter.js"></script><link href="//s1.hdslb.com/bfs/static/jinkela/risk-captcha/cs
s/risk-captcha.0.3bd977140b994afccbcc4d0102d33b9577e89cc0.css" rel="stylesheet"></head><body><div id="biliMainHeader"></div><div id="risk-captcha-app"></div><script
src="//s1.hdslb.com/bfs/seed/jinkela/risk-captcha-sdk/CaptchaLoader.js"></script><script type="text/javascript" src="//s1.hdslb.com/bfs/static/jinkela/risk-captcha/1
.risk-captcha.3bd977140b994afccbcc4d0102d33b9577e89cc0.js"></script><script type="text/javascript" src="//s1.hdslb.com/bfs/static/jinkela/risk-captcha/risk-captcha.3bd977140b994afccbcc4d0102d33b9577e89cc0.js"></script></body></html>
1.4 FileSystemResource 文件系统资获取资源案例
作用:从文件系统读取资源
package com.toast.learn.spring.core.resource;import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;/*** @author liuwq* @time 2025/6/12* @remark*/
public class FileSystemResourceExample {public static void main(String[] args) throws Exception {// 指定文件系统的资源路径Resource resource = new FileSystemResource("D:\\work-space\\spring_GROUP\\spring-framework-6.2.7\\spring-learn\\doc\\与时俱进.txt");if (resource.exists()) {System.out.println("资源描述: " + resource.getDescription());try (InputStream is = resource.getInputStream();BufferedReader reader = new BufferedReader(new InputStreamReader(is));) {String line;while ((line = reader.readLine()) != null) {System.out.println(line);}}} else {System.out.println("资源不存在!");}}
}
与时俱进.txt 内容
输出结果:
> Task :spring-learn:run
资源描述: file [D:\work-space\spring_GROUP\spring-framework-6.2.7\spring-learn\doc\与时俱进.txt]
【当代话梗】芜湖~ 起飞咯
【当代话梗】有的兄弟有的
【当代话梗】包的包的
【当代话梗】那咋了
【21世纪名场面】王法官:人不是你撞的,你为什么要扶!
1.5 ClassPathResourceExample classpath获取资源案例
作用:读取 src/main/resources
目录下的配置文件(如 application.properties
)
package com.toast.learn.spring.core.resource;import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;/*** @author liuwq* @time 2025/6/12* @remark*/
public class ClassPathResourceExample {public static void main(String[] args) throws Exception {// 指定类路径下的资源Resource resource = new ClassPathResource("application.properties");if (resource.exists()) {System.out.println("资源描述: " + resource.getDescription());try (InputStream is = resource.getInputStream();BufferedReader reader = new BufferedReader(new InputStreamReader(is))) {String line;while ((line = reader.readLine()) != null) {System.out.println(line);}}} else {System.out.println("资源不存在!");}}
}
application.properties 的内容如下:
输出结果
> Task :spring-learn:run
资源描述: class path resource [application.properties]
toast.learn.spring.resource.classpath="Toast Learn Spring-framework"
1.6 ByteArrayResource 内存获取资源
基于内存的字节数组资源,虽然是只读资源,但可以通过继承或包装实现写入逻辑。
package com.toast.learn.spring.core.resource;import org.springframework.core.io.ByteArrayResource;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;/*** @author liuwq* @time 2025/6/12* @remark*/
public class ByteArrayResourceExample {public static void main(String[] args) {// 假设这是从某个地方获取的原始字节数据(比如网络、数据库)String data = "准备好学习 spring 了吗。\nHello, ByteArrayResource!";byte[] bytes = data.getBytes();// 使用 ByteArrayResource 包装字节数组ByteArrayResource resource = new ByteArrayResource(bytes);// 读取资源内容try (InputStream inputStream = resource.getInputStream();BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {String line;while ((line = reader.readLine()) != null) {System.out.println(line); // 打印每一行}} catch (IOException e) {e.printStackTrace();}}
}
输出结果:
> Task :spring-learn:run
准备好学习 spring 了吗。
Hello, ByteArrayResource!
二,WritableResource 接口
WritableResource 接口,该接口是为了扩展 Resource 接口的写能力而提供的。
public interface WritableResource extends Resource {}
可以发现其有三个接口都做了写的扩展能力。使用PathResource做一次写案例
2.1 PathResource 写处理
该类通过 java.nio.file.Path 路径来获取文件位置,从而获取资源。以下案例是将写入一些指定数据到指定文件中:与时俱进-writer.txt
package com.toast.learn.spring.core.resource;import org.springframework.core.io.PathResource;import java.io.*;
import java.nio.file.Path;
import java.nio.file.Paths;/*** @author liuwq* @time 2025/6/12* @remark*/
public class PathWritableResourceExample {public static void main(String[] args) {Path path = Paths.get("D:\\work-space\\spring_GROUP\\spring-framework-6.2.7\\spring-learn\\doc\\与时俱进-writer.txt");PathResource resource = new PathResource(path);if (resource.exists()) {System.out.println("资源描述: " + resource.getDescription());try (OutputStream os = resource.getOutputStream(); // 默认是覆盖内容PrintWriter writer = new PrintWriter(os)) {writer.println("【当代话梗】芜湖~ 起飞咯");writer.println("【当代话梗】有的兄弟有的");writer.println("【当代话梗】包的包的");writer.println("【当代话梗】那咋了");writer.println("【21世纪名场面】王法官:人不是你撞的,你为什么要扶!");} catch (Exception e) {e.printStackTrace();}} else {System.out.println("资源不存在!");}}
}
输出结果:
> Task :spring-learn:run
资源描述: path [D:\work-space\spring_GROUP\spring-framework-6.2.7\spring-learn\doc\与时俱进-writer.txt]
同样的道理,其中 FileSystemResource 子类也是通过文件系统来获取文件资源,也可以进行写入数据。而通过URL 的方式获取资源,则不可以进行写入,因为URL获取到的资源并非本地文件资源。要是真的支持写入,那不是成攻击别人的服务器的吗。案例如下:
2.2 FileUrlResource 写处理
package com.toast.learn.spring.core.resource;import org.springframework.core.io.FileUrlResource;
import org.springframework.core.io.PathResource;import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;/*** @author liuwq* @time 2025/6/12* @remark*/
public class FileUrlWritableResourceExample {public static void main(String[] args) {try {// 获取B 站资源URL url = new URL("https://www.bilibili.com/");FileUrlResource resource = new FileUrlResource(url);if (resource.exists()) {System.out.println("资源描述: " + resource.getDescription());try (OutputStream os = resource.getOutputStream(); // 往里面写数据PrintWriter writer = new PrintWriter(os)) {writer.println("【当代话梗】芜湖~ 起飞咯");writer.println("【当代话梗】有的兄弟有的");writer.println("【当代话梗】包的包的");writer.println("【当代话梗】那咋了");writer.println("【21世纪名场面】王法官:人不是你撞的,你为什么要扶!");} catch (Exception e) {e.printStackTrace();}} else {System.out.println("资源不存在!");}} catch (MalformedURLException e) {throw new RuntimeException(e);}}
}
输出结果:
> Task :spring-learn:run
资源描述: URL [https://www.bilibili.com/]
java.io.FileNotFoundException: URL [https://www.bilibili.com/] cannot be resolved to absolute file path because it does not reside in the file system: https://www.bilibili.com/at org.springframework.util.ResourceUtils.getFile(ResourceUtils.java:223)at org.springframework.core.io.AbstractFileResolvingResource.getFile(AbstractFileResolvingResource.java:210)at org.springframework.core.io.UrlResource.getFile(UrlResource.java:297)at org.springframework.core.io.FileUrlResource.getFile(FileUrlResource.java:84)at org.springframework.core.io.FileUrlResource.getOutputStream(FileUrlResource.java:102)at com.toast.learn.spring.core.resource.FileUrlWritableResourceExample.main(FileUrlWritableResourceExample.java:28)[Incubating] Problems report is available at: file:///D:/work-space/spring_GROUP/spring-framework-6.2.7/build/reports/problems/problems-report.html
你试图将一个 URL 资源(网络地址)当作本地文件路径来处理 ,但这个 URL 并不是本地文件系统中的资源。这些操作都试图把一个 网络资源 当作本地文件来处理,这是不允许的!
- 不能处理网络地址(http:// 或 https://)
- 它只能处理本地文件路径(如
file:/xxx/xxx
)
package com.toast.learn.spring.core.resource;import org.springframework.core.io.FileUrlResource;
import org.springframework.core.io.PathResource;import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;/*** @author liuwq* @time 2025/6/12* @remark*/
public class FileUrlWritableResourceExample {public static void main(String[] args) {try {URL url = new URL("file:/D:/work-space/spring_GROUP/spring-framework-6.2.7/spring-learn/doc/demo.txt");FileUrlResource resource = new FileUrlResource(url);if (resource.exists()) {System.out.println("资源描述: " + resource.getDescription());try (OutputStream os = resource.getOutputStream(); // 默认是覆盖内容PrintWriter writer = new PrintWriter(os)) {writer.println("【当代话梗】芜湖~ 起飞咯");writer.println("【当代话梗】有的兄弟有的");writer.println("【当代话梗】包的包的");writer.println("【当代话梗】那咋了");writer.println("【21世纪名场面】王法官:人不是你撞的,你为什么要扶!");} catch (Exception e) {e.printStackTrace();}} else {System.out.println("资源不存在!");}} catch (MalformedURLException e) {throw new RuntimeException(e);}}
}
输出结果:
三,ContextResource 接口
ContextResource
是 Spring 框架中的一个扩展接口 ,它继承自 Resource
接口,定义在 spring-core
模块中。
它的作用是:为资源提供相对于某个上下文路径(context-relative)的定位能力。
通俗点说,就是这个资源知道它是在哪个“上下文”中加载的,比如它是从类路径下加载的、还是从 Web 应用上下文中加载的等。
在 Resource 接口已经定义读取资源相关信息的标准接口,比如获取文件的数据流,文件是否存在,是否被已被流打开进行读取。
public interface ContextResource extends Resource {/** 获取该资源在上下文路径内的相对路径 */String getPathWithinContext();
}
资源位置 | 上下文路径 |
|
classpath:config/app.properties | classpath: | config/app.properties |
file:/data/config/db.properties | file:/data/ | config/db.properties |
webapp/WEB-INF/web.xml | webapp/WEB-INF/ | web.xml |
🧠 3.1、什么是“上下文”?
“上下文”本质上是指:程序运行时所处的环境或状态信息。
它包含了当前操作所需要的所有相关信息 ,比如:
- 配置信息
- 资源路径
- 当前用户身份
- 应用的状态
- Bean 的定义和实例
- 环境变量
你可以把它想象成一个人说话的“背景环境”,你只有了解了这个背景,才能正确理解他说的话。
📌 3.2、不同场景下的“上下文”
3.2.1 Spring 中的 ApplicationContext
这是最常提到的“上下文”。
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
- 它是 Spring 容器的核心接口。
- 包含了所有 Bean 的定义、配置、环境信息等。
- 提供了获取 Bean、发布事件、加载资源等功能。
📌 所以,当你看到 ContextResource
接口中的方法 getPathWithinContext()
,它的意思是:
这个资源在这个 Spring 容器(ApplicationContext)中的相对路径。
例如:
new ClassPathResource("config/app.properties");
它的 getPathWithinContext()
就是 "config/app.properties"
,表示在类路径上下文中的位置
3.2.2 Web 应用中的上下文(ServletContext)
在 Java Web 开发中(如 Servlet 或 Spring MVC),也有一个叫 ServletContext
的接口。
-
- 它代表整个 Web 应用的上下文。
- 可以用来获取 Web 应用的路径、初始化参数、全局属性等。
举个例子:
ServletContext context = request.getServletContext();
String realPath = context.getRealPath("/WEB-INF/web.xml");
这里的“上下文”就是指整个 Web 应用运行的环境。
3.2.3 线程上下文(ThreadLocal)
有时候我们会说“线程上下文”,指的是每个线程私有的数据存储空间。
ThreadLocal<User> currentUser = new ThreadLocal<>();
currentUser.set(new User("Toast"));
这里的 ThreadLocal
就是一种线程级别的“上下文”,用于保存线程独有的状态信息。
3.2.4 HTTP 请求上下文(Request Context)
在处理 HTTP 请求时,我们也会提到“请求上下文”,它通常包括:
- 请求头(Headers)
- 请求参数(Params)
- 用户会话(Session)
- 当前登录用户(Principal)
这些信息构成了一个完整的请求“上下文”。
🧩 3.3、通俗解释一下“上下文”
你可以把“上下文”理解为:
“你现在在哪里?你是谁,你能访问什么?你知道哪些信息”
就像你在公司上班时,你的工作环境(电脑、网络、权限、同事)就构成了你的“办公上下文”。如果你换到了家里,你的“家庭上下文”就不同了。
✅ 3.4、总结:“上下文”的核心思想
特性 | 说明 |
含义 | 表示程序运行时所依赖的环境信息 |
作用 | 提供运行时所需的数据、配置、状态等 |
示例 |
|
在 Spring 中 | 是容器的核心,负责管理 Bean 和资源 |
四,Resource 接口继承结构分析
4.1 AbstractResource 抽象类
Resource 接口有一个公共抽象类 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 java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.util.function.Supplier;import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;import org.springframework.lang.Nullable;
import org.springframework.util.ResourceUtils;/*** @author Juergen Hoeller* @author Sam Brannen* @since 28.12.2003*/
public abstract class AbstractResource implements Resource {@Overridepublic boolean exists() {// Try file existence: can we find the file in the file system?if (isFile()) {try {return getFile().exists();}catch (IOException ex) {debug(() -> "Could not retrieve File for existence check of " + getDescription(), ex);}}// Fall back to stream existence: can we open the stream?try {getInputStream().close();return true;}catch (Throwable ex) {debug(() -> "Could not retrieve InputStream for existence check of " + getDescription(), ex);return false;}}@Overridepublic boolean isReadable() { return exists(); }@Overridepublic boolean isOpen() { return false; }@Overridepublic boolean isFile() { return false; }@Overridepublic URL getURL() throws IOException {throw new FileNotFoundException(getDescription() + " cannot be resolved to URL");}@Overridepublic URI getURI() throws IOException {URL url = getURL();try {return ResourceUtils.toURI(url);}catch (URISyntaxException ex) {throw new IOException("Invalid URI [" + url + "]", ex);}}@Overridepublic File getFile() throws IOException {throw new FileNotFoundException(getDescription() + " cannot be resolved to absolute file path");}@Overridepublic ReadableByteChannel readableChannel() throws IOException {return Channels.newChannel(getInputStream());}// 忽略其他业务方法
}
4.2 AbstractFileResolvingResource 抽象类
org.springframework.core.io.AbstractFileResolvingResource
是 Spring 框架中用于抽象资源处理 的一个核心类,它位于 spring-core
模块中。
AbstractFileResolvingResource
是一个抽象类,提供了将资源解析为 java.io.File
对象的能力。
🔍 主要作用
✅ 1. 提供统一的 File
解析方法
✅ 2. 判断是否是文件系统资源
✅ 3. 支持文件路径转换
package org.springframework.core.io;import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.JarURLConnection;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.NoSuchFileException;
import java.nio.file.StandardOpenOption;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;import org.springframework.util.ResourceUtils;/*** @author Juergen Hoeller* @since 3.0*/
public abstract class AbstractFileResolvingResource extends AbstractResource {/** 忽略部分方法 */@Overridepublic 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;}}@Overridepublic 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());}@Overrideprotected 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();}}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;}}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());}@Overridepublic ReadableByteChannel readableChannel() throws IOException {try {// Try file system channelreturn FileChannel.open(getFile().toPath(), StandardOpenOption.READ);}catch (FileNotFoundException | NoSuchFileException ex) {// Fall back to InputStream adaptation in superclassreturn super.readableChannel();}}@Overridepublic long contentLength() throws IOException {URL url = getURL();if (ResourceUtils.isFileURL(url)) {// Proceed with file system resolutionFile 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 headerURLConnection con = url.openConnection();customizeConnection(con);if (con instanceof HttpURLConnection httpCon) {httpCon.setRequestMethod("HEAD");}long length = con.getContentLengthLong();if (length <= 0 && con instanceof HttpURLConnection httpCon &&httpCon.getResponseCode() == HttpURLConnection.HTTP_BAD_METHOD) {con = url.openConnection();customizeConnection(con);length = con.getContentLengthLong();}return length;}}@Overridepublic long lastModified() throws IOException {URL url = getURL();boolean fileCheck = false;if (ResourceUtils.isFileURL(url) || ResourceUtils.isJarURL(url)) {// Proceed with file system resolutionfileCheck = 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 headerURLConnection con = url.openConnection();customizeConnection(con);if (con instanceof HttpURLConnection httpCon) {httpCon.setRequestMethod("HEAD");}long lastModified = con.getLastModified();if (lastModified == 0) {if (con instanceof HttpURLConnection httpCon &&httpCon.getResponseCode() == HttpURLConnection.HTTP_BAD_METHOD) {con = url.openConnection();customizeConnection(con);lastModified = con.getLastModified();}if (fileCheck && con.getContentLengthLong() <= 0) {throw new FileNotFoundException(getDescription() +" cannot be resolved in the file system for checking its last-modified timestamp");}}return lastModified;}/** 忽略部分方法 */
}
AbstractResource 和 AbstractFileResolvingResource 都是抽象类,其作用都是承担公共业务流程的实现。AbstractResource 是所有Resource 资源可用的通用抽象类。而 AbstractFileResolvingResource 抽象类是承担着资源转File 统一解析业务流程。其业务颗粒度更细,是为统一解析File 而提供的标准接口。
AbstractFileResolvingResource
子类 | 是否继承 | 特点 |
| ✅ | 表示文件系统中的文件,可完全解析为 |
| ✅ | 使用 Java NIO 的 |
| ✅ | 如果 URL 是 |
| ✅ | 如果资源在文件系统中,也可解析为 |
| ❌ | 内存资源,不能转成 |
五,其他的Resource 资源
提问,资源除了常见文件,网络,以及项目中的resource静态资源基本上都可以转成File 。还有什么类型的资源不是文件的?
当然是有的了,比如业务资源。就spring 框架内部也有许多业务需要为其管理资源,比如说Bean 对象的资源,比如模块资源管理。这些非可转File 资源的业务。其实就用不到 AbstractFileResolvingResource 这个抽象类。而是使用 AbstractResource。
假设说Bean资源来源很多,需要做一个Bean 资源监控业务类。也可以为其业务追加一个公共的抽象类,比如类名:AbstractBeanResourceMonitor
那么想实现Bean资源监控都可以通过这个类进行实现与进一步的处理。当然这是只是假设。Spring里面并没有提供这样的类,AbstractBeanResourceMonitor。