在日常程序开发中,处理外部资源是很繁琐的事情,我们可能需要处理URL资源、File资源资源、ClassPath相关资源、服务器相关资源(JBoss AS 5.x上的VFS资源)等等很多资源。因此处理这些资源需要使用不同的接口,这就增加了我们系统的复杂性;而且处理这些资源步骤都是类似的(打开资源、读取资源、关闭资源),因此如果能抽象出一个统一的接口来对这些底层资源进行统一访问,是不是很方便,而且使我们系统更加简洁,都是对不同的底层资源使用同一个接口进行访问。 Spring 提供一个Resource接口来统一这些底层资源一致的访问,而且提供了一些便利的接口,从而能提供我们的生产力。

Resource接口

Spring的Resource接口代表底层外部资源,提供了对底层外部资源的一致性访问接口。

接口中的方法如下:

怎么创建resources文件夹_xml

  • 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是什么意思?

看一个图片

怎么创建resources文件夹_怎么创建resources文件夹_02


这个路径就是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前缀处理。