8.3.1 Resource实现类

8.3.1 Resource实现类

Resource接口是Spring资源访问的接口,具体的资源访问由该接口的实现类完成。 Spring提供了Resource接口的大量实现类。

Resource接口实现类 描述
UrlResource 访问网络资源的实现类。
ClassPathResource 访问类加载路径里资源的实现类。
FileSystemResource 访问文件系统里资源的实现类。
ServletContextResource 访问相对于ServletContext路径下的资源的实现类.
InputStreamResource 访问输入流资源的实现类。
ByteArrayResource 访问字节数组资源的实现类.

针对不同的底层资源,这些Resource实现类提供了相应的资源访问逻辑,并提供便捷的包装,以利于客户端程序的资源访问。

1. 访问网络资源

访问网络资源通过UrlResource类实现, UrlResourcejava.net.URL类的包装,主要用于访问之前通过URL类访问的资源对象。URL资源通常应该提供标准的协议前缀。例如:file:用于访问文件系统;http:于通过HTTP协议访问资源;ftp:用于通过FTP协议访问资源等。

UrlResource类实现了Resource接口,对Resource的全部方法提供了实现,完全支持Resource的全部API

程序示例

1
2
3
4
5
6
7
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\UrlResource
├─book.xml
├─lib\
│ └─dom4j-1.6.1.jar
└─src\
└─lee\
└─UrlResourceTest.java

下面的代码示范了使用UrlResource访问文件系统资源的示例。

1
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
29
30
31
32
33
34
35
36
37
38
package lee;

import org.springframework.core.io.UrlResource;
import org.dom4j.*;
import org.dom4j.io.*;
import java.util.*;

public class UrlResourceTest
{
public static void main(String[] args) throws Exception
{
// 创建一个Resource对象,指定从文件系统里读取资源
UrlResource ur = new UrlResource("file:book.xml");

// 获取该资源的简单信息
System.out.println(ur.getFilename());
System.out.println(ur.getDescription());
// 创建基于SAX的dom4j解析器
SAXReader reader = new SAXReader();
Document doc = reader.read(ur.getFile());
// 获取根元素
Element el = doc.getRootElement();
List l = el.elements();
// 遍历根元素的全部子元素
for (Iterator it = l.iterator(); it.hasNext();)
{
// 每个节点都是<书>节点
Element book = (Element) it.next();
List ll = book.elements();
// 遍历<书>节点的全部子节点
for (Iterator it2 = ll.iterator(); it2.hasNext();)
{
Element eee = (Element) it2.next();
System.out.println(eee.getText());
}
}
}
}

上面程序中的粗体字代码使用UrlResource来访问本地磁盘资源,虽然UrlResource是为访问网络资源而设计的,但通过使用file:前缀也可访问本地磁盘资源。如果需要访问网络资源,则可以使用如下两个常用前缀。

  1. http:,该前缀用于访问基于HTTP协议的网络资源。
  2. ftp:,该前缀用于访问基于FTP协议的网络资源。

由于UrIResource是对java.net.URL的封装,所以UrIResource支持的前缀与URL类所支持的前缀完全相同。
将应用所需的book.xml访问放在应用的当前路径下,运行该程序,即可看到使用UrlResource访问本地磁盘资源的效果。

1
2
3
4
5
6
book.xml
URL [file:book.xml]
疯狂Java讲义
李刚
轻量级Java EE企业应用实战
李刚

2. 访问类加载路径下的资源

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

程序示例

1
2
3
4
5
6
7
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\ClassPathResource
├─lib\
│ └─dom4j-1.6.1.jar
└─src\
├─book.xml
└─lee\
└─ClassPathResourceTest.java

下面示例程序示范了将book xml放在类加载路径(src目录)下,然后使用如下程序访问它。

1
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
29
30
31
32
33
34
35
36
37
38
39
40
41
package lee;

import java.util.Iterator;
import java.util.List;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.core.io.ClassPathResource;

public class ClassPathResourceTest
{
public static void main(String[] args)
throws Exception
{
// 创建一个Resource对象,从类加载路径里读取资源
ClassPathResource cr = new ClassPathResource("book.xml");

// 获取该资源的简单信息
System.out.println(cr.getFilename());
System.out.println(cr.getDescription());
// 创建基于SAX的dom4j解析器
SAXReader reader = new SAXReader();
Document doc = reader.read(cr.getFile());
// 获取根元素
Element el = doc.getRootElement();
List l = el.elements();
// 遍历根元素的全部子元素
for (Iterator it = l.iterator();it.hasNext() ; )
{
// 每个节点都是<书>节点
Element book = (Element)it.next();
List ll = book.elements();
// 遍历<书>节点的全部子节点
for (Iterator it2 = ll.iterator();it2.hasNext() ; )
{
Element eee = (Element)it2.next();
System.out.println(eee.getText());
}
}
}
}

上面程序中的粗体字代码用于访问类加载路径下的book.xml文件,对比前面进行资源访问的示例程序,发现两个程序除了进行资源访问的代码有所区别之外,其他程序代码基本一致,这就是Spring资源访问的优势— Spring的资源访问消除了底层资源访问的差异,允许程序以一致的方式来访问不同的底层资源。

3. 访问文件系统资源

Spring提供的File SystemResource类用于访问文件系统资源。不过使用FileSystemResource来访问文件系统资源并没有太大的优势,因为Java提供的File类也可用于访问文件系统资源。
当然,使用FileSystemResource也可消除底层资源访问的差异,程序通过统一的Resource API来进行资源访问。

程序示例

1
2
3
4
5
6
7
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\FileSystemResource
├─book.xml
├─lib\
│ └─dom4j-1.6.1.jar
└─src\
└─lee\
└─FileSystemResourceTest.java

下面的程序是使用FileSystemResource来访问文件系统资源的示例程序。

1
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
29
30
31
32
33
34
35
36
37
38
39
40
package lee;

import java.util.Iterator;
import java.util.List;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.core.io.FileSystemResource;

public class FileSystemResourceTest
{
public static void main(String[] args) throws Exception
{
// 默认从文件系统的当前路径加载book.xml资源
FileSystemResource fr = new FileSystemResource("book.xml");

// 获取该资源的简单信息
System.out.println(fr.getFilename());
System.out.println(fr.getDescription());
// 创建基于SAX的dom4j解析器
SAXReader reader = new SAXReader();
Document doc = reader.read(fr.getFile());
// 获取根元素
Element el = doc.getRootElement();
List l = el.elements();
// 遍历根元素的全部子元素
for (Iterator it = l.iterator();it.hasNext() ; )
{
// 每个节点都是<书>节点
Element book = (Element)it.next();
List ll = book.elements();
// 遍历<书>节点的全部子节点
for (Iterator it2 = ll.iterator();it2.hasNext() ; )
{
Element eee = (Element)it2.next();
System.out.println(eee.getText());
}
}
}
}

与前两种使用Resource进行资源访问的区别在于:用于确定的资源的字符串写法不同,位于本地文件系统内,而且无须使用任何前缀。
FileSystemResource实例可使用FileSystemResource构造器显式地创建,但更多的时候它都是隐式创建的。执行Spring的某个方法时,该方法接受一个代表资源路径的字符串参数,当Spring识别该字符串参数中包含file:前缀后,系统将会自动创建FileSystemResource对象。

4. 访问应用相关资源

Spring提供了ServletContextResource类来访问Web Context下相对路径下的资源,ServletContextResource构造器接受一个代表资源位置的字符串参数,该资源位置是相对于Web应用根路径的位置
使用ServletContextResource访问的资源,也可通过文件IO访问或URL访问。

访问Web Context下的资源时使用File类ServletContextResource的区别

  • 通过java.io.File访问要求资源被解压缩,而且在本地文件系统中;
  • 但使用ServletContextResource进行访问时则无须关心资源是否被解压缩出来,或者直接存放在JAR文件中,总可通过Servlet容器访问。

当程序试图直接通过File来访问Web Context下相对路径下的资源时,应该先使用ServletContextgetRealPath()方法来取得资源绝对路径,再以该绝对路径来创建File对象。

程序示例

1
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
29
30
31
32
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\ServletContextResource
├─src\
└─WebContent\
├─META-INF\
│ └─MANIFEST.MF
├─test.jsp
└─WEB-INF\
├─book.xml
├─lib\
│ ├─dom4j-1.6.1.jar
│ ├─spring-aop-5.0.2.RELEASE.jar
│ ├─spring-aspects-5.0.2.RELEASE.jar
│ ├─spring-beans-5.0.2.RELEASE.jar
│ ├─spring-context-5.0.2.RELEASE.jar
│ ├─spring-context-indexer-5.0.2.RELEASE.jar
│ ├─spring-context-support-5.0.2.RELEASE.jar
│ ├─spring-core-5.0.2.RELEASE.jar
│ ├─spring-expression-5.0.2.RELEASE.jar
│ ├─spring-instrument-5.0.2.RELEASE.jar
│ ├─spring-jcl-5.0.2.RELEASE.jar
│ ├─spring-jdbc-5.0.2.RELEASE.jar
│ ├─spring-jms-5.0.2.RELEASE.jar
│ ├─spring-messaging-5.0.2.RELEASE.jar
│ ├─spring-orm-5.0.2.RELEASE.jar
│ ├─spring-oxm-5.0.2.RELEASE.jar
│ ├─spring-test-5.0.2.RELEASE.jar
│ ├─spring-tx-5.0.2.RELEASE.jar
│ ├─spring-web-5.0.2.RELEASE.jar
│ ├─spring-webflux-5.0.2.RELEASE.jar
│ ├─spring-webmvc-5.0.2.RELEASE.jar
│ └─spring-websocket-5.0.2.RELEASE.jar
└─web.xml

下面把book.xml文件放在web应用的WEB-INF路径下,然后通过JSP页面来直接访问该book.xm文件。值得指出的是,在默认情况下,JSP不能直接访问WEB-INF路径下的任何资源,所以该应用中的JSP页面需要使用ServletContextResource来访问该资源。
下面是JSP页面代码。

1
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
29
30
31
32
33
34
35
36
37
38
39
40
41
<%@ page contentType="text/html; charset=GBK" language="java"
errorPage=""%>
<%@ page
import="org.springframework.web.context.support.ServletContextResource"%>
<%@ page import="org.dom4j.*,org.dom4j.io.*,java.util.*"%>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>测试ServletContextResource</title>
</head>
<body>
<h3>测试ServletContextResource</h3>
<%
// 从Web Context下的WEB-INF路径下读取book.xml资源
ServletContextResource src = new ServletContextResource(application,
"WEB-INF/book.xml");
// 获取该资源的简单信息
System.out.println(src.getFilename());
System.out.println(src.getDescription());
// 创建基于SAX的dom4j解析器
SAXReader reader = new SAXReader();
Document doc = reader.read(src.getFile());
// 获取根元素
Element el = doc.getRootElement();
List l = el.elements();
// 遍历根元素的全部子元素
for (Iterator it = l.iterator(); it.hasNext();)
{
// 每个节点都是<书>节点
Element book = (Element) it.next();
List ll = book.elements();
// 遍历<书>节点的全部子节点
for (Iterator it2 = ll.iterator(); it2.hasNext();)
{
Element eee = (Element) it2.next();
out.println(eee.getText());
out.println("<br/>");
}
}
%>
</body>
</html>

上面程序中的粗体字代码指定应用从Web Context下的WEB-INF路径下读取book.xml资源,该示例恰好将book.xml文件放在应用的WEB-INF/路径下,通过使用ServletContextResource就可让JSP页面直接访问WEB-INF下的资源了。

将应用部署在Tomcat中,然后启动Tomcat,再打开浏览器访问该JSP页面,将看到浏览器显示内容如下:

1
2
3
4
5
6
测试ServletContextResource

疯狂Java讲义
李刚
轻量级Java EE企业应用实战
李刚

5. 访问字节数组资源

Spring提供了InputStreamResource来访问二进制输入流资源, InputSteamResource是针对输入流Resource实现,只有当没有合适的Resource实现时,才考虑使用该InputSteamResource。在通常情况下,优先考虑使用ByteArrayResource,或者基于文件的Resource实现.
与其他Resource实现不同的是, nputSteamResource是一个总是被打开的Resource,所以isOpen方法总是返回true。因此如果需要多次读取某个流,就不要使用InputSteamResource.
在创建InputStreamResource实例时要提供一个InputStream参数。
在一些个别的情况下, InputStreamResource是有用的。例如从数据库中读取一个Blob对象,程序需要获取该Blob对象的内容,就可先通过BlobgetBinaryStream()方法获取二进制输入流,再将该二进制输入流包装成Resource对象,然后就可通过该Resource对象来访问该Blob对象所包含的资源了。

尽量不要使用InputStreamResource

InputStreamResource虽然是适应性很广的Resource实现,但效率并不好。因此,尽量不要使用InputStreamResource作为参数,而应尽量使用ByteArrayResourceFileSystemResource代替它。

程序示例

1
2
3
4
5
6
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\ByteArrayResource
├─lib\
│ └─dom4j-1.6.1.jar
└─src\
└─lee\
└─ByteArrayResourceTest.java

如下程序示范了如何使用ByteArrayResource来读取字节数组资源。出于演示目的,程序中字节数组直接通过字符串来获得

1
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package lee;

import java.util.Iterator;
import java.util.List;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.core.io.ByteArrayResource;

public class ByteArrayResourceTest
{
public static void main(String[] args) throws Exception
{
// encoding设置为当前文件的编码,不然解析错误
String file = "<?xml version='1.0' encoding='UTF-8'?>"
+ "<计算机书籍列表><书><书名>疯狂Java讲义" + "</书名><作者>李刚</作者></书><书><书名>"
+ "轻量级Java EE企业应用实战</书名><作者>李刚" + "</作者></书></计算机书籍列表>";
byte[] fileBytes = file.getBytes();
// 以字节数组作为资源来创建Resource对象
ByteArrayResource bar = new ByteArrayResource(fileBytes);
// 获取该资源的简单信息
System.out.println(bar.getDescription());
// 创建基于SAX的dom4j解析器
SAXReader reader = new SAXReader();
Document doc = reader.read(bar.getInputStream());
// 获取根元素
Element el = doc.getRootElement();
List l = el.elements();
// 遍历根元素的全部子元素
for (Iterator it = l.iterator(); it.hasNext();)
{
// 每个节点都是<书>节点
Element book = (Element) it.next();
List ll = book.elements();
// 遍历<书>节点的全部子节点
for (Iterator it2 = ll.iterator(); it2.hasNext();)
{
Element eee = (Element) it2.next();
System.out.println(eee.getText());
}
}
}
}

上面程序中的粗体字代码用于根据字节数组来创建ByteArrayResource对象,接下来就可通过该Resource对象来访问该字节数组资源了。访问字节数组资源时, Resource对象的getFile()getFilename()两个方法不可用—这是可想而知的事情—因为此时访问的资源是字节数组,当然不存在对应的File对象和文件名了.
在实际应用中,字节数组可能通过网络传输获得,也可能通过管道流获得,还可能通过其他方式获得……只要得到了代表资源的字节数组,程序就可通过ByteArrayResource将字节数组包装成Resource实例,并利用Resource来访问该资源。
对于需要采用InputStreamResource访问的资源,可先从InputStream流中读出字节数组,然后以字节数组来创建ByteArrayResource。这样, InputStreamResource也可被转换成ByteArrayResource,从而方便多次读取。