Spring6 - Resource
# 1. Spring Resources 概述
Java 的标准 Java.net.URL
类和各种 URL 前缀的标准处理程序无法满足所有对 low-level 资源的访问,比如:没有标准化的 URL 实现可用于访问需要从类路径或相对于 ServletContext 获取的资源。并且缺少某些 Spring 所需要的功能,例如检测某资源是否存在等。而 Spring 的 Resource 声明了访问 low-level 资源的能力。
主要问题和Spring的解决方案
- Java标准URL类的限制:
- Java的
URL
类及其处理程序主要支持对文件、HTTP等标准协议的资源的访问。然而,它不支持诸如类路径(classpath)或相对于ServletContext
的资源访问,这在多环境应用中非常常见。 - Spring通过其
Resource
抽象提供了一套统一的接口来处理各种来源的资源。
- Java的
- Spring
Resource
的功能:- 多种资源类型支持:Spring提供了对不同资源类型的访问,如
UrlResource
、ClassPathResource
、FileSystemResource
等,每种资源类型都对应一种资源访问策略。 - 资源存在性检查:Spring的资源接口允许开发者检查资源是否存在,而无需打开资源。
- 资源读取:提供
getInputStream()
方法,可以对资源进行一次性或重复读取。 - 资源信息获取:能够获取资源的URL、URI、文件形式,以及资源的其他描述信息,如文件名、内容长度等。
- 多种资源类型支持:Spring提供了对不同资源类型的访问,如
# 2. Resource 接口
Spring 的 Resource 接口位于 org.Springframework.core.io
中。 旨在成为一个更强大的接口,用于抽象对低级资源的访问。以下显示了 Resource 接口定义的方法
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();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Resource 接口继承了 InputStreamSource 接口,提供了很多 InputStreamSource 所没有的方法。InputStreamSource 接口,只有一个方法:
public interface InputStreamSource {
InputStream getInputStream() throws IOException;
}
2
3
4
5
其中一些重要的方法:
getInputStream()
: 找到并打开资源,返回一个 InputStream 以从资源中读取。预计每次调用都会返回一个新的InputStream()
,调用者有责任关闭每个流exists()
: 返回一个布尔值,表明某个资源是否以物理形式存在isOpen()
: 返回一个布尔值,指示此资源是否具有开放流的句柄。如果为 true,InputStream 就不能够多次读取,只能够读取一次并且及时关闭以避免内存泄漏。对于所有常规资源实现,返回 false,但是 InputStreamResource 除外。getDescription()
: 返回资源的描述,用来输出错误的日志。这通常是完全限定的文件名或资源的实际URL
其他方法:
isReadable()
: 表明资源的目录读取是否通过getInputStream()
进行读取。isFile()
: 表明这个资源是否代表了一个文件系统的文件getURL()
: 返回一个 URL 句柄,如果资源不能够被解析为 URL,将抛出 IOExceptiongetURI()
: 返回一个资源的 URI 句柄getFile()
: 返回某个文件,如果资源不能够被解析称为绝对路径,将会抛出 FileNotFoundExceptionlastModified()
: 资源最后一次修改的时间戳createRelative()
: 创建此资源的相关资源getFilename()
: 资源的文件名是什么 例如:最后一部分的文件名myfile.txt
# 3. Resource 的实现类
Resource 接口是 Spring 资源访问策略的抽象,它本身并不提供任何资源访问实现,Resource
接口在 Spring 框架中定义了对不同种类资源的通用访问策略。不同的实现类代表不同的资源访问策略,使得 Spring 能够透明地访问资源,无论资源位于何处。以下是 Resource
的一些主要实现类:
- UrlResource:
- 访问网络资源,支持通过 URL 的绝对路径访问。
- 支持的协议包括:HTTP (
http://
), HTTPS (https://
), FTP (ftp://
), 文件 (file://
), 等。
- ClassPathResource:
- 用于访问类路径下的资源。这种方式不需要完整的路径,只需相对于类路径的位置。
- FileSystemResource:
- 用于访问文件系统中的资源,可以通过绝对或相对路径访问。
- ServletContextResource:
- 用于访问相对于 Servlet 上下文的资源,主要用在 Web 应用中。
- InputStreamResource:
- 用于访问任何给定的输入流。适用于从输入流加载的内容,而不是从一个实际的物理位置。
- ByteArrayResource:
- 用于访问内存中的资源,如字节数组所表示的数据。
# UrlResource 访问网络资源
Resource 的一个实现类,用来访问网络资源,它支持 URL 的绝对路径
http:-该前缀用于访问基于 HTTP 协议的网络资源
ftp:该前缀用于访问基于 FTP 协议的网络资源
file:该前缀用于从文件系统中读取资源
实验一:访问基于 HTTP 协议的网络资源
创建一个 maven 子模块 Spring6-resources,配置 Spring 依赖(参考前面)
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());
// 获取资源内容(示例仅读取一个字节)
System.out.println(url.getInputStream().read());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
// 访问网络资源
loadAndReadUrlResource("http://www.baidu.com");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
实验二:在项目根路径下创建文件,从文件系统中读取资源
修改实验一中的方法调用,使其能够从本地文件系统中读取文件:
public static void main(String[] args) {
// 1、访问网络资源
// loadAndReadUrlResource("https://note.wym123.cn/");
// 2、访问文件系统资源
loadAndReadUrlResource("file:scholar.txt");
}
2
3
4
5
6
7
# ClassPathResource 访问类路径下资源
ClassPathResource 用来访问类加载路径下的资源,相对于其他的 Resource 实现类,其主要优势是方便访问类加载路径里的资源,尤其对于 Web 应用,ClassPathResource 可自动搜索位于 classes 下的资源文件,无须使用绝对路径访问。
实验:在类路径下创建文件 scholar.txt,使用 ClassPathResource 访问
import org.Springframework.core.io.ClassPathResource;
import Java.io.InputStream;
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("scholar.txt");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
ClassPathResource 实例可使用 ClassPathResource 构造器显式地创建,但更多的时候它都是隐式地创建的。当执行 Spring 的某个方法时,该方法接受一个代表资源路径的字符串参数,当 Spring 识别该字符串参数中包含 classpath:
前缀后,系统会自动创建 ClassPathResource 对象。
# FileSystemResource 访问文件系统资源
Spring 提供的 FileSystemResource 类用于访问文件系统资源,使用 FileSystemResource 来访问文件系统资源并没有太大的优势,因为 Java 提供的 File 类也可用于访问文件系统资源。
实验:使用 FileSystemResource 访问文件系统资源
import org.Springframework.core.io.FileSystemResource;
import Java.io.InputStream;
public class FileSystemResourceDemo {
public static void loadAndReadUrlResource(String path) throws Exception{
// 相对路径
FileSystemResource resource = new FileSystemResource("scholar.txt");
// 绝对路径
//FileSystemResource resource = new FileSystemResource("C:\\scholar.txt");
// 获取文件名
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("scholar.txt");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
FileSystemResource 实例可使用 FileSystemResource 构造器显示地创建,但更多的时候它都是隐式创建。执行 Spring 的某个方法时,该方法接受一个代表资源路径的字符串参数,当 Spring 识别该字符串参数中包含 file:
前缀后,系统将会自动创建 FileSystemResource 对象。
# ServletContextResource
这是 ServletContext 资源的 Resource 实现,它解释相关 Web 应用程序根目录中的相对路径。它始终支持流(stream)访问和 URL 访问,但只有在扩展 Web 应用程序存档且资源实际位于文件系统上时才允许 Java.io.File
访问。无论它是在文件系统上扩展还是直接从 JAR 或其他地方(如数据库)访问,实际上都依赖于 Servlet 容器。
# InputStreamResource
InputStreamResource 是给定的输入流(InputStream)的 Resource 实现。它的使用场景在没有特定的资源实现的时候使用(感觉和 @Component
的适用场景很相似)。与其他 Resource 实现相比,这是已打开资源的描述符。 因此,它的 isOpen()
方法返回 true。如果需要将资源描述符保留在某处或者需要多次读取流,请不要使用它。
# ByteArrayResource
字节数组的 Resource 实现类。通过给定的数组创建了一个 ByteArrayInputStream。它对于从任何给定的字节数组加载内容非常有用,而无需求助于单次使用的 InputStreamResource。
# 4. Resource 类图
上述 Resource 实现类与 Resource 顶级接口之间的关系可以用下面的 UML 关系模型来表示
# 5. ResourceLoader 接口
ResourceLoader
是 Spring 框架提供的一个核心接口,用于抽象资源加载的策略。它主要用于获取资源的 Resource
实例,让资源的加载更加灵活和方便。Spring 的应用上下文(如 ApplicationContext
)都实现了 ResourceLoader
接口,使得资源加载可以直接与 Spring 的功能集成。
主要方法
getResource(String location)
: 这是ResourceLoader
接口的核心方法,根据提供的路径返回相应的Resource
实例。这个位置路径可以是任何形式的资源路径,具体解析依赖于特定的ResourceLoader
实现。
# ResourceLoader 的实用性
- 灵活的资源访问:
ResourceLoader
提供了一种从多种源(如类路径、文件系统或网络等)加载资源的方式,增加了资源访问的灵活性。 - 与 Spring 集成:Spring 的任何组件都可以通过实现
ResourceLoaderAware
接口来获取ResourceLoader
的引用,从而能够加载外部资源。
使用示例
示例一:使用 ClassPathXmlApplicationContext
获取资源
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 ctx = new ClassPathXmlApplicationContext();
// 通过 ApplicationContext 访问资源
// ApplicationContext 实例获取 Resource 实例时,
// 默认采用与 ApplicationContext 相同的资源访问策略
Resource res = ctx.getResource("classpath:scholar.txt");
System.out.println("Resource filename: " + res.getFilename());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
这里,ClassPathXmlApplicationContext
用于从类路径加载资源。资源位置使用 classpath:
前缀指明。
示例二:使用 FileSystemXmlApplicationContext
获取资源
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.core.io.Resource;
public class Demo2 {
public static void main(String[] args) {
// 创建 ApplicationContext 实例
ApplicationContext ctx = new FileSystemXmlApplicationContext();
// 获取文件系统中的资源
Resource res = ctx.getResource("file:scholar.txt");
System.out.println("Resource filename: " + res.getFilename());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
这里,FileSystemXmlApplicationContext
用于从文件系统加载资源。资源位置使用 file:
前缀指明。
# ResourceLoader 总结
Spring 将采用和 ApplicationContext 相同的策略来访问资源。也就是说,如果 ApplicationContext 是 FileSystemXmlApplicationContext,res 就是 FileSystemResource 实例;如果 ApplicationContext 是 ClassPathXmlApplicationContext,res 就是 ClassPathResource 实例。
当 Spring 应用需要进行资源访问时,实际上并不需要直接使用 Resource 实现类,而是调用 ResourceLoader 实例的 getResource()
方法来获得资源,ReosurceLoader 将会负责选择 Reosurce 实现类,也就是确定具体的资源访问策略,从而将应用程序和具体的资源访问策略分离开来。
另外,使用 ApplicationContext 访问资源时,可通过不同前缀指定强制使用指定的 ClassPathResource、FileSystemResource 等实现类
Resource res = ctx.getResource("calsspath:Bean.xml");
Resrouce res = ctx.getResource("file:Bean.xml");
Resource res = ctx.getResource("http://localhost:8080/Beans.xml");
2
3
# 6. ResourceLoaderAware 接口
在 Spring 框架中,ResourceLoaderAware
接口使实现类能够获得 ResourceLoader
的引用,这允许 Bean 动态地加载资源。当一个 Bean 类实现了 ResourceLoaderAware
接口,Spring 容器在创建 Bean 的过程中会自动调用 setResourceLoader(ResourceLoader resourceLoader)
方法,传入当前的 ApplicationContext
作为 ResourceLoader
。这种方式使 Bean 可以通过 Spring 容器访问外部资源。
实现 ResourceLoaderAware 接口
实现 ResourceLoaderAware
接口允许 Bean 在运行时获取资源,而无需直接依赖于容器的具体实现。下面是一个实现 ResourceLoaderAware
的示例:
创建 ResourceLoaderAware 实现类
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.ResourceLoader;
public class TestBean implements ResourceLoaderAware {
private ResourceLoader resourceLoader;
// Spring 容器调用此方法来注入 ResourceLoader
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
// 供外部调用,返回 ResourceLoader 对象
public ResourceLoader getResourceLoader() {
return this.resourceLoader;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
第二步:创建 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">
<bean id="testBean" class="com.scholar.spring6.resources.TestBean"></bean>
</beans>
2
3
4
5
6
7
第三步:测试
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 Demo3 {
public static void main(String[] args) {
// Spring 容器会将一个 ResourceLoader 对象作为该方法的参数传入
ApplicationContext ctx = new ClassPathXmlApplicationContext("Bean.xml");
TestBean testBean = ctx.getBean("testBean",TestBean.class);
// 获取 ResourceLoader 对象
ResourceLoader resourceLoader = testBean.getResourceLoader();
System.out.println("Spring容器将自身注入到ResourceLoaderAware Bean 中 ? :" + (resourceLoader == ctx));
// 加载其他资源
Resource resource = resourceLoader.getResource("scholar.txt");
System.out.println(resource.getFilename());
System.out.println(resource.getDescription());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 7. 使用 Resource 作为属性
前面介绍了 Spring 提供的资源访问策略,但这些依赖访问策略要么需要使用 Resource 实现类,要么需要使用 ApplicationContext 来获取资源。实际上,当应用程序中的 Bean 实例需要访问资源时,Spring 有更好的解决方法:直接利用依赖注入。从这个意义上来看,Spring 框架不仅充分利用了策略模式来简化资源访问,而且还将策略模式和 XML 进行充分地结合,最大程度地简化了 Spring 资源访问。
归纳起来,如果 Bean 实例需要访问资源,有如下两种解决方案:
- 代码中获取 Resource 实例
- 使用依赖注入
对于第一种方式,当程序获取 Resource 实例时,总需要提供 Resource 所在的位置,不管通过 FileSystemResource 创建实例,还是通过 ClassPathResource 创建实例,或者通过 ApplicationContext 的 getResource()
方法获取实例,都需要提供资源位置。这意味着:资源所在的物理位置将被耦合到代码中,如果资源位置发生改变,则必须改写程序。因此,通常建议采用第二种方法,让 Spring 为 Bean 实例 依赖注入 资源。
实验:让 Spring 为 Bean 实例依赖注入资源
第一步:创建依赖注入类,定义属性和方法
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());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
第二步:创建 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">
<bean id="testBean" class="com.scholar.spring6.resources.TestBean"></bean>
<bean id="resourceBean" class="com.scholar.spring6.resources.ResourceBean" >
<!-- 可以使用 file:、http:、ftp: 等前缀强制 Spring 采用对应的资源访问策略 -->
<!-- 如果不采用任何前缀,则 Spring 将采用与该 ApplicationContext 相同的资源访问策略来访问资源 -->
<property name="res" value="classpath:scholar.txt"/>
</bean>
</beans>
2
3
4
5
6
7
8
9
10
11
12
第三步:测试
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Demo4 {
public static void main(String[] args) {
ApplicationContext ctx =
new ClassPathXmlApplicationContext("Bean.xml");
ResourceBean resourceBean = ctx.getBean("resourceBean",ResourceBean.class);
resourceBean.parse();
}
}
2
3
4
5
6
7
8
9
10
11
12
# 8. 应用程序上下文和资源路径
# 概述
不管以怎样的方式创建 ApplicationContext 实例,都需要为 ApplicationContext 指定配置文件,Spring 允许使用一份或多分 XML 配置文件。当程序创建 ApplicationContext 实例时,通常也是以 Resource 的方式来访问配置文件的,所以 ApplicationContext 完全支持 ClassPathResource、FileSystemResource、ServletContextResource 等资源访问方式。
ApplicationContext 确定资源访问策略通常有两种方法:
使用 ApplicationContext 实现类指定访问策略
使用前缀指定访问策略
# ApplicationContext 实现类指定访问策略
创建 ApplicationContext 对象时,通常可以使用如下实现类,不同的 ApplicationContext
实现类默认采用不同的资源访问策略:
- ClassPathXmlApplicationContext: 默认使用
ClassPathResource
,适合于访问类路径下的资源文件。 - FileSystemXmlApplicationContext: 默认使用
FileSystemResource
,适合于访问文件系统中的资源。 - XmlWebApplicationContext: 默认使用
ServletContextResource
,主要用于 Web 应用中,访问相对于 Web 应用根目录的资源。
使用不同的 ApplicationContext
实现,意味着 Spring 将按照相应的策略来访问配置文件和其他资源。
# 实验一:使用 classpath 前缀
通过 ClassPathXmlApplicationContext
加载类路径下的资源,即使实例是基于文件系统的上下文,通过 classpath:
前缀也可以访问类路径资源。
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("scholar.txt");
System.out.println(resource.getFilename());
System.out.println(resource.getDescription());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 实验二:使用 classpath* 通配符
classpath*:
前缀提供了加载多个 XML 配置文件的能力,当使用 classpath*:
前缀来指定 XML 配置文件时,系统将搜索类加载路径,找到所有与文件名匹配的文件,分别加载文件中的配置定义,最后合并成一个 ApplicationContext。
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath*:Bean.xml");
System.out.println(ctx);
2
当使用 classpath*:
前缀时,Spring 将会搜索类加载路径下所有满足该规则的配置文件。
如果不是采用 classpath*:
前缀,而是改为使用 classpath:
前缀,Spring 则只加载第一个符合条件的 XML 文件
注意
classpath*:
前缀仅对 ApplicationContext 有效。实际情况是,创建 ApplicationContext 时,分别访问多个配置文件(通过 ClassLoader 的getResource 方法实现)。因此,classpath*:
前缀不可用于 Resource。
# 实验三:通配符其他使用
一次性加载多个配置文件的方式:指定配置文件时使用通配符
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:Bean*.xml");
Spring 允许将 classpath*:
前缀和通配符结合使用:
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath*:Bean*.xml");
注意事项
虽然 classpath*:
对于 ApplicationContext
构造非常有用,但它不能用于单个 Resource
的获取,因为 Resource
接口是为单个资源设计的。要加载多个资源,应该通过 ApplicationContext
或使用 Spring 的 PathMatchingResourcePatternResolver
。