Skip to content

资源抽象 - 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 场景矩阵

场景使用类典型应用频率
配置文件加载ClassPathResourceapplication.yml, logback.xml⭐⭐⭐⭐⭐
模板文件读取ClassPathResourceThymeleaf/Freemarker模板⭐⭐⭐⭐
静态资源访问ServletContextResourceWeb静态资源⭐⭐⭐⭐
文件上传下载FileSystemResource本地文件操作⭐⭐⭐⭐
远程资源获取UrlResourceREST API, 外部配置⭐⭐⭐
批量资源扫描PathMatchingResourcePatternResolverMapper 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资源访问的碎片化问题。基于该体系的封装设计应关注:

  1. 简化使用:提供静态工具方法,减少样板代码
  2. 增强功能:添加缓存、监听、处理链等扩展能力
  3. 保持兼容:与Spring原生API兼容,便于迁移
  4. 异常统一:统一异常处理,提供更友好的错误信息

相关链接

基于 Apache 2.0 许可发布