文章基于Spring3.1版本
在日常程序开发中,处理外部资源是很繁琐的事情,因为我们可能需要处理不同类型的资源如URL资源、File资源、ClassPath资源等。另外处理这些资源需要使用不同的接口,但步骤都是类似的(打开、读写、关闭等),因此如果能抽象出一个统一的接口来对这些底层资源进行统一访问,会使系统更加简洁,都是对不同的底层资源使用同一个接口进行访问。
Spring 就提供一个Resource接口来统一这些底层资源一致的访问,而且提供了一些便利的接口。
下面来看一下Spring3.1中Resource的继承体系:
从源码看一下InputStreamSource接口和Resource接口都定义哪些方法。
InputStreamSource接口:
package org.springframework.core.io; import java.io.IOException; import java.io.InputStream; public interface InputStreamSource { InputStream getInputStream() throws IOException; }
getInputStream:返回一个新的资源对应的java.io. InputStream字节流,调用者在使用完毕后必须关闭该资源。
Resource接口:
package org.springframework.core.io; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URL; public interface Resource extends InputStreamSource { boolean exists(); boolean isReadable(); boolean isOpen(); URL getURL() throws IOException; URI getURI() throws IOException; File getFile() throws IOException; long contentLength() throws IOException; long lastModified() throws IOException; Resource createRelative(String relativePath) throws IOException; String getFilename(); String getDescription(); }
UrlResource内部封装了一个java.net.URL,可以用于访问可通过URL访问的任何资源对象,支持的URL协议如下:
HTTP:通过标准的HTTP协议,例如:new UrlResource("http://xxxx.xxx,xxx");
FTP:通过FTP协议访问资源,例如:new UrlResource("ftp://xxx/xxx/xxx");
File:通过文件协议访问本地资源,例如:new UrlResource("file:c:/xxx/xxx");跟FileSystemResource作用一样。
ClassPathResource代表一个需要从类路径获取的Resource,它可以使用给定的类加载器、给定类或者默认的当前线程上下文类加载器来加载资源。
FileSystemResource内部封装了java.io.File,getInputStream方法返回的是该File对象对应的文件流FileInputStream。
代表Web应用中的资源,可以翻译相对于Web应用根目录的相对路径。
InputStreamResource代表java.io.InputStream字节流,getInputStream方法直接返回该字节流,与其它的Resource实现不同的是isOpen方法永远返回true,所以不能多次读取该字节流,如果可能,最好使用ByteArrayResource或者其它任何基于文件的Resource实现来代替。
ByteArrayResource代表byte[]数组资源,同样内部封装了一个byte数组,getInputStream方法将返回给定byte数组对应的ByteArrayInputStream。在加载给定字节型数组内容时比较有用,不需要再去使用一次性的InputStreamResource。
public interface ResourceLoader { Resource getResource(String location); }
当我们调用ApplicationContext的getResource方法获取Resource时,如果参数location没有指定前缀,得到的Resource类型根据ApplicationContext的类型来定,比如调用ClassPathXmlApplicationContext实例的getResource方法返回的就是ClassPathResource,如果同样的方法在FileSystemXmlApplicationContext实例上调用,则返回的是FileSystemResource。
另外,我们以指定前缀来强行得到指定的Resource,而不用考虑当前是哪种类型的ApplicationContext,比如在FileSystemXmlApplicationContext上这样调用:
ctx.getResource("classpath:some/resource/path/myTemplate.txt")
ResourceLoaderAware是一个标记接口,用于通过ApplicationContext上下文获取Bean的时候为实现该接口的Bean注入ResourceLoader。
public interface ResourceLoaderAware { void setResourceLoader(ResourceLoader resourceLoader); }
假设我们需要在Bean使用某个资源,当然可以采用第4步说的实现ResourceLoaderAware接口得到ResourceLoader,然后使用它来得到资源。其实Spring还提供了资源注入来简化这一操作,就是PropertyEditor这个JavaBean,它可以将String类型的路径转化为Resource对象,所以我们可以将资源注入到Bean中。
<bean id="myBean" class="..."> <property name="template"value="some/resource/path/myTemplate.txt"/> </bean>
<property name="template"value="classpath:some/resource/path/myTemplate.txt"> <property name="template"value="file:/some/resource/path/myTemplate.txt"/>
一个特定的ApplicationContext构造函数通常会接收一个字符串或者一个字符串数组作为Resource资源路径,跟ResourceLoader接口一样,如果我们没有加前缀,内部构建什么类型的Resource来加载BeanDefinition是根据ApplicationContext的类型来定的,比如:
ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");
需要注意的是,如果我们在路径上使用的前缀,如classpath或者其它的URL前缀,这样会覆盖ApplicationContext构造时使用的默认Resource类型,比如:
ApplicationContext ctx = new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");
Spring还支持通配符加载匹配的一批Resource资源。
“?”:匹配一个字符,比如“application-context-?.xml“可以匹配”application-context-a.xml“;
"*":匹配零或多个字符,比如“application-context-*.xml”可以匹配“application-context-service.xml”、“application-context-dao.xml”等资源;
“**”:匹配零或多个目录,比如“/com/**/service/application-context-?.xml”可以匹配“/com/service/application-context-?.xml”、““/com/zyh/service/application-context-?.xml””等资源。
Spring在加载类路径资源时提供了前缀“classpath:”的来支持加载一个Resource,其实Spring还提供了另一个前缀“classpath*:”来支持加载所有匹配的类路径Resource资源,比如:
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath*:conf/appContext.xml");