Skip to content

资源操作Resources

1. Spring Resources概述

Java中java.net.URL类常用于进行资源操作,但是存在访问底层资源时局限性。比如:URL的实现不支持用于Web项目中访问需要从类路径或在ServletContext上下文中获取的资源。并且缺少某些Spring所需要的功能,例如检测某资源是否存在等。
Spring框架使用Resource接口封装了底层资源访问,实现统一的方式处理不同类型的资源。同时Resource接口为底层资源的访问提供了更强大的能力。它不仅可以处理来自类路径的资源和相对于服务器上下文的资源,还包含了一些额外的实用功能,如资源存在性的检测。因此,对于需要进行细致的资源管理和访问的情况,选择Spring的Resource接口通常是更优的选择。这种设计不仅增强了资源访问的灵活性,也提升了开发效率和代码的可维护性。

2. Resource接口

Spring的Resource接口位于org.springframework.core.io中。旨在成为一个更强大的接口,用于抽象对低级资源的访问。以下显示了Resource接口定义的方法:

java
public interface Resource extends InputStreamSource {
 
    boolean exists();

    boolean isReadable();

    boolean isOpen();

    boolean isFile();

    URL getURL() throws IOException;

    URI getURI() throws IOException;

    File getFile() throws IOException;

    ReadableByteChannel readableChannel() throws IOException;

    long contentLength() throws IOException;

    long lastModified() throws IOException;

    Resource createRelative(String relativePath) throws IOException;

    String getFilename();

    String getDescription();
}

Resource接口继承了InputStreamSource接口,InputStreamSource接口,只有一个方法:

java
public interface InputStreamSource {

    InputStream getInputStream() throws IOException;
}

接口方法说明:

  • getInputStream(): 找到并打开资源,返回一个InputStream以从资源中读取。预计每次调用都会返回一个新的- InputStream(),调用者有责任关闭每个流
  • exists(): 返回一个布尔值,表明某个资源是否以物理形式存在
  • isOpen: 返回一个布尔值,指示此资源是否具有开放流的句柄。如果为true,InputStream就不能够多次读取,只能够读取一次并且及时关闭以避免内存泄漏。对于所有常规资源实现,返回false,但是InputStreamResource除外。
  • getDescription(): 返回资源的描述,用来输出错误的日志。这通常是完全限定的文件名或资源的实际URL。
  • isReadable(): 表明资源的目录读取是否通过getInputStream()进行读取。
  • isFile(): 表明这个资源是否代表了一个文件系统的文件。
  • getURL(): 返回一个URL句柄,如果资源不能够被解析为URL,将抛出IOException
  • getURI(): 返回一个资源的URI句柄
  • getFile(): 返回某个文件,如果资源不能够被解析称为绝对路径,将会抛出FileNotFoundException
  • lastModified(): 资源最后一次修改的时间戳
  • createRelative(): 创建此资源的相关资源
  • getFilename(): 资源的文件名是什么 例如:最后一部分的文件名 myfile.txt

3. Resource的实现类

Resource接口是Spring资源访问策略的抽象,它本身并不提供任何资源访问实现,具体的资源访问由该接口的实现类完成——每个实现类代表一种资源访问策略。Resource一般包括这些实现类:UrlResource、ClassPathResource、FileSystemResource、ServletContextResource、InputStreamResource、ByteArrayResource。
Resource实现类与Resource顶级接口之间的关系可以用下面的UML关系模型来表示:
Alt text

3.1 UrlResource访问网络资源

Resource的一个实现类,用来访问网络资源,它支持URL的绝对路径。

  • http: 该前缀用于访问基于HTTP协议的网络资源。
  • ftp:该前缀用于访问基于FTP协议的网络资源。
  • file: 该前缀用于从文件系统中读取资源。
  1. 访问基于HTTP协议的网络资源
    创建子模块spring6-resources
    创建UrlResourceDemo类
java
import org.springframework.core.io.UrlResource;

public class UrlResourceDemo {

    public static void loadAndReadUrlResource(String path){
        // 创建一个 Resource 对象
        UrlResource url = null;
        try {
            url = new UrlResource(path);
            // 获取资源名
            System.out.println(url.getFilename());
            System.out.println(url.getURI());
            // 获取资源描述
            System.out.println(url.getDescription());
            //获取资源内容
            InputStream inputStream = url.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
            Stream<String> lines = reader.lines();
            System.out.println(lines.collect(Collectors.joining("\r\n")));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    public static void main(String[] args) {
        //访问网络资源
        loadAndReadUrlResource("http://www.baidu.com");
    }
}
  1. 在项目根路径下创建文件,从文件系统中读取资源
    修改调用传递路径从网络地址改为文件路径:
java
public static void main(String[] args) {
    // 访问文件系统资源
    loadAndReadUrlResource("file:data.txt");
}

3.2 ClassPathResource访问类路径下资源

ClassPathResource用来访问类加载路径下的资源,相对于其他的Resource实现类,其主要优势是方便访问类加载路径里的资源,尤其对于Web应用,ClassPathResource可自动搜索位于classes下的资源文件,无须使用绝对路径访问。

  1. 在类路径下创建文件beansData.txt,使用ClassPathResource访问: Alt text
  2. 创建ClassPathResourceDemo.java:
java
public class ClassPathResourceDemo {

    public static void loadAndReadUrlResource(String path) throws Exception{
        // 创建一个 Resource 对象
        ClassPathResource resource = new ClassPathResource(path);
        // 获取文件名
        System.out.println("resource.getFileName = " + resource.getFilename());
        // 获取文件描述
        System.out.println("resource.getDescription = "+ resource.getDescription());
        //获取文件内容
        InputStream in = resource.getInputStream();
        byte[] b = new byte[1024];
        while(in.read(b)!=-1) {
            System.out.println(new String(b));
        }
    }

    public static void main(String[] args) throws Exception {
        loadAndReadUrlResource("beansData.txt");
    }
}

ClassPathResource实例可通过构造方法直接创建,但更多的时候它都是隐式地创建的。当执行Spring的某个方法时,该方法接受一个代表资源路径的字符串参数,当Spring识别该字符串参数中包含classpath:前缀后,系统会自动创建ClassPathResource对象。

3.3 FileSystemResource访问文件系统资源

Spring提供的FileSystemResource类用于访问文件系统资源,使用FileSystemResource来访问文件系统资源并没有太大的优势,因为Java提供的File类也可用于访问文件系统资源。
创建FileSystemResourceDemo类,访问文件目录内容:

java
public class FileSystemResourceDemo {

    public static void loadAndReadUrlResource(String path) throws Exception{
        FileSystemResource resource = new FileSystemResource(path);
        // 获取文件名
        System.out.println("resource.getFileName = " + resource.getFilename());
        // 获取文件描述
        System.out.println("resource.getDescription = "+ resource.getDescription());
        //获取文件内容
        InputStream in = resource.getInputStream();
        byte[] b = new byte[1024];
        while(in.read(b)!=-1) {
            System.out.println(new String(b));
        }
    }

    public static void main(String[] args) throws Exception {
        //支持绝对路径,相对路径
        loadAndReadUrlResource("data.txt");
    }
}

FileSystemResource实例可使用FileSystemResource构造器显示地创建,但更多的时候它都是隐式创建。执行Spring的某个方法时,该方法接受一个代表资源路径的字符串参数,当Spring识别该字符串参数中包含file:前缀后,系统将会自动创建FileSystemResource对象。

3.4 ServletContextResource

这是ServletContext资源的Resource实现,它解释相关Web应用程序根目录中的相对路径。它始终支持流(stream)访问和URL访问,但只有在扩展Web应用程序存档且资源实际位于文件系统上时才允许java.io.File访问。无论它是在文件系统上扩展还是直接从JAR或其他地方(如数据库)访问,实际上都依赖于Servlet容器。

3.5 InputStreamResource

InputStreamResource 是给定的输入流(InputStream)的Resource实现。它的使用场景在没有特定的资源实现的时候使用(感觉和@Component 的适用场景很相似)。与其他Resource实现相比,这是已打开资源的描述符。因此,它的isOpen()方法返回true。如果需要将资源描述符保留在某处或者需要多次读取流,请不要使用它。

3.6 ByteArrayResource

字节数组的Resource实现类。通过给定的数组创建了一个ByteArrayInputStream。它对于从任何给定的字节数组加载内容非常有用,而无需求助于单次使用的InputStreamResource。

4. Resource资源加载接口

Spring提供如下两个重要的接口来处理资源加载:

  1. ResourceLoader:该接口实现类的实例可以获得一个Resource实例。它是用于加载不同类型资源的抽象接口,提供了一种统一的方式来访问各种资源,如文件、类路径资源、URL等。通过ResourceLoader,可以轻松地获取和操作应用程序中的资源。
  2. ResourceLoaderAware接口:该接口实现类的实例将获得对ResourceLoader的引用。然后可以通过ResourceLoader来访问资源。这对于将资源加载能力注入到特定的类中非常有用,使它们能够以一种更灵活的方式处理资源。

4.1 ResourceLoader接口

ResourceLoader接口的源码如下:

java
public interface ResourceLoader {

	Resource getResource(String location);

	ClassLoader getClassLoader();
}

可以看到ResourceLoader接口里有方法getResource(): 用于返回一个Resource实例。实际上ApplicationContext实现类都实现了ResourceLoader接口,并且ApplicationContext通过getResource直接获取Resource实例。

4.2 获取Resource实例例子

如果要通过ResourceLoader获取资源,按照上文描述可以直接使用ApplicationContext达到目的,下面按照ApplicationContext实现类ClassPathXmlApplicationContext\FileSystemXmlApplicationContext获取Resource实例举例说明:

java
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.Resource;

public class Demo1 {
    public static void main(String[] args) {
        /**
         * 通过ApplicationContext访问资源
         * 默认采用与ApplicationContext具体子类实现的资源访问策略
         */
        ApplicationContext ctx = new ClassPathXmlApplicationContext();
        Resource res = ctx.getResource("beansData.txt");
        System.out.println(res.getClass()+": "+res);

        ApplicationContext ctx2 = new FileSystemXmlApplicationContext();
        Resource res2 = ctx2.getResource("demo.txt");
        System.out.println(res2.getClass()+": "+res2);
    }
}

ResourceLoader总结

ApplicationContext访问加载资源进行实例化和资源的关系如下表:

Spring上下文对象Resource实现类
ClassPathXMLApplicationContextClassPathResource
FileSystemXmlApplicationContextFileSystemResource
XmlWebApplicationContextServletContextResource

当Spring上下文需要进行资源访问时,没有直接实现Resource接口也没有直接使用Resource实现类,而是通过调用ResourceLoader实例的getResource()方法的方式来获得资源,ReosurceLoader将会负责通过具体访问资源的策略选择Reosurce实现类,从而将应用上下文和具体的资源访问细节分离开来。

4.3 ResourceLoaderAware接口

ResourceLoaderAware接口的源码如下:

java
public interface ResourceLoaderAware extends Aware {

	void setResourceLoader(ResourceLoader resourceLoader);
}

实现了该接口实例将获得对ResourceLoader的引用。然后通过ResourceLoader来访问资源,这对于需要资源加载能力的类中非常有用,使它们能够以一种更灵活的方式处理资源。
ResourceLoaderAware中的setResourceLoader()方法,该方法由Spring容器负责调用,Spring容器会将ResourceLoader对象作为该方法的参数传入。又因为ApplicationContext的实现类都实现了ResourceLoader接口,Spring容器自身完全可作为参数传入作为ResorceLoader使用。

4.4 ResourceLoaderAware的使用

  1. 创建类,实现ResourceLoaderAware接口
java
public class MyResourceLoaderAware implements ResourceLoaderAware {
    private ResourceLoader resourceLoader;

    // 实现ResourceLoaderAware接口必须实现的方法
    // 如果把该Bean部署在Spring容器中,该方法将会有Spring容器负责调用。
    // Spring容器调用该方法时,Spring会将自身作为参数传给该方法。
    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }
    // 返回ResourceLoader对象的应用
    public ResourceLoader getResourceLoader(){
        return this.resourceLoader;
    }
}
  1. 创建beans.xml文件,配置MyResourceLoaderAware类:
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="myResourceLoaderAware" class="com.rocket.spring.resource.MyResourceLoaderAware"></bean>
</beans>
  1. 测试代码:
java
public class Demo3 {

    public static void main(String[] args) {
        //Spring容器会将一个ResourceLoader对象作为该方法的参数传入
        ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
        MyResourceLoaderAware testBean = ctx.getBean("myResourceLoaderAware", MyResourceLoaderAware.class);
        //获取ResourceLoader对象
        ResourceLoader resourceLoader = testBean.getResourceLoader();
        System.out.println("Spring容器将自身注入到ResourceLoaderAware的Bean中:" + (resourceLoader == ctx));
        //加载其他资源
        Resource resource = resourceLoader.getResource("beansData.txt");
        System.out.println(resource.getFilename());
        System.out.println(resource.getDescription());
    }
}

运行结果:
Alt text

5. 使用依赖注入获取Resource实例

Spring框架充分利用了策略模式来简化资源访问, 比如使用ApplicationContext来获取资源,要不自行使用具体Resource实现类。Spring额外支持通过依赖注入简化对Spring资源访问。通过依赖注入,Spring可以将资源注入到Bean实例中,而不需要在代码中硬编码资源的位置。这种方式使得代码更加松耦合,更容易维护和测试。无论资源的位置如何变化,只需要调整配置而不是修改代码即可。

5.1 让Spring为Bean实例依赖注入资源

  1. 创建依赖注入类,定义属性和方法
java
import org.springframework.core.io.Resource;

public class ResourceBean {
    
    private Resource res;
    
    public void setRes(Resource res) {
        this.res = res;
    }
    public Resource getRes() {
        return res;
    }
    
    public void parse(){
        System.out.println(res.getFilename()+ "获取资源后进行处理逻辑");
        System.out.println(res.getDescription());
    }
}
  1. 创建spring配置文件,配置依赖注入:
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="resourceBean" class="com.rocket.spring.resouceloader.ResourceBean" >
      <!-- 可以使用file:、http:、ftp:等前缀强制Spring采用对应的资源访问策略 -->
      <!-- 如果不采用任何前缀,则Spring将采用与该ApplicationContext相同的资源访问策略来访问资源 -->
        <property name="res" value="classpath:data.txt"/>
    </bean>
</beans>
  1. 代码测试:
java
public class Demo4 {

    public static void main(String[] args) {
        ApplicationContext ctx =
                new ClassPathXmlApplicationContext("beans.xml");
        ResourceBean resourceBean = ctx.getBean("resourceBean",ResourceBean.class);
        resourceBean.parse();
    }
}

运行结果:
Alt text

6. 资源路径和解析

Spring上下文实例初始化都需要指定配置文件路径,可以通过使用前缀指定访问策略,比如:

java
// ClassPathResource
Resource res = ctx.getResource("classpath:beans.xml");
// FileSystemResource
Resource res = ctx.getResource("file:beans.xml");
// UrlResource
Resource res = ctx.getResource("http://localhost:8080/beans.xml");

6.1 前缀和通配符使用

路径通配符仅对ApplicationContext有效

  • classpath* : 前缀允许加载多个XML配置文件的能力。当使用classpath*:前缀指定XML配置文件时,系统将搜索类加载路径下所有与文件名匹配的文件,并分别加载这些文件中的配置内容,最后将它们合并到ApplicationContext中。
  • beans*.xml: 通过通配符来指定一次性加载多个配置文件