spring 之资源操作:Resources

Java
315
0
0
2024-03-11
标签   Spring

1、Spring Resources概述

在 Java 编程中,java.net.URL 类常用于进行资源操作。然而,这个类在访问某些底层资源时存在局限性。例如,它不能直接从类路径中获取资源,或者在 Web 项目中无法方便地访问相对于服务器上下文的资源。此外,java.net.URL 在功能方面也有所欠缺,比如无法检测某个资源是否存在。

针对这些限制,Spring 框架提供了 Resource 接口。Resource 接口为底层资源的访问提供了更强大的能力。它不仅可以处理来自类路径的资源和相对于服务器上下文的资源,还包含了一些额外的实用功能,如资源存在性的检测。因此,对于需要进行细致的资源管理和访问的情况,选择 Spring 的 Resource 接口通常是更优的选择。这种设计不仅增强了资源访问的灵活性,也提升了开发效率和代码的可维护性。

Spring Resources概述

2、Resource接口

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

public interface Resource extends InputStreamSource {

    /**
     * 判断资源是否物理存在。
     * 这个方法进行确切的存在性检查,而资源的存在只保证了有效的描述符。
     */
    boolean exists();

    /**
     * 指示是否可以通过 {@link #getInputStream()} 读取此资源的非空内容。
     * 通常对于存在的典型资源描述符,返回 {@code true},因为它严格隐含了 {@link #exists()} 的语义。
     * 注意,实际的内容读取可能仍然会失败。然而,{@code false} 明确指出资源内容无法读取。
     */
    default boolean isReadable() {
        return exists();
    }

    /**
     * 指示此资源是否代表一个开放的流句柄。
     * 如果为 {@code true},InputStream 不能被多次读取,并且必须在读取后及时关闭以避免资源泄漏。
     * 对于典型的资源描述符,这将是 {@code false}。
     */
    default boolean isOpen() {
        return false;
    }

    /**
     * 判断此资源是否代表文件系统中的一个文件。
     * {@code true} 值强烈暗示(但不保证){@link #getFile()} 调用会成功。
     * 默认情况下,这是保守地设置为 {@code false}。
     */
    default boolean isFile() {
        return false;
    }

    /**
     * 返回此资源的 URL 句柄。
     * @throws IOException 如果资源无法作为 URL 解析,即如果资源作为描述符不可用。
     */
    URL getURL() throws IOException;

    /**
     * 返回此资源的 URI 句柄。
     * @throws IOException 如果资源无法作为 URI 解析,即如果资源作为描述符不可用。
     */
    URI getURI() throws IOException;

    /**
     * 返回此资源的文件句柄。
     * @throws java.io.FileNotFoundException 如果资源无法解析为绝对文件路径,即如果资源在文件系统中不可用。
     * @throws IOException 如果在解析/读取时发生一般性故障。
     */
    File getFile() throws IOException;

    /**
     * 返回一个 {@link ReadableByteChannel}。
     * 预期每次调用都会创建一个新的通道。
     * 默认实现通过 {@link #getInputStream()} 的结果返回 {@link Channels#newChannel(InputStream)}。
     * @return 此资源底层的字节通道(不能为空)。
     * @throws java.io.FileNotFoundException 如果底层资源不存在。
     * @throws IOException 如果无法打开内容通道。
     */
    default ReadableByteChannel readableChannel() throws IOException {
        return Channels.newChannel(getInputStream());
    }

    /**
     * 将此资源的内容作为字节数组返回。
     * @return 此资源的内容作为字节数组。
     * @throws java.io.FileNotFoundException 如果资源无法解析为绝对文件路径,即如果资源在文件系统中不可用。
     * @throws IOException 如果在解析/读取时发生一般性故障。
     */
    default byte[] getContentAsByteArray() throws IOException {
        return FileCopyUtils.copyToByteArray(getInputStream());
    }

    /**
     * 使用指定的字符集,将此资源的内容作为字符串返回。
     * @param charset 用于解码的字符集。
     * @return 此资源的内容作为一个 {@code String}。
     * @throws java.io.FileNotFoundException 如果资源无法解析为绝对文件路径,即如果资源在文件系统中不可用。
     * @throws IOException 如果在解析/读取时发生一般性故障。
     */
    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}。
     * 鼓励实现返回未编码的文件名。
     */
    @Nullable
    String getFilename();

    /**
     * 返回此资源的描述,用于在使用资源时输出错误信息。
     * 鼓励实现也从他们的 {@code toString} 方法返回这个值。
     * @see Object#toString()
     */
    String getDescription();

}

另外它继承这个接口中有一个方法叫getInputStream,这个方法能实现对低级别资源的访问

public interface InputStreamSource {

    InputStream getInputStream() throws IOException;

}

3 Resource的实现类

因为Resource是一个接口或者说是一个抽象,它本身并不提供资源的访问实现,所以它有很多的实现类。我们用的话就用这个resource接口中的各个实现类。

image-20240102070516896

3.1 UrlResource 访问网络资源

首先我们来看第一个实现类:UrlResource。

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

http:------该前缀用于访问基于HTTP协议的网络资源。 ftp:------该前缀用于访问基于FTP协议的网络资源 file: ------该前缀用于从文件系统中读取资源

接下来我们来做一个演示,访问基于HTTP协议的网络资源

import org.springframework.core.io.UrlResource;

/**
 * UrlResource访问资源
 *
 * @author 阿杰 2416338031@qq.com
 * @version 1.0
 * @date 2024/1/2 7:13
 */
public class UrlResourceTest {

    /**
     * 通过UrlResource访问网络资源
     *
     * @param path 资源路径
     */
    public static void loadAndReadUrlResource(String path) {
        // 创建一个 Resource 对象
        UrlResource url = null;
        try {
            url = new UrlResource(path);
            // 获取资源名
            System.out.println("资源名:" + url.getFilename());
            System.out.println("资源路径:" + url.getURL());
            // 获取资源描述
            System.out.println("资源描述:" + url.getDescription());
            //获取资源内容
            System.out.println("资源内容:" + url.getInputStream().read());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        loadAndReadUrlResource("https://www.baidu.com");
    }

}

image-20240102071725597

第二个我们演示如何获取项目根路径下的文件,也就是从文件系统中读取资源。

image-20240102072537801

当然我们也可以使用文件的绝对路径。

image-20240102072754994

3.2 ClassPathResource 访问类路径下资源

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

那我们就来演示 在类路径下创建文件,使用ClassPathResource 访问

import org.springframework.core.io.ClassPathResource;

import java.io.InputStream;

/**
 * 访问类路径下的资源演示
 *
 * @author 阿杰 2416338031@qq.com
 * @version 1.0
 * @date 2024/1/2 7:31
 */
public class ClassPathResourceTest {

    /**
     * 通过ClassPathResource访问类路径下的资源
     *
     * @param path 资源路径
     */
    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("提示词.txt");
    }
}

image-20240102073609429

3.3 FileSystemResource 访问文件系统资源

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

演示 使用FileSystemResource 访问文件系统资源

import org.springframework.core.io.FileSystemResource;

import java.io.InputStream;

/**
 *  使用FileSystemResource 访问文件系统资源
 *
 * @author 阿杰 2416338031@qq.com
 * @version 1.0
 * @date 2024/1/2 7:38
 */
public class FileSystemResourceTest {


    /**
     * 通过FileSystemResource访问文件系统资源
     *
     * @param path 资源路径
     */
    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("pom.xml");

        // 访问文件系统资源 绝对路径
        loadAndReadUrlResource("D:\\code\\Java\\spring6-learning-project\\pom.xml");
    }
}

image-20240102074323622

4、Resource类图

上述Resource实现类与Resource顶级接口之间的关系可以用下面的UML关系模型来表示

image-20221206232920494

5、ResourceLoader 接口

5.1 概述

Spring 提供了两个关键的接口来处理资源加载:

1. ResourceLoader接口: ResourceLoader接口的实现类可以获取资源的Resource实例。它是用于加载不同类型资源的抽象接口,提供了一种统一的方式来访问各种资源,如文件、类路径资源、URL等。通过ResourceLoader,可以轻松地获取和操作应用程序中的资源。

2. ResourceLoaderAware接口: 实现了ResourceLoaderAware接口的类实例将获得对ResourceLoader的引用。这意味着它们可以在运行时通过ResourceLoader来访问资源。这对于将资源加载能力注入到特定的类中非常有用,使它们能够以一种更灵活的方式处理资源。

ResourceLoader接口提供了一个主要方法:

Resource getResource(String location):这个方法用于根据资源位置获取Resource实例。Resource可以表示各种资源,例如文件、URL、类路径资源等。ApplicationContextResourceLoader接口的常见实现之一,因此它可以直接用于获取Resource实例。

5.2 使用演示

实验一:ClassPathXmlApplicationContext获取Resource实例

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

public class ResourceExample {
    public static void main(String[] args) {
        // 访问类路径下的资源
        ApplicationContext ctx = new ClassPathXmlApplicationContext();
        // 获取类路径下的资源
        Resource res = ctx.getResource("提示词.txt");
        // 输出资源信息
        System.out.println(res.getFilename());
    }
}

image-20240102203252092

实验二:FileSystemApplicationContext获取Resource实例

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.core.io.Resource;

public class ResourceExample {
    public static void main(String[] args) {
        // 访问类路径下的资源
        ApplicationContext ctx = new FileSystemXmlApplicationContext();
        // 获取类路径下的资源
        Resource res = ctx.getResource("提示词.txt");
        // 输出资源信息
        System.out.println(res.getFilename());
    }
}

image-20240102203602315

5.3 ResourceLoader 总结

Spring采用与ApplicationContext相同的策略来访问资源。

也就是说,如果ApplicationContextFileSystemXmlApplicationContext,res就是FileSystemResource实例;

如果ApplicationContextClassPathXmlApplicationContext,res就是ClassPathResource实例。

当Spring应用程序需要进行资源访问时,实际上不需要直接使用Resource实现类,而是调用ResourceLoader实例的getResource()方法来获取资源。ResourceLoader将负责选择Resource实现类,也就是确定具体的资源访问策略,从而将应用程序和具体的资源访问策略分离开来。

此外,使用ApplicationContext访问资源时,可以通过不同的前缀来强制使用指定的ClassPathResourceFileSystemResource等实现类。

Resource res = ctx.getResource("classpath:bean.xml");
Resource res = ctx.getResource("file:bean.xml");
Resource res = ctx.getResource("http://localhost:8080/beans.xml");

6、ResourceLoaderAware 接口

ResourceLoaderAware接口的实现类的实例将获得一个ResourceLoader的引用。ResourceLoaderAware接口提供了一个setResourceLoader()方法,该方法会被Spring容器负责调用。Spring容器会将一个ResourceLoader对象作为该方法的参数传入。

如果将实现ResourceLoaderAware接口的Bean类部署在Spring容器中,Spring容器会将自身作为ResourceLoader对象传递给setResourceLoader()方法。这意味着在Bean中可以使用该ResourceLoader对象来加载资源,因为ApplicationContext的实现类通常都实现了ResourceLoader接口,所以Spring容器本身可以用作ResourceLoader

解释一下:

ResourceLoaderAware接口的实现类将会获得一个ResourceLoader引用。例如,如果您编写了一个类并实现了ResourceLoaderAware接口,然后将这个类部署到Spring容器中,Spring容器可以将自身作为ResourceLoader对象传递给这个类。 由于ApplicationContext的实现类通常也实现了ResourceLoader接口,所以Spring容器本身完全可以作为ResourceLoader进行使用。这意味着您可以在Bean中使用Spring容器来加载和访问资源,从而使资源加载更加灵活和方便。

实验:演示ResourceLoaderAware使用

第一步 创建类,实现ResourceLoaderAware接口

package com.jie.resource;


import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.ResourceLoader;

/**
 * 实现ResourceLoaderAware接口,获取ResourceLoader对象
 *
 * @author 阿杰 2416338031@qq.com
 * @version 1.0
 * @date 2024/1/2 20:52
 */
public class TestBean implements ResourceLoaderAware {

    /**
     * ResourceLoader对象
     */
    private ResourceLoader resourceLoader;

    /**
     * 实现ResourceLoaderAware接口必须实现的方法
     * 如果把该Bean部署在Spring容器中,该方法将会有Spring容器负责调用。
     * SPring容器调用该方法时,Spring会将自身作为参数传给该方法。
     *
     * @param resourceLoader ResourceLoader对象
     */
    public void setResourceLoader(ResourceLoader resourceLoader) {
        // 将ResourceLoader对象保存起来
        this.resourceLoader = resourceLoader;
    }

    /**
     * 获取ResourceLoader对象
     *
     * @return ResourceLoader对象
     */
    public ResourceLoader getResourceLoader() {
        // 返回ResourceLoader对象
        return this.resourceLoader;
    }
}

第二步 创建bean.xml文件,配置TestBean

<?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">

    <!-- 通过classpath:来指定资源的位置 -->
    <bean id="testBean" class="com.jie.resource.TestBean"/>
</beans>

第三步 测试

import com.jie.resource.TestBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;

public class ResourceExample {
    public static void main(String[] args) {
        // Spring容器会将一个ResourceLoader对象作为该方法的参数传入
        ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
        // 获取TestBean对象
        TestBean testBean = ctx.getBean("testBean", TestBean.class);
        // 获取ResourceLoader对象
        ResourceLoader resourceLoader = testBean.getResourceLoader();
        // 判断ResourceLoader对象是否是Spring容器本身
        System.out.println("Spring容器将自身注入到ResourceLoaderAware Bean 中 ? :" + (resourceLoader == ctx));
        // 获取Resource对象
        Resource resource = resourceLoader.getResource("提示词.txt");
        // 输出Resource对象的文件名
        System.out.println(resource.getFilename());
        // 输出Resource对象的描述信息
        System.out.println(resource.getDescription());
    }
}

image-20240102205702055

7、使用Resource作为属性

在前面的部分,我们介绍了Spring提供的资源访问策略,这些策略通常要求我们要么使用Resource实现类,要么使用ApplicationContext来获取资源。然而,在实际应用中,当Bean实例需要访问资源时,Spring提供了更加方便的解决方案:直接利用依赖注入。从这个角度来看,Spring框架充分利用了策略模式来简化资源访问,并将策略模式与控制反转(IoC)有机结合,最大程度地简化了Spring资源访问的流程。

总结起来,如果一个Bean实例需要访问资源,通常有以下两种解决方案:

  1. 在代码中获取Resource实例: 这种方式需要在程序中明确提供资源的位置信息,无论是通过FileSystemResource创建实例、通过ClassPathResource创建实例,还是通过ApplicationContextgetResource()方法获取实例,都需要提供资源的位置信息。这意味着资源的物理位置与代码耦合在一起,如果资源位置发生改变,就需要修改代码。因此,这种方式通常用于少数情况下需要动态决定资源位置的情况。
  2. 使用依赖注入: 这是更推荐的方式。通过依赖注入,Spring可以将资源注入到Bean实例中,而不需要在代码中硬编码资源的位置。这种方式使得代码更加松耦合,更容易维护和测试。无论资源的位置如何变化,只需要调整配置而不是修改代码即可。

因此,通常建议使用依赖注入的方式,让Spring为Bean实例注入所需的资源,以提高应用程序的可维护性和灵活性。

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

第一步 创建依赖注入类,定义属性和方法

package com.jie.resource;

import org.springframework.core.io.Resource;

/**
 * ResourceBean
 *
 * @author 阿杰 2416338031@qq.com
 * @version 1.0
 * @date 2024/1/2 21:03
 */
public class ResourceBean {


    /**
     * Resource对象
     */
    private Resource res;

    /**
     * 设置Resource对象
     *
     * @param res Resource对象
     */
    public void setRes(Resource res) {
        this.res = res;
    }

    /**
     * 获取Resource对象
     *
     * @return Resource对象
     */
    public Resource getRes() {
        return res;
    }

    /**
     * 解析Resource对象
     */
    public void parse() {
        System.out.println(res.getFilename());
        System.out.println(res.getDescription());
    }
}

第二步 创建spring配置文件,配置依赖注入

<?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">

    <!-- 通过classpath:来指定资源的位置 -->
    <bean id="resourceBean" class="com.jie.resource.ResourceBean">
        <!-- 可以使用file:、http:、ftp:等前缀强制Spring采用对应的资源访问策略 -->
        <!-- 如果不采用任何前缀,则Spring将采用与该ApplicationContext相同的资源访问策略来访问资源 -->
        <property name="res" value="classpath:提示词.txt"/>
    </bean>
</beans>

第三步 测试

import com.jie.resource.ResourceBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class ResourceExample {
    public static void main(String[] args) {
        // 通过类路径加载配置文件
        ApplicationContext ctx = new ClassPathXmlApplicationContext("bean2.xml");
        // 获取ResourceBean对象
        ResourceBean resourceBean = ctx.getBean("resourceBean", ResourceBean.class);
        // 调用ResourceBean的parse方法
        resourceBean.parse();
    }
}

image-20240102210853503

8、应用程序上下文和资源路径

8.1 概述

无论以何种方式创建ApplicationContext实例,都需要为ApplicationContext指定配置文件。Spring允许使用一个或多个XML配置文件来配置应用程序上下文。当程序创建ApplicationContext实例时,通常也是通过Resource的方式来访问这些配置文件。因此,ApplicationContext完全支持不同类型的资源访问方式,包括ClassPathResource、FileSystemResource、ServletContextResource等。

ApplicationContext确定资源访问策略通常有两种方法:

(1)使用ApplicationContext实现类指定访问策略。

(2)使用前缀指定访问策略。

8.2 ApplicationContext实现类指定访问策略

创建ApplicationContext对象时,通常可以使用以下不同的实现类:

  1. ClassPathXMLApplicationContext 对应使用ClassPathResource进行资源访问。
  2. FileSystemXmlApplicationContext 对应使用FileSystemResource进行资源访问。
  3. XmlWebApplicationContext 对应使用ServletContextResource进行资源访问。

当使用不同的ApplicationContext实现类时,意味着Spring将采用相应的资源访问策略。

前面已经演示了相关示例来展示不同ApplicationContext实现类的效果。

8.3 使用前缀指定访问策略

实验一:classpath前缀使用

package com.atguigu.spring6.context;

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) {
        /*
         * 通过搜索文件系统路径下的xml文件创建ApplicationContext,
         * 但通过指定classpath:前缀强制搜索类加载路径
         * classpath:bean.xml
         * */
        ApplicationContext ctx =
                new ClassPathXmlApplicationContext("classpath:bean.xml");
        System.out.println(ctx);
        Resource resource = ctx.getResource("atguigu.txt");
        System.out.println(resource.getFilename());
        System.out.println(resource.getDescription());
    }
}

image-20240102211834866

实验二:classpath通配符使用

classpath* : 前缀允许加载多个XML配置文件的能力。当使用classpath*:前缀指定XML配置文件时,系统将搜索类加载路径下所有与文件名匹配的文件,并分别加载这些文件中的配置定义,最后将它们合并成一个ApplicationContext。

ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath*:bean.xml");
System.out.println(ctx);

使用classpath* : 前缀时,Spring会搜索类加载路径下所有符合条件的配置文件。

需要注意的是,classpath* : 前缀仅对ApplicationContext有效,因为创建ApplicationContext时会分别访问多个配置文件,而不是像Resource一样使用ClassLoader的getResource方法。

使用三:通配符其他使用

一次性加载多个配置文件的方式是通过通配符来指定配置文件:

ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:bean*.xml");

Spring也允许将classpath* : 前缀与通配符结合使用:

ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath*:bean*.xml");