1、Spring Resources概述
在 Java 编程中,java.net.URL
类常用于进行资源操作。然而,这个类在访问某些底层资源时存在局限性。例如,它不能直接从类路径中获取资源,或者在 Web 项目中无法方便地访问相对于服务器上下文的资源。此外,java.net.URL
在功能方面也有所欠缺,比如无法检测某个资源是否存在。
针对这些限制,Spring 框架提供了 Resource
接口。Resource
接口为底层资源的访问提供了更强大的能力。它不仅可以处理来自类路径的资源和相对于服务器上下文的资源,还包含了一些额外的实用功能,如资源存在性的检测。因此,对于需要进行细致的资源管理和访问的情况,选择 Spring 的 Resource
接口通常是更优的选择。这种设计不仅增强了资源访问的灵活性,也提升了开发效率和代码的可维护性。
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接口中的各个实现类。
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");
}
}
第二个我们演示如何获取项目根路径下的文件,也就是从文件系统中读取资源。
当然我们也可以使用文件的绝对路径。
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");
}
}
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");
}
}
4、Resource类图
上述Resource实现类与Resource顶级接口之间的关系可以用下面的UML关系模型来表示
5、ResourceLoader
接口
5.1 概述
Spring 提供了两个关键的接口来处理资源加载:
1. ResourceLoader
接口: ResourceLoader
接口的实现类可以获取资源的Resource实例。它是用于加载不同类型资源的抽象接口,提供了一种统一的方式来访问各种资源,如文件、类路径资源、URL等。通过ResourceLoader
,可以轻松地获取和操作应用程序中的资源。
2. ResourceLoaderAware
接口: 实现了ResourceLoaderAware
接口的类实例将获得对ResourceLoader
的引用。这意味着它们可以在运行时通过ResourceLoader
来访问资源。这对于将资源加载能力注入到特定的类中非常有用,使它们能够以一种更灵活的方式处理资源。
ResourceLoader
接口提供了一个主要方法:
Resource getResource(String location):这个方法用于根据资源位置获取Resource实例。Resource可以表示各种资源,例如文件、URL、类路径资源等。ApplicationContext
是ResourceLoader
接口的常见实现之一,因此它可以直接用于获取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());
}
}
实验二: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());
}
}
5.3 ResourceLoader
总结
Spring采用与ApplicationContext
相同的策略来访问资源。
也就是说,如果ApplicationContext
是FileSystemXmlApplicationContext
,res就是FileSystemResource
实例;
如果ApplicationContext
是ClassPathXmlApplicationContext
,res就是ClassPathResource
实例。
当Spring应用程序需要进行资源访问时,实际上不需要直接使用Resource
实现类,而是调用ResourceLoader
实例的getResource()
方法来获取资源。ResourceLoader
将负责选择Resource实现类,也就是确定具体的资源访问策略,从而将应用程序和具体的资源访问策略分离开来。
此外,使用ApplicationContext
访问资源时,可以通过不同的前缀来强制使用指定的ClassPathResource
、FileSystemResource
等实现类。
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());
}
}
7、使用Resource作为属性
在前面的部分,我们介绍了Spring提供的资源访问策略,这些策略通常要求我们要么使用Resource
实现类,要么使用ApplicationContext
来获取资源。然而,在实际应用中,当Bean实例需要访问资源时,Spring提供了更加方便的解决方案:直接利用依赖注入。从这个角度来看,Spring框架充分利用了策略模式来简化资源访问,并将策略模式与控制反转(IoC)有机结合,最大程度地简化了Spring资源访问的流程。
总结起来,如果一个Bean实例需要访问资源,通常有以下两种解决方案:
- 在代码中获取Resource实例: 这种方式需要在程序中明确提供资源的位置信息,无论是通过
FileSystemResource
创建实例、通过ClassPathResource
创建实例,还是通过ApplicationContext
的getResource()
方法获取实例,都需要提供资源的位置信息。这意味着资源的物理位置与代码耦合在一起,如果资源位置发生改变,就需要修改代码。因此,这种方式通常用于少数情况下需要动态决定资源位置的情况。 - 使用依赖注入: 这是更推荐的方式。通过依赖注入,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();
}
}
8、应用程序上下文和资源路径
8.1 概述
无论以何种方式创建ApplicationContext实例,都需要为ApplicationContext指定配置文件。Spring允许使用一个或多个XML配置文件来配置应用程序上下文。当程序创建ApplicationContext实例时,通常也是通过Resource的方式来访问这些配置文件。因此,ApplicationContext完全支持不同类型的资源访问方式,包括ClassPathResource、FileSystemResource、ServletContextResource等。
ApplicationContext确定资源访问策略通常有两种方法:
(1)使用ApplicationContext实现类指定访问策略。
(2)使用前缀指定访问策略。
8.2 ApplicationContext
实现类指定访问策略
创建ApplicationContext对象时,通常可以使用以下不同的实现类:
ClassPathXMLApplicationContext
: 对应使用ClassPathResource
进行资源访问。FileSystemXmlApplicationContext
: 对应使用FileSystemResource
进行资源访问。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());
}
}
实验二: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");