Java 的标准 java.net.URL 类和各种 URL 前缀的标准处理程序无法满足所有对 low-level 资源的访问,比如:没有标准化的 URL 实现可用于访问需要从类路径或相对于 ServletContext 获取的资源。并且缺少某些 Spring 所需要的功能,例如检测某资源是否存在等。而 Spring 的 Resource 声明了访问 low-level 资源的能力。
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();
}
其中一些重要的方法:
- getInputStream(): 找到并打开资源,返回一个 InputStream 以从资源中读取。预计每次调用都会返回一个新的I nputStream(),调用者有责任关闭每个流。
- exists(): 返回一个布尔值,表明某个资源是否以物理形式存在
- isOpen(): 返回一个布尔值,指示此资源是否具有开放流的句柄。如果为 true,InputStream 就不能够多次读取,只能够读取一次并且及时关闭以避免内存泄漏。对于所有常规资源实现,返回 false,但是 InputStreamResource 除外。
- getDescription(): 返回资源的描述,用来输出错误的日志。这通常是完全限定的文件名或资源的实际 URL。
其他方法:
- isReadable(): 表明资源的目录读取是否通过 getInputStream() 进行读取。
- isFile(): 表明这个资源是否代表了一个文件系统的文件。
- getURL(): 返回一个 URL 句柄,如果资源不能够被解析为 URL,将抛出 IOException
- getURI(): 返回一个资源的 URI 句柄
- getFile(): 返回某个文件,如果资源不能够被解析称为绝对路径,将会抛出 FileNotFoundException
- lastModified(): 资源最后一次修改的时间戳
- createRelative(): 创建此资源的相关资源
- getFilename(): 资源的文件名是什么 例如:最后一部分的文件名 myfile.txt
Resource 实现类
Resource 接口是 Spring 资源访问策略的抽象,是基于策略模式的。它本身并不提供任何资源访问实现,具体的资源访问由该接口的实现类完成——每个实现类代表一种资源访问策略。Resource 一般包括这些实现类:UrlResource、ClassPathResource、FileSystemResource、ServletContextResource、InputStreamResource、ByteArrayResource
UrlResource(常用)
Resource 的一个实现类,常用来访问网络资源,它支持 URL 的绝对路径。它也支持访问系统资源,功能最强大。
- http:该前缀用于访问基于 HTTP 协议的网络资源。
- ftp:该前缀用于访问基于 FTP 协议的网络资源
- file:该前缀用于从文件系统中读取资源
Resource resource;
@Test
public void urlResourceTest() throws Exception {
// http
// String path = "http://www.baidu.com";
// file,注意指向的是工程的根路径,而不是模块的根路径
// 当然也可以写绝对路径:file:D:/Documents/Links.txt
String path = "file:pom.xml";
// ftp 协议也可以通过这种方式访问
// 1. 创建 UrlResource 实现类
resource = new UrlResource(path);
// 2. 获取 UrlResource 实现类的信息
System.out.println(resource.getFilename());
System.out.println(resource.getURI());
System.out.println(resource.getDescription());
System.out.println(resource.getInputStream().read());
}
ClassPathResource(常用)
ClassPathResource 用来访问类加载路径下的资源,相对于其他的 Resource 实现类,其主要优势是方便访问类加载路径里的资源,尤其对于 Web 应用,ClassPathResource 可自动搜索位于 classes 下的资源文件,无须使用绝对路径访问。
@Test
public void classPathResourceTest() throws Exception {
// ClassPathResource 的好处是直接默认处于当前模块的根目录,
// 也就是说对于当前模块 resources 下的文件可以直接访问
String path = "penghao.txt";
// 1. 创建 ClassPathResource 实现类
resource = new ClassPathResource(path);
// 2. 获取 ClassPathResource 实现类的信息
System.out.println(resource.getFilename());
System.out.println(resource.getURI());
System.out.println(resource.getDescription());
InputStream inputStream = resource.getInputStream();
byte[] b = new byte[1024];
int len;
while ((len = inputStream.read(b)) != -1) {
System.out.print(new String(b, 0, len));
}
}
FileSystemResource(常用)
Spring 提供的 FileSystemResource 类用于访问文件系统资源。
使用 FileSystemResource 来访问文件系统资源并没有太大的优势,因为 Java 提供的 File 类也可用于访问文件系统资源。
@Test
public void fileSystemResourceTest() throws Exception {
// 这里可以直接使用绝对路径
String path = "E:\\Documents\\Links.txt";
// 相对路径是工程的根路径,和 UrlResource 类似
// 1. 创建 FileSystemResource 实现类
resource = new FileSystemResource(path);
// 2. 获取 FileSystemResource 实现类的信息
System.out.println(resource.getFilename());
System.out.println(resource.getURI());
System.out.println(resource.getDescription());
InputStream inputStream = resource.getInputStream();
byte[] b = new byte[1024];
int len;
while ((len = inputStream.read(b)) != -1) {
System.out.print(new String(b, 0, len));
}
}
ServletContextResource
这是 ServletContext 资源的 Resource 实现,它解释相关 Web 应用程序根目录中的相对路径。它始终支持流(stream)访问和 URL 访问,但只有在扩展 Web 应用程序存档且资源实际位于文件系统上时才允许 java.io.File 访问。无论它是在文件系统上扩展还是直接从 JAR 或其他地方(如数据库)访问,实际上都依赖于 Servlet 容器。
InputStreamResource
InputStreamResource 是给定的输入流(InputStream)的 Resource 实现。它的使用场景在没有特定的资源实现的时候使用(感觉和@Component 的适用场景很相似)。与其他 Resource 实现相比,这是已打开资源的描述符。 因此,它的 isOpen() 方法返回true。如果需要将资源描述符保留在某处或者需要多次读取流,请不要使用它。
ByteArrayResource
字节数组的 Resource 实现类。通过给定的数组创建了一个 ByteArrayInputStream。它对于从任何给定的字节数组加载内容非常有用,而无需求助于单次使用的 InputStreamResource。
ResourceLoader 接口
ResourceLoader 接口实现类的实例可以获得一个 Resource 实例。在ResourceLoader接口里有且仅有一个方法:
Resource getResource(String location)
: 用于返回一个 Resource 实例。
ApplicationContext 的所有实现类都实现了 ResourceLoader 接口,这意味着它们都可以使用 getResource() 获得 Resource 实例。获得的 Resource 的具体类型与 ApplicationContext 的类型有关。
例如:
-
使用
ClassPathXmlApplicationContext
@Test public void test() { ApplicationContext context = new ClassPathXmlApplicationContext(); Resource res = context.getResource("penghao.txt"); System.out.println(res.getDescription()); }
得到的是当前模板下类加载路径的文件,也即 IDEA 中当前模块 resources 中的文件。
-
使用
FileSystemXmlApplicationContext
@Test public void test2() { ApplicationContext context = new FileSystemXmlApplicationContext(); Resource res = context.getResource("pom.xml"); System.out.println(res.getDescription()); }
得到的是整个工程根目录下的文件。
需要使用 Resource 时,既可以使用 ApplicationContext 获得,也可以自行创建不同类型的 Resource 实现类以实现各种资源的访问。
ResourceLoaderAware 接口
ResourceLoaderAware 接口实现类的实例拥有一个 ResourceLoader 的引用,该接口有且仅有一个方法:
void setResourceLoader(ResourceLoader resourceLoader)
: 用于设定拥有的 ResourceLoader 实例。
如果把ResourceLoaderAware 接口的实现类部署在 bean 容器中,Spring 容器会将自身作为 setResourceLoader() 方法的参数传入。由于 ApplicationContext 的实现类都实现了 ResourceLoader 接口,所以都可以作为参数传入 setResourceLoader。
例如我们创建一个简单的 ResourceLoaderAware 接口实现类来验证:
package site.penghao.spring6.resource_loader_aware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Component;
/**
* @author hope
* @date 2023/4/1 - 12:25
*/
@Component
public class TestBean implements ResourceLoaderAware {
private ResourceLoader resourceLoader;
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
public ResourceLoader getResourceLoader() {
return resourceLoader;
}
}
测试结果:
/**
* @author hope
* @date 2023/4/1 - 12:28
*/
public class ResourceLoaderAwareTest {
@Test
public void test() {
ApplicationContext context =
new ClassPathXmlApplicationContext("bean.xml");
TestBean testBean = context.getBean(TestBean.class);
System.out.println(testBean.getResourceLoader() == context); // true
}
}
依赖注入 Resource
使用前面的方法,如果需要获取 Resource 实例,都需要在代码中输入 path 路径,如果需要修改路径,就需要修改代码,这是我们不希望的。Spring 实际上有更好的解决方案:使用依赖注入。Spring 框架不仅充分地利用了策略模式来简化资源访问,还将策略模式和 IoC 进行充分地结合,最大程度地简化了 Spring 资源访问。
具体地,以依赖注入的方式获取资源的基本步骤如下:
-
环境准备:待注入的 Resource 属性
package site.penghao.spring6.resource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.Resource; import org.springframework.stereotype.Component; /** * @author hope * @date 2023/4/1 - 12:46 */ public class ResourceBean { private Resource resource; public Resource getResource() { return resource; } public void setResource(Resource resource) { this.resource = resource; } }
-
配置 bean,注入 Resource 属性(这是一种特殊的注入:使用 value 而不是 ref)
<bean id="resourceBean" class="site.penghao.spring6.resource.ResourceBean"> <!-- 可以使用file:、http:、ftp:等前缀强制Spring采用对应的资源访问策略 --> <!-- 如果不采用任何前缀,则Spring将采用与该ApplicationContext相同的资源访问策略来访问资源 --> <property name="resource" value="classpath:penghao.txt"/> </bean>
-
测试
@Test public void test() { ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml"); ResourceBean resourceBean = context.getBean(ResourceBean.class); Resource resource = resourceBean.getResource(); System.out.println(resource.getDescription()); // class path resource [penghao.txt] }
配置 ApplicationContext 的资源访问策略
不管以怎样的方式创建 ApplicationContext 实例,都需要为 ApplicationContext 指定一份或多份配置文件。然而,根路径的含义却并不相同,例如使用 ClassPathXMLApplicationContext
指定配置文件时,根路径是类加载路径;使用 FileSystemXmlApplicationContext
指定配置文件时,根路径是整个工程的根路径。事实上,创建 ApplicationContext 时的资源访问策略就是基于不同的 Resource 策略进行的。
ApplicationContext 确定资源访问策略通常有两种方法:
- 使用 ApplicationContext 实现类指定访问策略。
- 使用前缀指定访问策略。
使用 ApplicationContext 实现类指定访问策略
就是我们以前一直使用的方式,例如可以使用下面的实现类进行配置文件的设置:
ClassPathXMLApplicationContext
:对应使用 ClassPathResource 进行资源访问。FileSystemXmlApplicationContext
:对应使用 FileSystemResource 进行资源访问。XmlWebApplicationContext
:对应使用 ServletContextResource 进行资源访问。
当使用 ApplicationContext 的不同实现类时,就意味着 Spring 使用相应的资源访问策略。
使用前缀指定访问策略
classpath 前缀使用
通过指定classpath:
前缀强制搜索类加载路径。
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("penghao.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 *:
前缀,而是改为使用classpath:
前缀,Spring 则只加载第一个符合条件的 XML 文件
注意 :
classpath * :
前缀仅对 ApplicationContext 有效。实际情况是,创建 ApplicationContext 时,分别访问多个配置文件(通过 ClassLoader 的 getResources 方法实现)。因此,classpath * :
前缀不可用于 Resource。
通配符其他使用
一次性加载多个配置文件的方式:指定配置文件时使用通配符
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:bean*.xml");
Spring允许将classpath*:
前缀和通配符结合使用:
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath*:bean*.xml");
小结
- Resource 是 Spring 提供的用于访问各种资源的接口,它抽象了对各种低级资源的访问方法。这种实现是基于策略模式的,Resource 的实现类提供了不同的访问的策略以方便调用。
- 常用的 Resource 实现类有 UrlResource、ClassPathResource、FileSystemResource、ServletContextResource、InputStreamResource、ByteArrayResource 等,它们代表不同的资源访问策略,可以使用不同的参数以访问各种资源。
- ResourceLoader 接口包含的 getResource() 方法可以获得 Resource 策略,所有的 ApplicationContext 都实现了 ResourceLoader 接口。
- ResourceLoaderAware 接口包含的 setResourceLoader() 方法需要设置一个 ResourceLoader,如果一个类实现了 ResourceLoaderAware 接口并且被配置为 bean,那么 IoC 容器会将自身作为 setResourceLoader() 的参数传递给这个实现类。
- 通过依赖注入的方式注入 Resource 属性的好处是可以将配置路径和程序代码分离,注入时,只需将属性的 value 值设置为路径即可。
- 确定 ApplicationContext 的配置资源访问策略既可以使用不同的实现类区分,也可以使用
classpath:
前缀强制使用类加载路径。