资源操作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接口定义的方法:
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接口,只有一个方法:
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关系模型来表示:
3.1 UrlResource访问网络资源
Resource的一个实现类,用来访问网络资源,它支持URL的绝对路径。
- http: 该前缀用于访问基于HTTP协议的网络资源。
- ftp:该前缀用于访问基于FTP协议的网络资源。
- file: 该前缀用于从文件系统中读取资源。
- 访问基于HTTP协议的网络资源
创建子模块spring6-resources
创建UrlResourceDemo类
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");
}
}
- 在项目根路径下创建文件,从文件系统中读取资源
修改调用传递路径从网络地址改为文件路径:
public static void main(String[] args) {
// 访问文件系统资源
loadAndReadUrlResource("file:data.txt");
}
3.2 ClassPathResource访问类路径下资源
ClassPathResource用来访问类加载路径下的资源,相对于其他的Resource实现类,其主要优势是方便访问类加载路径里的资源,尤其对于Web应用,ClassPathResource可自动搜索位于classes下的资源文件,无须使用绝对路径访问。
- 在类路径下创建文件beansData.txt,使用ClassPathResource访问:
- 创建ClassPathResourceDemo.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类,访问文件目录内容:
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提供如下两个重要的接口来处理资源加载:
- ResourceLoader:该接口实现类的实例可以获得一个Resource实例。它是用于加载不同类型资源的抽象接口,提供了一种统一的方式来访问各种资源,如文件、类路径资源、URL等。通过ResourceLoader,可以轻松地获取和操作应用程序中的资源。
- ResourceLoaderAware接口:该接口实现类的实例将获得对ResourceLoader的引用。然后可以通过ResourceLoader来访问资源。这对于将资源加载能力注入到特定的类中非常有用,使它们能够以一种更灵活的方式处理资源。
4.1 ResourceLoader接口
ResourceLoader接口的源码如下:
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实例举例说明:
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实现类 |
---|---|
ClassPathXMLApplicationContext | ClassPathResource |
FileSystemXmlApplicationContext | FileSystemResource |
XmlWebApplicationContext | ServletContextResource |
当Spring上下文需要进行资源访问时,没有直接实现Resource接口也没有直接使用Resource实现类,而是通过调用ResourceLoader实例的getResource()
方法的方式来获得资源,ReosurceLoader将会负责通过具体访问资源的策略选择Reosurce实现类,从而将应用上下文和具体的资源访问细节分离开来。
4.3 ResourceLoaderAware接口
ResourceLoaderAware接口的源码如下:
public interface ResourceLoaderAware extends Aware {
void setResourceLoader(ResourceLoader resourceLoader);
}
实现了该接口实例将获得对ResourceLoader的引用。然后通过ResourceLoader来访问资源,这对于需要资源加载能力的类中非常有用,使它们能够以一种更灵活的方式处理资源。
ResourceLoaderAware中的setResourceLoader()
方法,该方法由Spring容器负责调用,Spring容器会将ResourceLoader对象作为该方法的参数传入。又因为ApplicationContext的实现类都实现了ResourceLoader接口,Spring容器自身完全可作为参数传入作为ResorceLoader使用。
4.4 ResourceLoaderAware的使用
- 创建类,实现ResourceLoaderAware接口
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;
}
}
- 创建beans.xml文件,配置MyResourceLoaderAware类:
<?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>
- 测试代码:
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());
}
}
运行结果:
5. 使用依赖注入获取Resource实例
Spring框架充分利用了策略模式来简化资源访问, 比如使用ApplicationContext来获取资源,要不自行使用具体Resource实现类。Spring额外支持通过依赖注入简化对Spring资源访问。通过依赖注入,Spring可以将资源注入到Bean实例中,而不需要在代码中硬编码资源的位置。这种方式使得代码更加松耦合,更容易维护和测试。无论资源的位置如何变化,只需要调整配置而不是修改代码即可。
5.1 让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());
}
}
- 创建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="resourceBean" class="com.rocket.spring.resouceloader.ResourceBean" >
<!-- 可以使用file:、http:、ftp:等前缀强制Spring采用对应的资源访问策略 -->
<!-- 如果不采用任何前缀,则Spring将采用与该ApplicationContext相同的资源访问策略来访问资源 -->
<property name="res" value="classpath:data.txt"/>
</bean>
</beans>
- 代码测试:
public class Demo4 {
public static void main(String[] args) {
ApplicationContext ctx =
new ClassPathXmlApplicationContext("beans.xml");
ResourceBean resourceBean = ctx.getBean("resourceBean",ResourceBean.class);
resourceBean.parse();
}
}
运行结果:
6. 资源路径和解析
Spring上下文实例初始化都需要指定配置文件路径,可以通过使用前缀指定访问策略,比如:
// 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
: 通过通配符来指定一次性加载多个配置文件