Spring 源码解析之资源抽象Resource接口

在Java提供的访问资源的API中,我们可以通过URL指定资源的位置,Java提供的API会使用不同类型的URLHandler去处理,但是它也有不足之处,并不能让所有人获取低层次的资源。比如没有提供从classpath或者ServletContext下访问资源的的标准URL实现。虽然可以为专门的URL前缀注册新的处理程序(类似于http:等前缀的现有处理程序),但这通常是相当复杂的,并且URL接口仍然缺少一些需要的功能,例如检查所指向的资源是否存在的方法。因此Spring提供了对资源访问的抽象,在Spring中使用Resource接口去访问低层次的资源,虽然Spring中大量使用了Resource接口,但实际的工作中我们也可以作为一个实用的工具类使用,让访问资源变得更加简单。因此我将这部分单独的拿出来讲解。如下是对Resource的定义:

public interface InputStreamSource { 
    //查找并加载资源,返回一个从资源中读取的InputStream。每次调用都会返回一个新的InputStream。流的关闭由调用者负责
    InputStream getInputStream() throws IOException; 
} 
public interface Resource extends InputStreamSource { 
    //判断资源是否存在
    boolean exists(); 
    //返回一个布尔值,该值指示此资源是否表示具有开放流的句柄。如果为true,则无法多次读取
    //InputStream,必须只读取一次,然后关闭以避免资源泄漏。对于所有常见的资源实现都将为false,
    //但InputStreamResource除外。
    boolean isOpen(); 
    //返回资源的URL实例
    URL getURL() throws IOException; 
    //返回资源的File实例
    File getFile() throws IOException; 
    //创建一个相对路径的资源
    Resource createRelative(String relativePath) throws IOException; 
    //返回资源名称
    String getFilename(); 
    //返回此资源的说明,在使用该资源时用于错误输出。通常表示完全限定的文件名或资源的实际URL
    String getDescription(); 
}

Resource是Spring中对资源(比如Spring的xml配置)的抽象,在Spring中被广泛使用,在很多方法需要使用资源的时候作为参数类型出现(比如ApplicationContext的很多实现中的构造方法)通常会使用一个字符串在构造方法中创建Resource,Resource会根据字符串选择合适的实现加载资源。后续我们会讲解Spring是如何加载资源的,这里不作讲解。

我们需要注意的是,Resource并没有实现任何功能,他只是将可能用到的功能抽象包装为Resource。例如,其实现UrlResource包装了一个URL,并使用包装好的URL来完成它的工作。除此之外Spring提供了几乎访问任何资源的Resource实现,如下为Resource实现的类图。

如上类的层次结构图是Resource的所有实现,包括URL、classpath、Context、FIle等等,下面我们介绍其中的一部分实现。

UrlResource封装了java.net.URL,并可用于访问通常可通过URL访问的任何对象,例如File、HTTP、FTP等。所有URL都具有标准化的字符串表示形式,以便于能根据给出的字符串表明资源的类型。这包括file:用于访问文件系统路径,http:用于通过http协议访问资源,ftp:用于通过ftp访问资源等等。

 ClassPathResource表示应从类路径获取的资源。它使用线程上下文类加载器、给定的类加载器或给定的类来加载资源。如果类路径资源驻留在文件系统中,而不是驻留在jar中且尚未扩展到文件系统(通过servlet引擎或其他环境)的类路径资源,它支持对java.io.File的解析。

 FileSystemResource是基于java.io.File操作的Resource实现,它支持File和URL的解决方案。

ServletContextResource是ServletContext资源的Resource实现,它将相对路径当作web应用程序根目录中。它始终支持流访问和URL访问,但是当web应用程序存档扩展并且资源实际位于文件系统上时,只允许java.io.File访问。不管它是否在文件系统中还是直接从JAR或其他地方(比如DB)访问(这是可以想象的)实际上取决于Servlet容器

InputStreamResource是对给定的InputStream的Resource的实现,只有在没有具体的资源实现适用的情况下才使用该方法。在可能的情况下,我们应该首选ByteArrayResource或基于文件的Resource实现。与其他Resource的实现不同的是,这是一个已打开资源的描述符,因此从isOpen()返回true。因此如果需要将资源描述符保存在某个地方,或者需要多次读取一个流,请使用其他的Resource实现,比如ByteArrayResource。

ByteArrayResource这是对给定字节数组的Resource的实现。它为给定的字节数组创建一个ByteArrayInputStream。它对于从任何给定的字节数组加载内容非常有用。而不必使用只能读取一次的InputStreamResource。

前面我们介绍了几种不同的Resource的实现,那么Spring是如何加载Resource的呢?下面我们介绍Spring是如何加载Resource的。在Spring中Resource的加载被抽象为ResourceLoader的接口,它提供了一个getResource方法用于返回一个Resource实例,其定义如下:

public interface ResourceLoader { 
    Resource getResource(String location); 
}

所有的应用上下文都实现了ResourceLoader接口,比如ClassPathXmlApplicationContext,FileSystemXmlApplicationContext,WebApplicationContext等,我们可以通过这些实现获取一个Resource实例,我们使用ClassPathXmlApplicationContext作为示例,类的层次关系图与代码如下所示:

Resource template = ctx.getResource("some/resource/path/myTemplate.txt");

上面的代码中,如果ctx是ClassPathXmlApplicationContext实例,他将返回一个ClassPathResource;如果ctx是FileSystemXmlApplicationContext instance, 它将返回一个FileSystemResource;如果是WebApplicationContext, 它将返回一个ServletContextResource等等。也可以在获取Resource的指定Resource的类型,此时会返回对应类型的Resource实现,代码如下所示:

Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt"); 
Resource template = ctx.getResource("file:///some/resource/path/myTemplate.txt");
Resource template = ctx.getResource("http://myhost.com/resource/path/myTemplate.txt"); 

前面我们说所有的应用程序上下文都实现了ResourceLoader,我们可以通过其获取ResourceLoader实例,除此之外我们还可以通过ResourceLoaderAware接口获取ResourceLoader实例,其定义如下:

public interface ResourceLoaderAware { 
    void setResourceLoader(ResourceLoader resourceLoader); 
}

当一个类实现了ResourceLoaderAware接口,并且该类由Spring容器管理时,该类会被应用上下文(application context)当作ResourceLoaderAware,然后调用setResourceLoader(ResourceLoader)提供一个ResourceLoader实例,其实Spring很多内部的实现都可以通过该方案提供,比如ApplicationContextAware会提供一个ApplicationContext实例,该类的用法这里不做介绍,可以参考博客:Spring中的Aware接口的使用——ApplicationContextAware与BeanNameAware

除此之外,我们还可以通过Bean自动注入一个Resource实例,如果在Bean中我们有一个字段为Resource类型,则可以通过Bean注入,实例如下:

<bean id="myBean" class="..."> 
    <property name="template" value="some/resource/path/myTemplate.txt"/> 
</bean> 

因为上面配置的资源没有前缀,因此应用程序上下文本身将作为ResourceLoader,资源本身将通过ClassPathResource加载,FileSystemResource或ServletContextResource,具体取决于上下文。当然我们也可以指定具体的类型,示例如下:

<bean id="myBean" class="..."> 
    <property name="template" value="file:///some/resource/path/myTemplate.txt"/> 
</bean> 

应用程序上下文构造函数(对于特定的应用程序上下文类型)通常采用字符串或字符串数组作为资源(如构成上下文定义的XML文件)的位置路径。当这样的位置路径没有前缀时,从该路径构建并用于加载bean定义的特定资源类型取决于并适合于特定的应用程序上下文。例如,如果按如下方式创建ClassPathXmlApplicationContext:

ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");

上面的方法会从classpath下加载资源并且使用ClassPathResource,但是如果你创建了一下FileSystemXmlApplicationContext,将会从文件系统位置加载,如下示例相对于当前工作目录。

ApplicationContext ctx  =  new FileSystemXmlApplicationContext("conf/appContext.xml");

如果我们在传入的参数中添加前缀或者URL,将会覆盖默认的类型,比如下面的示例会从classpath下加载资源:

ApplicationContext ctx =  new FileSystemXmlApplicationContext("classpath:conf/appContext.xml"); 

应用程序上下文构造函数值中的资源路径可以是一个简单的路径(如shownabove所示),它与目标资源有一对一的映射,也可以包含特殊的“classpath*:”前缀和/或内部Ant样式的正则表达式(使用Spring的PathMatcher实用程序进行匹配)。如下为Ant样式的资源路径示例:

/WEB-INF/*-context.xml 
com/mycompany/**/applicationContext.xml 
file:C:/some/path/*-context.xml 
classpath:com/mycompany/**/applicationContext.xml

猜你喜欢

转载自blog.csdn.net/wk19920726/article/details/108782929
今日推荐