标签:
在日常程序开发中,处理外部资源是很繁琐的事情,我们可能需要处理URL资源、File资源、ClassPath相关资源、服务器相关资源等等很多资源。因此处理这些资源需要使用不同的接口,这就增加了我们系统的复杂性;而且处理这些资源步骤都是类似的(打开资源、读取资源、关闭资源),因此如果能抽象出一个统一的接口来对这些底层资源进行统一访问,是不是很方便,而且使我们系统更加简洁,都是对不同的底层资源使用同一个接口进行访问。
Spring提供一个Resource接口来统一这些底层资源一致的访问,而且提供了一些便利的接口,从而能提高开发效率。
Spring的Resource接口代表底层外部资源,提供了对底层外部资源的一致性访问接口。
public interface InputStreamSource{
//每次调用都将返回一个新的资源对应的java.io.InputStream字节流,调用者在使用完毕后必须关闭该资源
InputStream getInputStream() throws IOException;
}
public interface Resource extends InputStreamSource{
//返回当前Resouce代表的底层资源是否存在,true表示存在
boolean exists();
//返回当前Resouce代表的底层资源是否可读,true表示可读
boolean isReadable();
//返回当前Resouce代表的底层资源是否已经打开,如果返回true,则只能被读取一次然后关闭以避免内存泄露;常见的Resource实现一般返回false;
boolean isOpen();
//如果当前Resouce代表的底层资源能由java.util.URL代表,则返回该URL,否则抛出IOException
URL getURL() throws IOException;
//如果当前Resouce代表的底层资源能由java.util.URI代表,则返回该URI,否则抛出IOException
URI getURI() throws IOException;
//如果当前Resouce代表的底层资源能由java.io.File代表,则返回该File,否则抛出IOException
File getFile() throws IOException;
//返回当前Resouce代表的底层文件资源的长度,一般的值代表的文件资源的长度
long contentLength() throws IOException;
//返回当前Resouce代表的底层文件资源的最后修改时间
long lastModified() throws IOException;
//用于创建相对于当前Resource代表的底层资源的资源,比如当前Resource代表文件资源“D:/test/”则createRelative("test.txt")将返回代表文件资源“D:/test/test.txt”Resource资源。
Resource createRelative(String relativePath) throws IOException;
//返回当前Resource代表的底层文件资源的文件路径,比如File资源:file://d:/test.txt 将返回d:/test.txt,而URL资源http://www.javass.cn将返回“”,因为只返回文件路径
String getFilename();
//返回当前Resource代表的底层资源的描述符,通常就是资源的全路径(实际文件名或实际URL地址)
String getDescription();
}
Resoucce接口提供了足够的抽象,足够满足我们日常所用,而且提供了很多内置Resource实现:ByteArrayResource、InputStreamResource、FileSystemResource、UrlResource、ClassPathResource、ServletContextResource、VfsResource等。
ByteArrayResource代表 btye[]数组资源,对于getInputStream操作将返回一个ByteArrayInputStream
首先我们看ByteArrayResource如何处理byte数组资源:
package com.lizhenhua.test3;
import java.io.IOException;
import java.io.InputStream;
import org.junit.Test;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
public class ResourceTest {
@Test
public void testByteArrayResource(){
//1.定义资源
Resource resource = new ByteArrayResource("hello".getBytes());
//2.验证资源
if(resource.exists()){
//3.访问资源
dumpStream(resource);
}
}
private void dumpStream(Resource resource){
InputStream is = null;
try {
//1.获取资源文件
is = resource.getInputStream();
//2.读取资源文件
byte[] descBytes = new byte[is.available()];
is.read(descBytes);
System.out.println(new String(descBytes));
} catch (IOException e) {
e.printStackTrace();
} finally{
try {
//3.关闭资源
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
dumpStream方法很抽象定义了访问流的三部曲:打开资源、读取资源、关闭资源
testByteArrayResouce方法也定义了基本步骤:定义资源、验证资源存在、访问资源
ByteArrayResource可多次读取数组资源,即isOpen永远是false
InputStreamResource代表java.io.InputStream字节流,对于个体InputStream操作将直接返回该字节流,因此只能读取一次字节流,即isOpen永远返回true
@Test
public void testInputStreamResource(){
ByteArrayInputStream bis = new ByteArrayInputStream("hello2".getBytes());
Resource resource = new InputStreamResource(bis);
if (resource.exists()) {
dumpStream(resource);
}
Assert.assertEquals(true, resource.isOpen());
}
FileSystemResource代表java.io.File资源,对于getInputStream 操作将返回底层文件的字节流,isOpen将永远返回false,从而表示可多次读取底层文件的字节流
@Test
public void testFileSystemResource(){
File file = new File("d:/test.txt");
Resource resource = new FileSystemResource(file);
if (resource.exists()) {
dumpStream(resource);
}
Assert.assertEquals(false, resource.isOpen());
}
ClassPathResource代表classpath路径的资源,将使用ClassLoader进行加载资源。classpath资源存在于类路径中的文件系统中或jar包里,且isOpen永远返回false,表示可多次读取资源。
ClassPathResource加载资源替代了Class类和ClassLoader类的getResource(String name)和getResouceAsStream(String name) 两个加载类路径资源方法,提供一致的访问方式
ClassPathResource提供了三个构造器
1.public ClassPathResource(String path):使用默认的ClassLoader加载path类路径资源将加载当前ClassLoader类路径上相对于根路径的资源
@Test
public void testClasspathResourceByDefaultClassLoader() throws IOException{
Resource resource =
new ClassPathResource("com/lizhenhua/test3/db.properties");
if (resource.exists()) {
dumpStream(resource);
}
System.out.println("path:" + resource.getFile().getAbsolutePath());
Assert.assertEquals(false, resource.isOpen());
}
2.public ClassPathResource(String path,ClassLoader classLoader):使用指定的ClassLoader加载path类路径资源
使用指定的ClassLoader进行加载资源,将加载指定的ClassLoader类路径上相对于根路径的资源。也就是可以通过这种方式访问jar包中的资源文件
@Test
public void testClasspathResourceByClassLoader() throws IOException{
ClassLoader classLoader = TypeComparator.class.getClassLoader();
Resource resource =
new ClassPathResource("META-INF/INDEX.LIST",classLoader);
if (resource.exists()) {
dumpStream(resource);
}
System.out.println("path:" + resource.getDescription());
Assert.assertEquals(false, resource.isOpen());
}
public ClassPathResource(String path,Class<?> clazz):使用指定的类加载path类路径资源,将加载相对于当前类的路径的资源。
3.使用指定的类进行加载资源,将尝试加载相对于当前类的路径的资源
@Test
public void testClasspathResourceByClass() throws IOException{
Class clazz = this.getClass();
Resource resource =
new ClassPathResource("com/lizhenhua/test3/db.properties",clazz);
if (resource.exists()) {
dumpStream(resource);
}
System.out.println("path:" + resource.getDescription());
Assert.assertEquals(false, resource.isOpen());
Resource resource2 =
new ClassPathResource("db.properties",clazz);
if (resource2.exists()) {
dumpStream(resource2);
System.out.println(1);
}
System.out.println("path:" + resource2.getDescription());
Assert.assertEquals(false, resource2.isOpen());
}
resouce将加载com/lizhenhua/test3/com/lizhenhua/test3/db.properties资源
resouce2将加载com/lizhenhua/test3/db.properties资源
4.加载jar包里的资源,首先在当前类路径下找不到,最后才到jar包里找,而且在第一个jar包里找到的将被返回
@Test
public void testClasspathResourceFromJar() throws IOException{
Resource resource =
new ClassPathResource("overview.html");
if (resource.exists()) {
dumpStream(resource);
}
System.out.println("path:" + resource.getURL().getPath());
Assert.assertEquals(false, resource.isOpen());
}
如果当前类路径包含overview.html,在项目的resouces目录下,将加载该资源,否则将加载jar包里的overview.html,而且不能使用getFile(),应该使用getURL()因为资源不存在于文件系统而是存在于jar包里,URL类似于file:/D:/Program%20Files/Genuitec/MyEclipse%208.5/configuration/org.eclipse.osgi/bundles/87/1/.cp/data/3.0/lib/core/org.springframework.core-3.0.1.RELEASE-A.jar!/overview.html
类路径一般都是相对路径,即相对于类路径或相对于当前类的路径,因此如果使用/test1.properties 带前缀/的路径,将自动删除/得到test1.properties。
UrlResource代表URL资源,用于简化URL资源访问。isOpen永远返回false,可以多次读取资源
支持如下资源访问
http:通过标准的http协议访问web资源,如new UrlResource("http://地址");
ftp:通过ftp协议访问资源,如new UrlResource("ftp://地址");
file:通过file协议访问本地文件系统资源 如:new UrlResource(file:d:/test.txt)
@Test
public void testUrlResource() throws IOException{
Resource resource =
new UrlResource("http://www.hao123.com");
if (resource.exists()) {
dumpStream(resource);
}
System.out.println("path:" + resource.getURL().getPath());
Assert.assertEquals(false, resource.isOpen());
}
代表web应用资源,用于简化servlet容器的ServletContext接口的getResource操作和getResourceAsStream操作
代表Jboss虚拟文件系统资源
Jboss VFS框架是一个文件系统资源访问的抽象层,它能一致的访问物理文件系统、jar资源、zip资源、war资源等,VFS能把这些资源一致的映射到一个目录上,访问它们就像访问物理文件资源一样,而其实这些资源不存在与物理文件系统。
在示例之前需要准备一些jar包,在此我们使用JBoss VFS3版本,
将Jboss-logging.jar和jboss-vfs.jar两个jar包拷贝到我们项目的lib目录中,
ResourceLoader接口用于返回Resource对象;其实现可以看作是一个生产Resource的工厂类。
public interface ResourceLoader{
Resource getResource(String location){
ClassLoader getClassLoader();
}
}
getResource接口用于根据提供的location参数返回相应的Resource对象;而getClassLoader则返回加载这些Resource的ClassLoader。
Spring提供了一个适用于所有环境的DefaultResourceLoader实现,可以返回ClassPathResource、UrlResource;还提供一个用于web环境的ServletContextResourceLoader,它继承了DefaultResourceLoader的所有功能,又额外提供了获取ServletContextResource的支持。
ResourceLoader在进行加载资源时需要使用前缀来指定需要加载的资源类型:"classpath:path"表示返回ClasspathResource,"http:path"和"file:path"表示返回UrlResource资源,如果不加前缀则需要根据当前上下文来决定,DefaultResourceLoader默认实现可以加载classpath资源,如
@Test
public void testResourceLoader(){
ResourceLoader loader = new DefaultResourceLoader();
Resource resource = loader.getResource("classpath:com/lizhenhua/test3/db.properties");
//验证返回的是ClassPathResource
Assert.assertEquals(ClassPathResource.class, resource.getClass());
Resource resource2 = loader.getResource("file:com/lizhenhua/test3/db.properties");
//验证返回的是UrlResource
Assert.assertEquals(UrlResource.class, resource2.getClass());
Resource resource3 = loader.getResource("com/lizhenhua/test3/db.properties");
//验证默认返回可以加载ClasspathResource
Assert.assertTrue(resource3 instanceof ClassPathResource);
}
对于目前所有ApplicationContext都实现了ResourceLoader,因此可以使用其来加载资源
ClassPathXmlApplicationContext:不指定前缀将返回默认的ClassPathReource资源,否则将根据前缀来加载资源
FileSystemXmlApplicationContext:不指定前缀将返回FileSystemResource,否则将根据前缀来加载资源
WebApplicationContext:不指定前缀将返回ServletContextResource否则将根据前缀来加载资源
其他:不指定前缀根据当前上下文返回Resource实现,否则将根据当前前缀来加载资源
ResourceLoaderAware是一个标记接口,用于通过ApplicationContext上下文注入ResourceLoader。
public interface ResourceLoaderAware{
void setResourceLoader(ResourceLoader resourceLoader);
}
1.首先准备测试Bean,我们的测试Bean很简单只需实现该接口,然后通过回调将ResourceLoader保存下来就可以了。
package com.lizhenhua.test3;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.ResourceLoader;
public class ResourceBean implements ResourceLoaderAware {
private ResourceLoader resourceLoader;
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
public ResourceLoader getResourceLoader() {
return resourceLoader;
}
}
2.配置Bean定义
<bean class="com.lizhenhua.test3.ResourceBean"></bean>
3.测试
@Test
public void test(){
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
ResourceBean resourceBean = ctx.getBean(ResourceBean.class);
ResourceLoader loader = resourceBean.getResourceLoader();
Assert.assertTrue(loader instanceof ApplicationContext);
}
注意:此处loader instanceof ApplicationContext,说明了ApplicationContext就是个ResourceLoader
由于上述实现回调接口注入ResourceLoader的方式属于侵入式,所以不推荐上述方法,可以采用更好的自动注入方式,如byType和constructor。
通过回调或注入方式注入ResourceLoader,然后再通过ResourceLoader再来加载需要的资源对于只需要加载某个固定的资源是不是很麻烦,有没有更好的方法类似于前边实例中注入java.io.File类似方式呢?
Spring提供了一个PropertyEditor “ResourceEditor” 用于在注入的字符串和Resource之间进行转换。因此可以使用注入方式注入Resource
ResourceEditor 完全使用ApplicationContext根据注入的路径字符串获取相应的Resource。
示例:
首先编写测试bean
package com.lizhenhua.test3;
import org.springframework.core.io.Resource;
public class ResourceBean {
private Resource resource;
public Resource getResource() {
return resource;
}
public void setResource(Resource resource) {
this.resource = resource;
}
}
准备配置文件
<bean id="resourceBean1" class="com.lizhenhua.test3.ResourceBean">
<!-- 注意此处注入的路径没有前缀表示根据使用的ApplicationContext实现进行选择Resource实现 -->
<property name="resource" value="com/lizhenhua/test3/db.properties"></property>
</bean>
<bean id="resourceBean2" class="com.lizhenhua.test3.ResourceBean">
<property name="resource" value="classpath:com/lizhenhua/test3/db.properties"></property>
</bean>
测试
public void test(){
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
ResourceBean resourceBean1 = ctx.getBean("resourceBean1",ResourceBean.class);
ResourceBean resourceBean2 = ctx.getBean("resourceBean2",ResourceBean.class);
Assert.assertTrue(resourceBean1.getResource() instanceof ClassPathResource);
Assert.assertTrue(resourceBean2.getResource() instanceof ClassPathResource);
Resource resource = resourceBean1.getResource();
//资源已经注入进来了。之前的方式是通过代码显示注入资源,现在可以通过Spring自动注入机制注入资源,只需要在配置文件中进行配置即可
if(resource.exists()){
dumpStream(resource);
}
}
让我们回顾下,以前的加载资源方式
将加载当前ClassLoader类路径上相对于根路径的资源
@Test
public void testClasspathResourceByDefaultClassLoader() throws IOException{
Resource resource =
new ClassPathResource("com/lizhenhua/test3/db.properties");
if (resource.exists()) {
dumpStream(resource);
}
System.out.println("path:" + resource.getFile().getAbsolutePath());
Assert.assertEquals(false, resource.isOpen());
}
接下来我们深入ApplicationContext对各种Resource的支持,及如何使用更便利的加载资源方式
前面介绍的资源路径都是非常简单的一个路径匹配一个资源,Spring还提供了一种更强大的Ant模式通配符匹配,从一个路径匹配一批资源
?:匹配一个字符
*:匹配零个或多个字符串
**:匹配路径中的零个或多个目录
Spring在加载类路径资源时除了提供前缀"classpath:" 的来支持加载一个Resource,
还提供一个前缀"classpath*:"来支持加载所有匹配的类路径Resource。
Spring提供ResourcePatternResolver接口来加载多个Resource,该接口继承了Resourceloader并添加了Resource[] getResources(String locationPattern)用来加载多个Resource
public interface ResourcePatternResolver extends ResourceLoader{
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
Resource[] getResources(String locationPattern) throws IOException;
}
Spring提供了一个ResourcePatternResolver实现PathMatchingResourcePatternResolver,它是基于模式匹配的,默认使用AntPathMatcher进行路径匹配,它除了支持ResourceLoader支持的前缀外,还额外支持“classpath*:”用于加载所有匹配的类路径Resource,ResourceLoader不支持前缀"classpath*:"
首先做以下准备工作,在项目的resources创建META-INF目录,然后再其下创建一个INDEX.LIST文件。同时在D:\Program Files\Genuitec\MyEclipse 8.5\configuration\org.eclipse.osgi\bundles\87\1\.cp\data\3.0\lib\core\org.springframework.context-3.0.1.RELEASE-A.jar里也存在相同目录和文件。
用于加载类路径(包括jar包)中的一个且仅一个资源;对于多个匹配的也只返回一个,所以如果需要多个匹配的请考虑“classpath*:”前缀‘
@Test
public void testClasspathPrefix() throws IOException{
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
//只加载一个绝对匹配Resource,且通过ResourceLoader.getResource进行加载
Resource[] resources = resolver.getResources("classpath:META-INF/INDEX.LIST");
Assert.assertTrue(resources.length == 1);
//判断资源是否存在
if(resources[0].exists()){
//打印资源
dumpStream(resources[0]);
}
//只加载一个匹配的Resource,且通过ResourceLoader.getResource进行加载
resources = resolver.getResources("classpath:META-INF/*.LIST");
Assert.assertTrue(resources.length == 1);
//判断资源是否存在
if(resources[0].exists()){
//打印资源
dumpStream(resources[0]);
}
}
用于加载类路径(包括jar包)中的所有匹配的资源。带通配符的classpath使用ClassLoader的Enumeration<URL> getResources(String name)方法在查找通配符之前的资源,然后通过模式匹配来获取匹配的资源。如:classpath:META-INF/*.LIST 将首先加载通配符之前的目录META-INF,然后再遍历路径进行子路径匹配从而获取匹配的资源。
@Test
public void testClasspathAsteriskPrefix() throws IOException{
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
//将加载多个绝对匹配的所有Resource
//首先通过ClassLoader.getResources("META-INF")加载非模式匹配部分
//然后进行遍历模式匹配
Resource[] resources = resolver.getResources("classpath*:META-INF/INDEX.LIST");
Assert.assertTrue(resources.length > 1);
//遍历打印资源文件内容
for (Resource resource : resources) {
if (resource.exists()) {
System.out.println(resource.getDescription()+"读取开始");
dumpStream(resource);
System.out.println(resource.getDescription()+"读取结束");
}
}
//将加载多个模式匹配的Resource
resources = resolver.getResources("classpath*:META-INF/*.LIST");
Assert.assertTrue(resources.length>1);
}
加载一个或多个文件系统中的Resource。如:file:D:/*.txt将返回D盘下的所有txt文件
1.通过ResourcePatternResolver实现根据configLocation获取资源
public ClassPathXmlApplicationContext(String configLocation);
public ClassPathXmlApplicationContext(String... configLocations);
public ClassPathXmlApplicationContext(String[] configLocations,...);
根据提供的配置文件路径使用ResourcePatternResolver的getResources()接口通过匹配获取资源;即如“classpath:config.xml”
2.通过直接根据path直接返回ClasspathResource
public ClassPathXmlApplicationContext(String path,Class clazz);
public ClassPathXmlApplicationContext(String[] paths,Class clazz);
public ClassPathXmlApplicationContext(String[] paths,Class clazz,....);
根据提供的路径和clazz来构造ClassResource资源。即采用public ClassPathResouce(String path,Class<?> clazz)构造器获取资源
将加载相对于当前工作目录的configLocation位置的资源,注意在linux系统上不管configLocation是否带/,都作为相对路径;
而在window系统上如:D:/resourceInject.xml是绝对路径。因此在除非很必要的情况下,不建议使用该ApplicationContext
//linux系统,以下全是相对于当前vm路径进行加载
new FileSystemXmlApplicationContext("application.xml");
new FileSystemXmlApplicationContext("/application.xml");
//windows系统,将相对于当前vm路径进行加载
new FileSystemXmlApplicationContext("application.xml");
//windows系统,绝对路径方式加载
new FileSystemXmlApplicationContext("/application.xml");
此处还需要注意:在linux系统上,构造器使用的是相对路径,而ctx.getResource()方法如果以“/”开头则表示获取绝对路径资源,而不带前导”/”将返回相对路径资源。
//linux系统,相对于当前vm路径进行加载
ctx.getResource(“application.xml”);
//linux系统,绝对路径方式加载
ctx.getResource(“/root/appliction.xml”);
//windows系统,相对于当前vm路径进行加载
ctx.getResource(“application.xml”);
//window系统,绝对路径方式加载
ctx.getResource(“d:/appliction.xml”);
因此,如果需要加载绝对路径资源最好选择前缀file方式,将全部根据绝对路径加载。
如在linux系统ctx.getResource("file:/root/application.xml");
标签:
原文地址:http://blog.csdn.net/li286487166/article/details/51233600