Skip to content

Spring教程 - 14 资源操作

在开发的时候,我们经常需要处理各种资源,比如配置文件、图片、模板文件等。Spring 提供了一个强大的资源抽象层,让我们能够以统一的方式访问这些资源,而不用关心它们的具体存储位置。

想象一下,你的应用可能需要从以下地方加载资源:

  • 类路径下(classpath)
  • 文件系统中
  • 网络URL
  • JAR包内部

Spring 的 Resource 接口和 ResourceLoader 接口就是为了解决这些问题而生的。它们提供了统一的API来访问这些不同类型的资源。


14.1 Resource 接口

Resource 是 Spring 资源访问的核心接口,它定义了访问资源的基本方法。你可以把它想象成一个更强大的 java.io.File 类,它不仅支持文件系统,还支持类路径、URL等多种资源类型。

Resource 接口定义如下:

java
public interface Resource extends InputStreamSource {
    // 资源是否存在
    boolean exists();
    
    // 资源是否可读
    boolean isReadable();
    
    // 资源是否已打开
    boolean isOpen();
    
    // 是否代表文件系统中的一个文件
    boolean isFile();
    
    // 获取资源的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();
}

Spring 针对不同的资源类型提供了多种 Resource 的实现,常见的实现类如下:

  • ClassPathResource:获取 classpath 下的资源(jar 内也可,但不能 getFile())。
  • FileSystemResource:获取磁盘文件系统中的资源,指定绝对或相对路径。
  • UrlResource:获取 http/ftp/file:// 前缀指定的URL资源。
  • ServletContextResource:获取 web 应用(/WEB-INF/...)下的资源。
  • ByteArrayResource:内存字节数组资源(可重复读取)。
  • InputStreamResource:包装单次可读的 InputStream(注意:通常不可重复读取)。
  • WritableResource:可写资源接口(如某些文件资源实现)。

下面简单演示一下 ClassPathResourceFileSystemResourceUrlResource 的使用。


1 ClassPathResource

使用 ClassPathResource 可以很方便的访问类路径下的资源。

举个栗子:

在项目的 resources 目录下创建 config 文件夹,然后创建一个 test.properties ,并输入内容如下:

properties
web.domain=www.foooor.com

然后创建一个测试类,测试一下读取文件中的内容:

java
package com.foooor.hellospring;

import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public class ResourceTest {

    @Test
    public void testClassPathResource() {
        // 加载类路径下的资源,例如 resources下config目录下的application.properties文件。
        Resource resource = new ClassPathResource("config/test.properties");

        try (InputStream is = resource.getInputStream()) {
            // 读取资源内容
            Properties props = new Properties();
            // 加载配置文件中的属性
            props.load(is);
            System.out.println("web.domain: " + props.getProperty("web.domain"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 在上面的代码中使用 Properties 读取配置文件中的配置。
  • 当然你也可以使用 InputStream 直接读取文件中的内容。

2 FileSystemResource

使用 FileSystemResource 可以访问文件系统资源。

java
@Test
public void testFileSystemResource() {
    // 加载文件系统中的资源
    Resource resource = new FileSystemResource("/path/to/your/file.txt");

    // 检查资源是否存在
    if (resource.exists()) {
        try {
            // 获取文件对象
            File file = resource.getFile();
            System.out.println("文件大小: " + file.length() + " 字节");

            // 读取文件内容
            String content = Files.readString(file.toPath());
            System.out.println("文件内容: " + content);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 通过指定路径,可以读取指定的文件,你可以指定绝对路径,例如 C:\file.txt

但是其实使用 Java 提供的 File 也没有太大区别,只能说使用 Resource 可以统一编程方式,如果要切换使用不同方式的资源,比较方便一些。

3 UrlResource

使用 UrlResource 可以根据提供的 URL 访问网络资源。

java
@Test
public void testUrlResource() throws Exception {
    // 加载网络资源
    Resource resource = new UrlResource("https://www.foooor.com");

    try (InputStream is = resource.getInputStream();
         BufferedReader reader = new BufferedReader(new InputStreamReader(is))) {

        // 读取JSON数据
        StringBuilder data = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            data.append(line);
        }

        System.out.println("获取到数据: " + data);
    } catch (Exception e) {
        System.err.println("加载网络资源失败: " + e.getMessage());
    }
}

14.2 ResourceLoader

在前面的例子中,我们直接使用了 ClassPathResourceFileSystemResource 等具体实现类来加载资源。这种方式虽然简单直接,但存在一些问题:

  1. 代码耦合:直接使用具体实现类会导致代码与特定资源类型耦合
  2. 不够灵活:如果需要切换资源类型(比如从类路径切换到文件系统),需要修改代码
  3. 不利于测试:难以在测试时替换为模拟的资源

ResourceLoader 是 Spring 提供的资源加载策略接口,它的主要作用是解耦资源加载逻辑。通过 ResourceLoader,我们可以使用统一的方式来加载不同类型的资源。

1 使用ResourceLoader

在项目中,我们可以直接注入 ResourceLoader

举个栗子:

java
package com.foooor.hellospring;

import com.foooor.hellospring.config.SpringConfig;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;

@SpringJUnitConfig(SpringConfig.class)  // 指定 Spring 配置类
public class ResourceTest {

    @Autowired
    private ResourceLoader resourceLoader;

     @Test
    public void testResourceLoader() {
        // 获取类路径下的 bean.xml 文件
        Resource resource = resourceLoader.getResource("classpath:bean.xml");
        System.out.println("----" + resource.getFilename());
    }
}
  • 可以直接在 Spring 管理的 bean 中注入 ResourceLoader 并使用。

这样就可以加载不同类型的 Resource,ResourceLoader 根据资源路径的前缀决定使用哪种 Resource 实现:

  • classpath::使用 ClassPathResource 加载类路径资源,例如:classpath:bean.xml
  • file::使用 FileSystemResource 加载文件系统资源,例如:file:/path/to/file.txt
  • http://https://:使用 UrlResource 加载网络资源,例如:https://www.foooor.com
  • 无前缀:由具体实现类决定,通常是 ClassPathResource

这样获取不同类型的文件,都是使用相同的方式,ResourceLoader 会负责选择 Resource 的实现类,确定具体的资源访问策略。

2 实现ResourceLoaderAware

在现代 Spring 应用中,通常更推荐使用依赖注入 ResourceLoader@Autowired),也就是上面的方式。在早期的版本中,是通过 ResourceLoaderAware 进行注入的,我们需要让我们的类实现 ResourceLoaderAware 接口,并实现 setResourceLoader 方法,Spring 会通过该方法进行注入。我们也可以通过这种方式进行 ResourceLoader 的注入和使用。

举个栗子:

java
@Component
public class MyBean implements ResourceLoaderAware {
    
    private ResourceLoader resourceLoader;
    
    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }
    
    public void testMethod() throws Exception {
        // 使用 resourceLoader 加载资源
        Resource resource = resourceLoader.getResource("classpath:config/settings.properties");
        // 处理资源...
    }
}

3 ApplicationContext

在 Spring 中,ApplicationContext 间接实现了 ResourceLoader 接口,这意味着任何 ApplicationContext实现类都可以作为 ResourceLoader 使用。

举个栗子,写一个测试类:

java
package com.foooor.hellospring;

import com.foooor.hellospring.config.SpringConfig;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;

@SpringJUnitConfig(SpringConfig.class)  // 指定 Spring 配置类
public class ResourceTest {

    @Autowired
    private ApplicationContext applicationContext;

    @Test
    public void testMethod() throws Exception {
        // 获取类路径下,指定目录下的文件
        Resource resource = resourceLoader.getResource("classpath:config/settings.properties");
        if (resource.exists()) {
            // 处理资源...
        } else {
            System.out.println("资源不存在");
        }
    }
}
  • 首先我们可以直接在类中注入 ApplicationContext
  • 然后通过 ApplicationContext 就可以获取 Resource

4 classpath使用通配符

Spring 还支持使用 Ant 风格的路径模式来匹配多个资源:

java
// 加载所有类路径下所有以 .properties 结尾的文件,得到一个数组
Resource[] resources = context.getResources("classpath*:*.properties");

// 加载类路径下 config 包及其子包中的所有 .xml 文件
Resource[] xmlResources = context.getResources("classpath*:config/**/*.xml");
  • 当使用 classpath*: 开头,表示将搜索所有类路径下满足条件的文件;如果使用 classpath: 开头,只会加载第一个符合条件的文件。
  • 但是需要注意,只有使用 ApplicationContext 获取 Resource 的时候才有效,因为 ApplicationContext 继承自 ResourcePatternResolver
内容未完......