在日常程序开发中,处理外部资源是很繁琐的事情,我们可能需要处理URL资源、File资源资源、ClassPath相关资源、服务器相关资源(JBoss AS 5.x上的VFS资源)等等很多资源。因此处理这些资源需要使用不同的接口,这就增加了我们系统的复杂性;而且处理这些资源步骤都是类似的(打开资源、读取资源、关闭资源),因此如果能抽象出一个统一的接口来对这些底层资源进行统一访问,是不是很方便,而且使我们系统更加简洁,都是对不同的底层资源使用同一个接口进行访问。 Spring 提供一个Resource接口来统一这些底层资源一致的访问,而且提供了一些便利的接口,从而能提供我们的生产力。
Resource接口
Spring的Resource接口代表底层外部资源,提供了对底层外部资源的一致性访问接口。
接口中的方法如下:
- exists:返回当前Resource代表的底层资源是否存在,true表示存在。
- isReadable:返回当前Resource代表的底层资源是否可读,true表示可读。
- isOpen:返回当前Resource代表的底层资源是否已经打开,如果返回true,则只能被读取一次然后关闭以避免资源泄露;常见的Resource实现一般返回false。
- getURL:如果当前Resource代表的底层资源能由java.util.URL代表,则返回该URL,否则抛出IOException。
- getURI:如果当前Resource代表的底层资源能由java.util.URI代表,则返回该URI,否则抛出IOException。
- getFile:如果当前Resource代表的底层资源能由java.io.File代表,则返回该File,否则抛出IOException。
- contentLength:返回当前Resource代表的底层文件资源的长度,一般是值代表的文件资源的长度。
- lastModified:返回当前Resource代表的底层资源的最后修改时间。
- createRelative:用于创建相对于当前Resource代表的底层资源的资源,比如当前Resource代表文件资源“d:/test/”则createRelative(“test.txt”)将返回表文件资源“d:/test/test.txt”Resource资源。
- getFilename:返回当前Resource代表的底层文件资源的文件路径,比如File资源“file://d:/test.txt”将返回“d:/test.txt”,而URL资源http://www.javass.cn将返回“”,因为只返回文件路径。
- getDescription:返回当前Resource代表的底层资源的描述符,通常就是资源的全路径(实际文件名或实际URL地址)。
Resource接口提供了足够的抽象,足够满足我们日常使用。而且提供了很多内置Resource实现:ByteArrayResource、InputStreamResource 、FileSystemResource 、UrlResource 、ClassPathResource、ServletContextResource、VfsResource等。
ResourceLoader 接口
ResourceLoader 接口是用来加载 Resource 对象的,换句话说,就是当一个对象需要获取 Resource 实例时,可以选择实现 ResourceLoader 接口。
public interface ResourceLoader {
Resource getResource(String location);
}
spring 里所有的应用上下文都是实现了 ResourceLoader 接口,因此,所有应用上下文都可以通过 getResource() 方法获取 Resource 实例。
当你在指定应用上下文调用 getResource() 方法时,而指定的位置路径又没有包含特定的前缀,spring 会根据当前应用上下文来决定返回哪一种类型 Resource。举个例子,假设下面的代码片段是通过 ClassPathXmlApplicationContext 实例来调用的:
Resource template = ctx.getResource("some/resource/path/myTemplate.txt");
那 spring 会返回一个 ClassPathResource 对象;类似的,如果是通过实例 FileSystemXmlApplicationContext 实例调用的,返回的是一个 FileSystemResource 对象;如果是通过 WebApplicationContext 实例的,返回的是一个 ServletContextResource 对象…… 如上所说,你就可以在指定的应用上下中使用 Resource 实例来加载当前应用上下文的资源。
还有另外一种场景里,如在其他应用上下文里,你可能会强制需要获取一个 ClassPathResource 对象,这个时候,你可以通过加上指定的前缀来实现这一需求,如:
Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");
类似的,你可以通过其他任意的 url 前缀来强制获取 UrlResource 对象:
Resource template = ctx.getResource("file:///some/resource/path/myTemplate.txt");
Resource template = ctx.getResource("http://myhost.com/resource/path/myTemplate.txt");
下面,给出一个表格来总结一下 spring 根据各种位置路径加载资源的策略:
前缀 | 样例 | 说明 |
classpath: | classpath:com/myapp/config.xml | 从类路径加载 |
file: | file:///data/config.xml | 将其作为 URL 对象,从文件系统加载 |
http: | http://myserver/logo.png | 将其作为 URL 对象 加载 |
(none) | /data/config.xml | 取决于底层的 ApplicationContext |
举个例子:
public class TestResource implements ApplicationContextAware {
private ApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
public void resource() throws IOException {
Resource resource = applicationContext.getResource("classpath:test.txt");
// ClassPathResource resource = (ClassPathResource) applicationContext.getResource("classpath:test.txt");
System.out.println(resource.getFilename());
System.out.println(resource.contentLength());
}
}
这个类实现了ApplicationContextAware 接口,这样就可以获取ApplicationContext 对象,用ApplicationContext 对象获取Resource 。
在类中定义一个resource()方法,在这个方法中获取一个text.txt文件(位置在src/main/resources下)并返回一个Resource对象,这里我是用的是classpath,所以返回的是ClassPathResource对象,只是这里我直接使用Resource接收。
现在在bean中配置一下:
<?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"
default-autowire="byName">
<bean id="testResource" class="com.mss.test.TestResource"></bean>
</beans>
写一个测试类:
@RunWith(BlockJUnit4ClassRunner.class)
public class TestOneInterface extends UnitTestBase{
public TestOneInterface() {
super("classpath*:spring-ioc.xml");
}
@Test
public void testAutoWriter() {
TestResource testResource=super.getBean("testResource");
try {
testResource.resource();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
运行结果:
test.txt
0
由于是一个空的文件,所以长度是0;
上面的classpath是什么意思?
看一个图片
这个路径就是classpath
我们也可以用file:
public class TestResource implements ApplicationContextAware {
private ApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
public void resource() throws IOException {
Resource resource = applicationContext.getResource("file:D:\\eclipse\\eclipse-workspace\\HelloSpring\\src\\main\\resources\\test.txt");
System.out.println(resource.getFilename());
System.out.println(resource.contentLength());
}
}
运行结果是一样的。
在看一下使用http:
public class TestResource implements ApplicationContextAware {
private ApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
public void resource() throws IOException {
Resource resource = applicationContext.getResource("https://www.duba.com/?f=liebao");
System.out.println(resource.getFilename());
System.out.println(resource.contentLength());
}
}
这里使用的是毒霸网址导航的链接,看一下运行结果:
?f=liebao
256405
如果前缀是空会怎么样?
前面已经说到当你在指定应用上下文调用 getResource() 方法时,而指定的位置路径又没有包含特定的前缀,spring 会根据当前应用上下文来决定返回哪一种类型 Resource。 看一下代码:
public class TestResource implements ApplicationContextAware {
private ApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
public void resource() throws IOException {
Resource resource = applicationContext.getResource("test.txt");
System.out.println(resource.getFilename());
System.out.println(resource.contentLength());
}
}
还记得我们的测试基类吗?
public class UnitTestBase {
private ClassPathXmlApplicationContext context;
private String springXmlpath;
public UnitTestBase() {}
public UnitTestBase(String springXmlpath) {
this.springXmlpath = springXmlpath;
}
@Before
public void before() {
if (StringUtils.isEmpty(springXmlpath)) {
springXmlpath = "classpath*:spring-*.xml";
}
try {
context = new ClassPathXmlApplicationContext(springXmlpath.split("[,\\s]+"));
context.start();
} catch (BeansException e) {
e.printStackTrace();
}
}
.......................
}
我们这里初始化ioc容器时,使用的是ClassPathXmlApplicationContext,所以Spring会把当前没有前缀的Resource 作为classpath前缀处理。