资源抽象 - Resource 体系
概述
Spring 的 Resource 体系提供了统一的资源访问抽象,支持类路径、文件系统、URL 等多种资源来源。该体系解决了 Java 中资源访问方式不统一的问题,为框架内部和应用程序提供了简洁、一致的资源操作 API。
核心能力矩阵
| 能力 | 核心类 | 解决的问题 | 使用频率 | 学习优先级 |
|---|---|---|---|---|
| 资源抽象 | Resource/ResourceLoader | 资源访问不统一 | ⭐⭐⭐⭐ | 🟡 中 |
1. 代码结构
1.1 核心接口与类层次
1.2 包结构
org.springframework.core.io
├── InputStreamSource.java # 输入流源接口
├── Resource.java # 资源接口(核心)
├── ResourceLoader.java # 资源加载器接口
├── ResourcePatternResolver.java # 资源模式解析器接口
├── AbstractResource.java # 资源抽象基类
├── DefaultResourceLoader.java # 默认资源加载器
├── PathMatchingResourcePatternResolver.java # 路径匹配解析器
├── ClassPathResource.java # 类路径资源实现
├── FileSystemResource.java # 文件系统资源实现
├── UrlResource.java # URL资源实现
├── ByteArrayResource.java # 字节数组资源实现
├── VfsResource.java # JBoss VFS资源实现
├── ContextResource.java # 上下文资源接口(Web)
└── support/ # 支持类
├── ResourcePatternUtils.java # 资源模式工具
├── ResourceArrayPropertyEditor.java # 属性编辑器
└── PropertiesLoaderUtils.java # 属性加载工具1.3 核心接口详解
Resource 接口
java
public interface Resource extends InputStreamSource {
// 存在性检查
boolean exists();
// 可读性检查
default boolean isReadable() { return exists(); }
// 是否已打开(某些资源只能读取一次)
default boolean isOpen() { return false; }
// 是否为文件系统资源
default boolean isFile() { return false; }
// 获取URL
URL getURL() throws IOException;
// 获取URI
URI getURI() throws IOException;
// 获取File对象
File getFile() throws IOException;
// 获取资源内容长度
long contentLength() throws IOException;
// 获取最后修改时间
long lastModified() throws IOException;
// 创建相对路径资源
Resource createRelative(String relativePath) throws IOException;
// 获取文件名
String getFilename();
// 获取描述信息
String getDescription();
}ResourceLoader 接口
java
public interface ResourceLoader {
// 类路径URL前缀
String CLASSPATH_URL_PREFIX = "classpath:";
// 根据位置加载资源
Resource getResource(String location);
// 获取类加载器
ClassLoader getClassLoader();
}ResourcePatternResolver 接口
java
public interface ResourcePatternResolver extends ResourceLoader {
// 所有类路径URL前缀(包括jar包)
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
// 根据模式加载多个资源
Resource[] getResources(String locationPattern) throws IOException;
}2. 主要使用场景
2.1 场景矩阵
| 场景 | 使用类 | 典型应用 | 频率 |
|---|---|---|---|
| 配置文件加载 | ClassPathResource | application.yml, logback.xml | ⭐⭐⭐⭐⭐ |
| 模板文件读取 | ClassPathResource | Thymeleaf/Freemarker模板 | ⭐⭐⭐⭐ |
| 静态资源访问 | ServletContextResource | Web静态资源 | ⭐⭐⭐⭐ |
| 文件上传下载 | FileSystemResource | 本地文件操作 | ⭐⭐⭐⭐ |
| 远程资源获取 | UrlResource | REST API, 外部配置 | ⭐⭐⭐ |
| 批量资源扫描 | PathMatchingResourcePatternResolver | Mapper XML扫描 | ⭐⭐⭐⭐ |
| 内存数据处理 | ByteArrayResource | 动态生成内容 | ⭐⭐⭐ |
2.2 场景详解
场景1:配置文件加载(最常用)
java
// 传统方式的问题:路径处理复杂
InputStream is = getClass().getClassLoader()
.getResourceAsStream("application.yml");
// Spring Resource方式:简洁统一
Resource resource = new ClassPathResource("application.yml");
try (InputStream is = resource.getInputStream()) {
// 读取配置
}
// 或使用ResourceLoader
ResourceLoader loader = new DefaultResourceLoader();
Resource resource = loader.getResource("classpath:application.yml");核心价值:屏蔽类加载器差异,统一访问方式
场景2:批量资源扫描
java
// 扫描所有Mapper XML文件
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources("classpath*:mapper/**/*Mapper.xml");
for (Resource resource : resources) {
System.out.println("找到Mapper: " + resource.getFilename());
}核心价值:支持Ant风格通配符,自动扫描jar包内资源
场景3:多环境资源适配
java
// 开发环境:文件系统
Resource devResource = new FileSystemResource("/home/config/app.yml");
// 生产环境:类路径
Resource prodResource = new ClassPathResource("config/app.yml");
// 统一处理逻辑
private void loadConfig(Resource resource) {
if (resource.exists()) {
// 加载配置(与资源类型无关)
}
}核心价值:同一套代码适配不同部署环境
3. 围绕主要场景的封装设计
3.1 封装目标
基于Spring Resource体系,封装一个更简洁、更易用的资源操作工具 - LinsirResource,主要解决以下痛点:
| 痛点 | 解决方案 |
|---|---|
| 需要手动管理ResourceLoader实例 | 提供静态工具方法 |
| 读取资源内容代码冗长 | 提供一键读取方法 |
| 异常处理繁琐 | 统一包装为运行时异常 |
| 缺乏资源监听能力 | 添加资源变化监听 |
| 没有资源缓存机制 | 添加热点资源缓存 |
3.2 封装架构
3.3 核心API设计
java
/**
* LinsirResource - 资源操作工具类
*
* <p>基于Spring Resource体系封装,提供更简洁的资源操作API</p>
*/
public final class LinsirResource {
// ========== 资源加载 ==========
/**
* 加载单个资源
*
* @param location 资源位置(支持classpath:, file:, http://等前缀)
* @return Resource对象
*/
public static Resource load(String location);
/**
* 批量加载资源(支持通配符)
*
* @param pattern 资源模式(如 classpath*:mapper/**/*.xml)
* @return Resource数组
*/
public static Resource[] loadAll(String pattern);
/**
* 从类路径加载资源
*/
public static Resource loadFromClasspath(String path);
/**
* 从文件系统加载资源
*/
public static Resource loadFromFileSystem(String path);
// ========== 内容读取 ==========
/**
* 读取资源内容为字符串
*
* @param location 资源位置
* @return 资源内容
* @throws ResourceNotFoundException 资源不存在时抛出
*/
public static String readAsString(String location);
/**
* 读取资源内容为字节数组
*/
public static byte[] readAsBytes(String location);
/**
* 读取资源内容为Properties
*/
public static Properties readAsProperties(String location);
/**
* 读取资源内容为JSON对象
*/
public static <T> T readAsJson(String location, Class<T> type);
// ========== 资源检查 ==========
/**
* 检查资源是否存在
*/
public static boolean exists(String location);
/**
* 获取资源最后修改时间
*/
public static long lastModified(String location);
/**
* 获取资源文件大小
*/
public static long size(String location);
// ========== 资源监听 ==========
/**
* 监听资源变化
*
* @param location 资源位置
* @param listener 变化监听器
*/
public static void watch(String location, ResourceChangeListener listener);
/**
* 停止监听
*/
public static void unwatch(String location);
// ========== 资源缓存 ==========
/**
* 启用资源缓存
*/
public static void enableCache();
/**
* 清除资源缓存
*/
public static void clearCache();
/**
* 获取缓存统计
*/
public static CacheStats getCacheStats();
}3.4 使用示例
java
// ===== 示例1:加载配置文件 =====
String config = LinsirResource.readAsString("classpath:application.yml");
// ===== 示例2:批量扫描Mapper文件 =====
Resource[] mappers = LinsirResource.loadAll("classpath*:mapper/**/*Mapper.xml");
// ===== 示例3:读取JSON配置 =====
AppConfig config = LinsirResource.readAsJson("classpath:config.json", AppConfig.class);
// ===== 示例4:监听配置文件变化 =====
LinsirResource.watch("file:/etc/app/config.yml", event -> {
System.out.println("配置已更新,重新加载...");
reloadConfig();
});
// ===== 示例5:检查资源存在性 =====
if (LinsirResource.exists("classpath:banner.txt")) {
String banner = LinsirResource.readAsString("classpath:banner.txt");
System.out.println(banner);
}4. 扩展模块分析
4.1 扩展点1:自定义资源协议
Spring Resource支持自定义协议处理器,可以扩展新的资源类型:
java
/**
* 自定义协议示例:oss:// 阿里云OSS资源
*/
public class OssResource extends AbstractResource {
private final String bucketName;
private final String objectKey;
private final OSSClient ossClient;
public OssResource(String location) {
// 解析 oss://bucket-name/object-key 格式
String[] parts = location.substring(6).split("/", 2);
this.bucketName = parts[0];
this.objectKey = parts[1];
this.ossClient = OssClientFactory.getClient();
}
@Override
public InputStream getInputStream() throws IOException {
return ossClient.getObject(bucketName, objectKey).getObjectContent();
}
@Override
public boolean exists() {
return ossClient.doesObjectExist(bucketName, objectKey);
}
// ... 其他方法实现
}
/**
* 自定义ResourceLoader
*/
public class OssResourceLoader implements ResourceLoader {
@Override
public Resource getResource(String location) {
if (location.startsWith("oss:")) {
return new OssResource(location);
}
// 委托给默认加载器
return new DefaultResourceLoader().getResource(location);
}
@Override
public ClassLoader getClassLoader() {
return Thread.currentThread().getContextClassLoader();
}
}应用场景:
- 云存储资源(OSS、S3、COS)
- 数据库BLOB资源
- 分布式文件系统(HDFS、FastDFS)
- 配置中心资源(Nacos、Apollo)
4.2 扩展点2:资源变化监听
实现资源热更新机制:
java
/**
* 资源变化监听器
*/
public interface ResourceWatcher {
/**
* 开始监听资源
*/
void watch(String location, ResourceChangeListener listener);
/**
* 停止监听
*/
void unwatch(String location);
}
/**
* 文件系统资源监听器(基于WatchService)
*/
public class FileSystemResourceWatcher implements ResourceWatcher {
private final WatchService watchService;
private final Map<String, ResourceChangeListener> listeners = new ConcurrentHashMap<>();
public FileSystemResourceWatcher() throws IOException {
this.watchService = FileSystems.getDefault().newWatchService();
startWatching();
}
@Override
public void watch(String location, ResourceChangeListener listener) {
try {
Path path = Paths.get(location).getParent();
path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
listeners.put(location, listener);
} catch (IOException e) {
throw new ResourceException("Failed to watch resource: " + location, e);
}
}
private void startWatching() {
Thread watchThread = new Thread(() -> {
while (!Thread.interrupted()) {
WatchKey key = watchService.take();
for (WatchEvent<?> event : key.pollEvents()) {
Path changed = (Path) event.context();
notifyListeners(changed.toString());
}
key.reset();
}
});
watchThread.setDaemon(true);
watchThread.start();
}
}应用场景:
- 配置文件热更新
- 模板文件热加载
- 证书文件更新监控
- 静态资源缓存刷新
4.3 扩展点3:资源缓存策略
针对热点资源提供缓存机制:
java
/**
* 资源缓存接口
*/
public interface ResourceCache {
/**
* 获取缓存的资源内容
*/
byte[] get(String location);
/**
* 缓存资源内容
*/
void put(String location, byte[] content);
/**
* 使缓存失效
*/
void evict(String location);
/**
* 清除所有缓存
*/
void clear();
}
/**
* 基于Caffeine的高性能缓存实现
*/
public class CaffeineResourceCache implements ResourceCache {
private final Cache<String, byte[]> cache;
public CaffeineResourceCache() {
this.cache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(Duration.ofMinutes(10))
.build();
}
@Override
public byte[] get(String location) {
return cache.getIfPresent(location);
}
@Override
public void put(String location, byte[] content) {
cache.put(location, content);
}
// ... 其他方法
}应用场景:
- 频繁读取的配置文件
- 模板文件缓存
- 静态资源缓存
- 远程资源本地缓存
4.4 扩展点4:资源内容处理链
实现资源内容的预处理链:
java
/**
* 资源处理器接口
*/
public interface ResourceProcessor {
/**
* 处理资源内容
*
* @param content 原始内容
* @param location 资源位置
* @return 处理后的内容
*/
byte[] process(byte[] content, String location);
/**
* 是否支持该资源
*/
boolean supports(String location);
}
/**
* 资源处理链
*/
public class ResourceProcessorChain {
private final List<ResourceProcessor> processors = new ArrayList<>();
public void addProcessor(ResourceProcessor processor) {
processors.add(processor);
}
public byte[] process(byte[] content, String location) {
byte[] result = content;
for (ResourceProcessor processor : processors) {
if (processor.supports(location)) {
result = processor.process(result, location);
}
}
return result;
}
}
/**
* 解密处理器示例
*/
public class DecryptResourceProcessor implements ResourceProcessor {
private final Cipher cipher;
public DecryptResourceProcessor(SecretKey key) {
this.cipher = Cipher.getInstance("AES");
this.cipher.init(Cipher.DECRYPT_MODE, key);
}
@Override
public byte[] process(byte[] content, String location) {
if (location.endsWith(".enc")) {
return cipher.doFinal(content);
}
return content;
}
@Override
public boolean supports(String location) {
return location.endsWith(".enc");
}
}应用场景:
- 配置文件加密解密
- 模板文件预处理
- 资源压缩解压
- 资源格式转换
5. 总结
Spring Resource体系通过统一的抽象接口,解决了Java资源访问的碎片化问题。基于该体系的封装设计应关注:
- 简化使用:提供静态工具方法,减少样板代码
- 增强功能:添加缓存、监听、处理链等扩展能力
- 保持兼容:与Spring原生API兼容,便于迁移
- 异常统一:统一异常处理,提供更友好的错误信息