码迷,mamicode.com
首页 > 编程语言 > 详细

Spring的资源详解

时间:2016-04-29 19:23:25      阅读:255      评论:0      收藏:0      [点我收藏+]

标签:

一、Spring的资源详解

1.1引言

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

1.2Resource接口

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等。

1.3内置Resource实现

1.ByteArrayResource

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

2.InputStreamResource

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());
    }

3.FileSystemResource

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());
    }

4.ClassPathResource

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

5.UrlResource

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());
        }

6.ServletContextResource

代表web应用资源,用于简化servlet容器的ServletContext接口的getResource操作和getResourceAsStream操作

7.VfsResource

代表Jboss虚拟文件系统资源

Jboss VFS框架是一个文件系统资源访问的抽象层,它能一致的访问物理文件系统、jar资源、zip资源、war资源等,VFS能把这些资源一致的映射到一个目录上,访问它们就像访问物理文件资源一样,而其实这些资源不存在与物理文件系统。
在示例之前需要准备一些jar包,在此我们使用JBoss VFS3版本,
将Jboss-logging.jar和jboss-vfs.jar两个jar包拷贝到我们项目的lib目录中,

1.3访问Resource

1.ResourceLoader接口

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实现,否则将根据当前前缀来加载资源

2.ResourceLoaderAware接口

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。

3.注入Resource

通过回调或注入方式注入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的支持,及如何使用更便利的加载资源方式

1.4Resource通配符路径

1.使用路径通配符加载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里也存在相同目录和文件。

一、classpath:

用于加载类路径(包括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]);
        }
    }

二、“classpath*:”

用于加载类路径(包括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);

    }

三、file:

加载一个或多个文件系统中的Resource。如:file:D:/*.txt将返回D盘下的所有txt文件

四、无前缀:

通过ResourceLoader实现加载一个资源

2.注入Resource数组

1.5ApplicationContext实现对各种Resource的支持

一、ClassPathXmlApplicationContext:默认将通过classpath进行加载返回ClassPathResource。

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)构造器获取资源

二、FlilSystemXmlApplicationContext:

将加载相对于当前工作目录的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");

Spring的资源详解

标签:

原文地址:http://blog.csdn.net/li286487166/article/details/51233600

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!