ResourceLoading服务指南
1.前言
Webx框架中,包含了一套查找和装载资源的服务。它从Spring ResourceLoader机制中扩展而来,并且和Spring框架融为一体。你无需写特别的Java代码,就可以利用Webx所提供的新的ResourceLoading机制。
1.1.资源概述
在一个稍具规模的应用程序中,经常要做的一件事,就是查找资源、读取资源的内容。这里所谓的“资源”,是指存放在某一介质中,可以被程序利用的文件、数据。例如,基于Java的WEB应用中,常用到下面的资源:
²  配置文件:*.xml、*.properties等。
²  Java类文件:*.class。
²  JSP页面、Velocity模板文件:*.jsp、*.vm等。
²  图片、CSS、JavaScript文件:*.jpg、*.css、*.js等。
1.1.1.如何表示资源
在Java中,有多种形式可以表示一个资源:
²  java.io. File —— 可代表文件系统中的文件或目录。
n  文件系统中的文件:“c:\config.sys”。
n  文件系统中的目录:“c:\windows\”。
²  java.net.URL(统一资源定位符)。
n  文件系统中的文件:c:\config.sys,可以表示成URL:“file:///c:/config.sys”。
n  文件系统中的目录:c:\windows\,可以表示成URL:“file:///c:/windows/”。
n  远程WEB服务器上的文件:“http://www.springframework.org/schema/beans.xml”。
n  Jar包中的某个文件,可以表示成URL:“jar:file:///c:/my.jar!/my/file.txt”。
²  java.io. InputStream输入流对象 —— 可直接访问资源的内容。
n  文件系统中的文件:c:\config.sys,可以转换成输入流:“new FileInputStream("c:\\config.sys")”。
n  远程WEB服务器上的文件,可以转换成输入流:
new URL("http://www.springframework.org/schema/beans.xml").openStream()”。
n  Jar包中的某个文件,可以转换成输入流:
new URL("jar:file:///c:/my.jar!/my/file.txt").openStream()”。
并不是所有的资源,都可以表现成上述所有的形式。例如,Windows文件系统中的目录,无法表现为输入流。而远程WEB服务器上的文件无法转换成File对象。多数的资源,都可以表现成URL形式。但也有例外,例如,数据库中的数据也可以看成资源,但一般来说无法转换成URL。
1.1.2.如何找到资源
在Java中,有如下几种常用的手段用来找到资源。
²  通过CLASSPATH找到资源。对于所有Java程序员而言,将资源放在CLASSPATH是最简单的。我们只要把所需的资源文件打包到Jar文件中,或是在运行java时,用-classpath参数中指定的路径中。接下来我们就可以用下面的代码来访问这些资源:
URL resourceURL =getClassLoader().getResource("java/lang/String.class");
InputStream resourceContent =getClassLoader().getResourceAsStream("java/lang/String.class");
²  通过文件系统找到资源。下面的代码从文件资源中读取信息:
File resourceFile = new File("c:\\test.txt");
InputStream resourceContent = new FileInputStream(resourceFile);
²  通过ServletContext找到Web应用中的资源。Web应用既可以打包成war文件,也可以展开到任意目录中。因此Webapp中的资源(JSP、模板、图片、Java类、配置文件)不总是可以用文件的方式存取。虽然Servlet API提供了ServletContext.getRealPath()方法,用来取得某个资源的实际文件路径,但该方法很可能返回null —— 取决于应用服务器的实现,以及Web应用的布署方式。更好的获取WEB应用资源的方法如下:
URL resourceURL = servletContext.getResource("/WEB-INF/web.xml");
InputStream resourceContent = servletContext.getResourceAsStream("/WEB-INF/web.xml");
²  通过URL找到Jar/Zip文件中的资源。下面的代码读取被打包在Jar文件中的资源信息:
URL jarURL = new File(System.getProperty("java.home") +"/lib/rt.jar").toURI().toURL();
URL resourceURL = new URL("jar:" + jarURL + "!/java/lang/String.class");
InputStream resourceContent = resourceURL.openStream();
还可以想到其它找到资源的方法,例如从数据库中查找资源。
1.1.3.如何遍历资源
有时候,我们不知道资源的路径,但希望能找出所有符合条件的资源,这个操作叫作遍历。例如,找出所有符合pattern  “/WEB-INF/webx-*.xml”的配置文件。
²  遍历文件系统。
File parentResource = new File("c:\windows");
File[] subResources = parentResource.listFiles();
²  遍历WEB应用中的资源。
Set<String> subResources = servletContext.getResourcePaths("/WEB-INF/");
²  遍历Jar/zip文件中的资源。
File jar = new File("myfile.jar");
ZipInputStream zis = new ZipInputStream(new FileInputStream(jar));
 
try {
    for (ZipEntry entry = zis.getNextEntry(); entry != null; entry = zis.getNextEntry()) {
        // visit entry
    }
finally {
    zis.close();
}
并非所有类型的资源都支持遍历操作。通常遍历操作会涉及比较复杂的递归算法。
1.2.在应用程序中访问资源的问题
首先,资源表现形式的多样性,给应用程序的接口设计带来一点麻烦。假如,我写一个ConfigReader类,用来读各种配置文件。那么我可能需要在接口中列出所有的资源的形式:
public interface ConfigReader {
    Object readConfig(File configFile);
    Object readConfig(URL configURL);
    Object readConfig(InputStream configStream);
}
特别是当一个通用的框架,如Spring和Webx,需要在对象之间传递各种形式的资源的时候,这种多样性将导致很大的编程困难。
其次,有这么多种查找资源和遍历资源的方法,使我们的应用程序和资源所在的环境高度耦合。这种耦合会妨碍代码的可移植性和可测试性。
比如,我希望在非WEB的环境下测试一个模块,但这个模块因为要存取Web应用下的资源,而引用了ServletContext对象。在测试环境中并不存在ServletContext而导致该模块难以被测试。再比如,我希望测试的一个模块,引用了classpath下的某个配置文件(这也是一种耦合)。而我希望用另一个专为该测试打造的配置文件来代替这个文件。由于原配置文件是在classpath中,因此是难以替换的。
对于不打算重用的应用程序来说,这个问题还不太严重:大不了我预先设定好,就从这个地方,以固定的方式存取资源。然而就算这样,也是挺麻烦的。有的人喜欢把资源放在某个子目录下,有的人喜欢把资源放在CLASSPATH下,又有人总是通过ServletContext来存取Web应用下的资源。当你要把这些不同人写的模块整合起来时,你会发现很难管理。
一种可能发生的情形是,因为某些原因,环境发生改变,导致资源的位置、存取方式不得不跟着改变。比如将老系统升级为新系统。但一些不得不继续使用的老代码,由于引用了旧环境的资源而不能工作 —— 除非你去修改这些代码。有时修改老代码是很危险的,可能导致不可预知的错误。又比如,由于存储硬件的改变或管理的需要,我们需要将部分资源移到另一个地方(我们曾经将Web页面模板中的某个子目录,移动到一个新的地方,因为这些模板必须由新的CMS系统自动生成)。想要不影响现有代码来完成这些事,是很困难的。
1.3.Spring的ResourceLoader机制
Spring内置了一套ResourceLoader机制,很好地解决了上述大部分问题。
1.3.1.Resource接口
Spring将所有形式的资源表现概括成一个Resource接口。如下所示(下面的接口定义是被简化的,有意省略了一些东西,以便突出重点):
public interface Resource {
    InputStream getInputStream();
    URL getURL();
    File getFile();
    boolean exists();
}
Resource接口向应用程序屏蔽了资源表现形式的多样性。于是,前面例子中的ConfigReader就可以被简化成下面的样子:
public interface ConfigReader {
    Object readConfig(Resource configResource);
}
Spring自己也会利用Resource接口来初始化它的ApplicationContext:
public abstract class AbstractXmlApplicationContext extends ... {
    ...
    protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) {
        Resource[] configResources = getConfigResources();
        ...
    }
 
    protected Resource[] getConfigResources();
}
1.3.2.ResourceLoader和ResourcePatternResolver接口
Spring利用ResourceLoader接口取得单一的资源对象,利用ResourcePatternResolver遍历并取得多个符合指定pattern的资源对象。
Webx3_resourceLoading.png
这个设计向应用程序屏蔽了查找和遍历资源的复杂性。
1.3.3.在代码中取得资源
1.3.3.1.通过ResourceLoader取得资源
public class MyBean implements ResourceLoaderAware {
    private ResourceLoader loader;
 
    public void setResourceLoader(ResourceLoader loader) {
        this.loader = loader;
    }
 
    public void func() {
        Resource resource = loader.getResource("myFile.xml");
        ...
    }
}
要取得资源,必须要拿到ResourceLoader对象。通过ResourceLoaderAware接口拿到ResourceLoader是最简单的方法。然后,代码就可以利用这个ResourceLoader来取得资源。
1.3.3.2.直接注入资源
另一种更简便的方法是,将资源直接“注入”到bean中 —— 你不需要手工调用ResourceLoader来取得资源的方式来设置资源。例如:
public class MyBean {
    private URL resource;
 
    public void setLocation(URL resource) {
        this.resource = resource;
    }
 
    ……
}
Spring配置文件可以这样写:
<bean id="myBean" class="MyBean">
    <property name="location" value="myFile.xml" />
</bean>
这样,Spring就会把适当的myFile.xml所对应的资源注入到myBean对象中。此外,Spring会自动把Resource对象转换成URL、File等普通对象。在上面的例子中,MyBean并不依赖于Resource接口,只依赖于URL类。
对代码稍作修改:
    public void setLocations(URL[] resources) {
        this.resources = resources;
    }
配置文件:
    <property name="locations" value="WEB-INF/webx-*.xml" />
就可以直接等到所有符合pattern “WEB-INF/webx-*.xml”的配置文件了。显然这是通过ResourcePatternResolver取得的。
1.3.4.Spring从哪里取得Resources?
Spring是如何找到资源文件(例如上面例子中的“myFile.xml”文件)的呢?对于不同的ApplicationContext的实现, 会有不同的查找方法。
Webx3_resourceLoading.png
Spring本身提供了如下几种ApplicationContext的实现,每种ApplicationContext的实现,也同时扩展了ResourceLoader和ResourcePatternResolver接口,从而对应着不同的装载资源的方法:
1.3.4.1.ClassPathXmlApplicationContext
ClassPathXmlApplicationContext支持从classpath中查找资源。假如我以下面的方式启动Spring:
ApplicationContext context = newClassPathXmlApplicationContext("beans.xml");
那么系统将会从classpath中查找myFile.xml文件。相当于:
URL resource = getClassLoader().getResource("myFile.xml");
1.3.4.2.FileSystemXmlApplicationContext
FileSystemXmlApplicationContext支持从文件系统中查找资源。假如我以下面的方式启动Spring:
ApplicationContext context = newFileSystemXmlApplicationContext("beans.xml");
那么系统将会在文件系统中查找myFile.xml文件。相当于:
File resource = new File("myFile.xml");
1.3.4.3.XmlWebApplicationContext
XmlWebApplicationContext支持从webapp上下文中(也就是ServletContext对象中)查找资源。假如我以下面的方式启动Spring:
ApplicationContext context = new XmlWebApplicationContext();
或者在/WEB-INF/web.xml中添加如下配置:
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/beans.xml</param-value>
</context-param>
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
那么系统将会在Web应用的根目录中查找myFile.xml。相当于:
URL resource = servletContext.getResource("myFile.xml");
1.3.4.4.Classpath和Classpath*前缀
除了用ClassPathXmlApplicationContext来装载classpath下的资源,所有的Spring ApplicationContext实现都支持从classpath中装载资源:
l  使用classpath前缀:“classpath:myFile.xml” —— 在classpath中查找资源myFile.xml。
l  使用classpath*前缀:“classpath*:/META-INF/my*.xml” ——在classpath中查找所有符合pattern的资源。
1.3.5.Spring ResourceLoader的缺点
l  鱼和熊掌不可得兼
Spring ResourceLoader是由ApplicationContext来实现的。而你一次只能选择一种ApplicationContext的实现 —— 如果你选择了XmlWebApplicationContext,你就放弃了FileSystemXmlApplicationContext;反之亦然。
在WEB应用中,由于Spring使用了XmlWebApplicationContext,因此你就无法装载文件系统下的资源。
l  不透明性
你必须用“绝对路径”来引用Spring中的资源。
假如你使用FileSystemXmlApplicationContext来访问资源,你必须使用绝对路径来访问文件或目录资源。这妨碍了应用程序在不同系统中布署的自由。因为在不同的系统中,例如Windows和Linux,文件的绝对路径是不同的。为了系统管理的需要,有时也需要将文件或目录放在不同于开发环境的地方。
即便是访问WEB应用下的资源,或者是classpath下的资源,你也必须明确指出它们的位置,例如:WEB-INF/myFile.xml、classpath:myFile.xml等。如果我希望把classpath:myFile.xml挪到另一个物理位置,就必须修改所有的引用。
l  无扩展性
我无法在Spring ResourceLoader机制中增加一种新的装载资源的方法。例如,我希望把资源文件保存在数据库中,并用ResourceLoader来取得它。
2.ResourceLoading服务
2.1.替换Spring ResourceLoader(Drop-in Replacement of Spring ResourceLoader)
Webx ResourceLoading服务可作为Spring ResourceLoader机制的替代品(Drop-in Replacement):
l  当你不使用它时,Spring原有的ResourceLoader不受影响;
l  当你在spring配置文件中添加ResourceLoading服务时,ResourceLoader即被切换到新的机制。新的机制可兼容原有的Spring配置和代码,但支持更多的资源装载方式,以及支持重命名、重定向资源。
只要在/WEB-INF/webx.xml中包含下面的配置:
<resource-loading
        xmlns="http://www.alibaba.com/schema/services"
        xmlns:res-loaders="http://www.alibaba.com/schema/services/resource-loading/loaders">
 
    <resource-alias pattern="/" name="/webroot" />
 
    <resource pattern="/webroot" internal="true">
        <res-loaders:webapp-loader />
    </resource>
    <resource pattern="/classpath" internal="true">
        <res-loaders:classpath-loader />
    </resource>
 
</ resource-loading>
这段配置使得ResourceLoading服务的行为和原来的Spring ResourceLoader完全相同:
l  classpath:和classpath*:前缀所定义的资源是由Spring原生支持的,不受影响。
l  /myFile.xml 代表着WEB应用根目录下的文件:/webroot/myFile.xml。
l  /WEB-INF/myFile.xml代表着WEB-INF目录下的文件:/webroot/WEB-INF/myFile.xml。
2.2.定义一种新资源
定义一种新资源,需要回答两个问题:
1.         资源的名称是什么?
2.         资源在哪里(或如何装载资源)?
下面的例子定义了一种新的资源,它的名称是“/jdk/*”,通过“file-loader”从文件系统${java.home}文件夹中装载。
<resource-loading
        xmlns="http://www.alibaba.com/schema/services"
        xmlns:res-loaders="http://www.alibaba.com/schema/services/resource-loading/loaders">
    ...
 
    <resource pattern="/jdk">
        <res-loaders:file-loader basedir="${java.home}" />
    </resource>
 
</resource-loading>
前文讲过,Spring可以直接把资源注入到对象中。使用ResourceLoading服务以后,你仍然可以这样做。下面的配置把JDK目录下的tools.jar文件(如果存在的话)的URL注入到myBean中:
<bean id="myBean" class="MyBean">
    <property name="location" value="/jdk/lib/tools.jar" />
</bean>
2.3.重命名资源
重命名资源是指对于即有的资源,改变其名字。
为什么需要修改资源的名字?理由是:取消资源名称和环境的关联性。有一些资源的名称,具有明显的环境相关性,比如:
l  classpath:myFile.xml或者/classpath/myFile.xml —— 从资源的名称就可以看出,这些资源是从classpath中装载的。
l  /WEB-INF/myFile.xml或者/webroot/WEB-INF/myFile.xml —— 从资源的名称可以看出,这些资源是从web应用中装载的。
使用和环境相关的资源名称有什么问题?问题就是,当环境改变时,应用代码会受到影响。最常见的一种状况是:单元测试时,用于测试的资源文件往往被放在专供测试的目录中,这些目录和应用运行时的环境是不同的 —— 你可能希望将classpath:myFile.xml或/WEB-INF/myFile.xml改成/src/test/config/myFile.xml。
对资源重命名就可以解决这类问题:
l  将classpath:myFile.xml或者/WEB-INF/myFile.xml重命名成:myapp/conf/myFile.xml。
l  在测试环境中,将myapp/conf/myFile.xml名称指向另一个物理地址src/test/config/myFile.xml。
重命名资源是通过alias别名实现的:
<resource-loading
        xmlns="http://www.alibaba.com/schema/services"
        xmlns:res-loaders="http://www.alibaba.com/schema/services/resource-loading/loaders">
    ...
 
    <resource-alias pattern="/myapp/conf" name="/webroot/WEB-INF" />
 
    <resource pattern="/webroot" internal="true">
        <res-loaders:webapp-loader />
    </resource>
 
</resource-loading>
以上配置定义了一个资源的别名:/myapp/conf。当你查找/myapp/conf/myFile.xml时,ResourceLoading服务实际上会去找/webroot/WEB-INF/myFile.xml。
需要注意的是,/webroot的资源定义中,有一个attribute:internal=true。这是一个可选项,当它的值为true时,代表它所修饰的资源是不能被外界所直接访问的。例如,你想直接在myBean中注入/webroot/WEB-INF/myFile.xml是不行的。把internal选项设成true,可以让强制用户转向新的资源名称。Internal参数的默认值为false,意味着,新旧两种名称同时可用。
2.4.重定向资源
重定向资源的意思是,将部分资源名称,指向另外的地址。
一个常见的需求是这样的:通常我们会把页面模板保存在WEB应用的/templates目录下。但是有一批模板是由外部的CMS系统生成的,这些模板文件不可能和WEB应用打包在一起,而是存放在某个外部的目录下的。我们希望用/templates/cms来引用这些模板。
由于/templates/cms只不过是/templates的子目录,所以如果没有ResourceLoading服务所提供的重定向功能,是不可能实现上述功能的。用ResourceLoading服务重定向的配置如下:
<resource-loading
        xmlns="http://www.alibaba.com/schema/services"
        xmlns:res-loaders="http://www.alibaba.com/schema/services/resource-loading/loaders">
    ...
 
    <resource-alias pattern="/templates" name="/webroot/templates" />
 
    <resource pattern="/templates/cms">
        <res-loaders:file-loader basedir="${cms_root}" />
    </resource>
 
    ...
</resource-loading>
通过上述配置,/templates/cms目录就被重定向到外部的某个文件目录中了。重要的是,访问/templates目录的应用程序并不知道这个资源重定向的存在,当cms所对应的实际目录被改变时,应用程序也不会受到任何影响 —— 这个正是ResourceLoading服务的“魔法”。
重定向资源也可以通过alias别名来实现。
2.5.利用通配符来匹配资源
ResourceLoading服务支持用通配符来匹配资源名称或资源别名(alias)。通配符定义如下:
l  *”匹配0-n个字符,但不包括“/”。即,“*”只匹配一级目录或文件中的零个或多个字符。
l  **”匹配0-n个字符,包括“/”。即,“**”匹配多级目录或文件。
l  ?”匹配0-1个字符,但不包括“/”。即,“?”匹配一级目录或文件中的零个或一个字符。
所有的匹配,将被按顺序赋予变量“$1”、“$2”、“$3”、“$4”、……。这些变量可以在其它地方被引用。
下面是一些例子 —— 你应该还可以想出更多的可能性:
l  仅仅重命名WEB-INF及其子目录下的所有的xml文件:
将 /myapp/conf/my/file.xml 转换成 /webroot/WEB-INF/my/file.xml
<resource-alias pattern="/myapp/conf/**/*.xml" name="/webroot/WEB-INF/$1/$2.xml" />
l  修改文件名后缀:
将 /myapp/conf/myfile.conf 转换成 /webroot/WEB-INF/myfile.xml
<resource-alias pattern="/myapp/conf/*.conf" name="/WEB-INF/$1.xml"/>
l  按首字母划分子目录:将a开头的文件名放到a子目录下,b开头的文件名放到b子目录下,以此类推:
将 /profiles/myname 转换成文件路径 ${profile_root}/m/myname
将 /profiles/othername 转换成文件路径 ${profile_root}/o/othername
<resource pattern="/profiles/?*">
    <res-loaders:file-loader basedir="${profile_root}">
        <res-loaders:path>$1/$1$2</res-loaders:path>
    </res-loaders:file-loader>
</resource>
2.6.在多个ResourceLoader中查找
假如,在我的Web应用中,我有一些配置文件放在/WEB-INF目录中,另外一部分配置放在classpath中。我可以这样做:
<resource-loading
        xmlns="http://www.alibaba.com/schema/services"
        xmlns:res-loaders="http://www.alibaba.com/schema/services/resource-loading/loaders">
    ...
 
    <resource pattern="/myapp/conf">
        <res-loaders:super-loader name="/webroot/WEB-INF" />
        <res-loaders:super-loader name="/classpath" />
    </resource>
 
    <resource pattern="/webroot" internal="true">
        <res-loaders:webapp-loader />
    </resource>
    <resource pattern="/classpath" internal="true">
        <res-loaders:classpath-loader />
    </resource>
 
    ...
</resource-loading>
ResourceLoading服务根据上面的配置,会这样查找资源“/myapp/conf/myFile.xml”:
1.         先查找:/webroot/WEB-INF/myFile.xml,如果找不到,
2.         则再查找:/classpath/myFile.xml,如果找不到,则放弃。
3.         返回第一个被找到的结果。
<super-loader>是一种特殊的ResourceLoader,它等同于<resource-alias>。下面的两种写法是完全等同的:
<resource pattern="/myapp/conf">
    <res-loaders:super-loader name="/webroot/WEB-INF" />
</resource>
 
<resource-alias pattern="/myapp/conf " name="/webroot/WEB-INF" />
但是用<resource-alias>没有办法实现上面所述的多重查找的功能。
2.7.装载parent容器中的资源
在Webx中,Spring容器被安排成级联的结构。
Webx3_resourceLoading.png
如图所示,每个Spring容器都可以配置自己的ResourceLoading服务。当调用子容器的ResourceLoading服务时,遵行这样的逻辑:
1.         先在子容器的ResourceLoading服务中查找资源,如果找不到,
2.         则再到parent容器的ResourceLoading服务中查找,如果找不到,则放弃。
3.         返回第一个找到的资源。
运用这种级联装载资源的方法,子应用可以把共享的资源定义在root context中,而把自己独享的资源定义在自己的容器当中。
前文所述的<super-loader>也支持级联装载资源。<super-loader>会先在当前容器的ResourceLoading服务中查找,如果找不到,就到parent容器的ResourceLoading服务中查找。利用<super-loader>,你甚至可以改变资源搜索的顺序。例如,你可以命令ResourceLoading服务先查找parent容器中的ResourceLoading服务,再查找当前容器中ResourceLoader:
<resource pattern="...">
    <!-- 先找parent容器中的ResourceLoading服务 -->
    <res-loaders:super-loader />
    <!-- 再找当前容器中的ResourceLoader -->
    <res-loaders:file-loader />
</resource>
2.8.修改资源文件的内容
ResourceLoading服务支持内容过滤 —— 你可以在获取资源以前读取甚至修改资源文件的内容。一种常见的情形是,将XML格式的资源文件用XSLT转换格式:
<resource-loading
        xmlns="http://www.alibaba.com/schema/services"
        xmlns:res-loaders="http://www.alibaba.com/schema/services/resource-loading/loaders">
        xmlns:res-filters="http://www.alibaba.com/schema/services/resource-loading/filters">
    ...
 
    <resource pattern="/tempdir">
        <loaders:file-loader basedir="${project.home}/target/test" />
    </resource>
 
    <resource-filters pattern="/**/test.xml">
        <res-filters:xslt-filter xslt="/$1/test.xsl" saveTo="/tempdir" />
    </resource-filters>
 
</resource-loading>
上面的配置引进了一种新的扩展点:ResourceFilter。ResourceFilter可以在应用获取资源之前,取得控制,以便对资源做一点事。XSLT filter能够把XML资源用指定的xsl文件转换成新的格式。假如指定了saveTo参数,就可以把转换的结果保存下来,避免每次访问都重新转换。值得注意的是,saveTo参数所指的文件目录本身也是由ResourceLoading服务装载的。
有哪些情况需要这种内容过滤的功能呢?
l  单元测试 —— 我们可能需要对单元测试的资源文件进行特殊的转换。
l  高速缓存 —— 有一些ResourceLoader可能会有性能的开销,例如:从数据库中装载资源。利用ResourceFilter功能,就可以把装载的资源缓存在高速cache中,以提高系统的性能。
2.9.直接使用ResourceLoadingService
前面所讲的ResourceLoading服务的用法,对应用程序而言,是完全透明的。也就是说,应用程序并不需要关心ResourceLoading服务的存在,而是按照Spring ResourceLoader的老用法,就可以工作。
但是你也可以直接注入ResourceLoadingService对象,以取得更多的功能。
public class MyClass {
    @Autowired
    private ResourceLoadingService resourceLoadingService;
}
下面是一些调用ResourceLoadingService的例子:
l  取得资源
Resource resource = resourceLoadingService.getResource("/myapp/conf/myFile.xml");
Resource resource = resourceLoadingService.getResource("/myapp/conf/myFile.xml",
                                                                 ResourceLoadingService.FOR_CREATE);
和Spring不同的是,如果你直接调用ResourceLoadingService取得资源,当资源文件不存在时,你会得到一个ResourceNotFoundException。而Spring无论如何都会取得Resource对象,但你可以随后调用Resource.exists()方法来判断资源存在于否。
ResourceLoadingService.getResource()方法还支持一个选项:FOR_CREATE。如果提供了这个选项,那么对于某些类型的资源(如文件系统的资源),即使文件或目录不存在,仍然会返回结果。这样,你就可以创建这个文件或目录 —— 这就是FOR_CREATE参数的意思。
l  取得特定类型的资源
File file = resourceLoadingService.getResourceAsFile("/myapp/conf/myFile.xml");
URL url = resourceLoadingService.getResourceAsURL("/myapp/conf/myFile.xml");
InputStream stream =resourceLoadingService.getResourceAsStream("/myapp/conf/myFile.xml");
可以直接取得File、URL、和InputStream。
l  判断资源存在于否
if (resourceLoadingService.exists("/myapp/conf/myFile.xml")) {
    ...
}
l  列举子资源
String[] resourceNames = resourceLoadingService.list("/myapp/conf");
Resource[] resources = resourceLoadingService.listResources("/myapp/conf");
相当于列出当前目录下的所有子目录和文件。
不是所有的ResourceLoader都支持这个操作 ——FileResourceLoader和WebappResourceLoader支持列举子资源,ClasspathResourceLoader则不支持。
l  跟踪取得资源的过程
ResourceTrace trace = resourceLoadingService.trace("/myapp/conf/webx.xml");
 
for (ResourceTraceElement element : trace) {
    System.out.println(element);
}
这是用来方便调试的功能。有点像Throwable.getStackTrace()方法,可以得到每一个方法调用的历史记录 —— ResourceLoadingService.trace()方法可以将取得资源的步骤记录下来。上面代码会在console中输出类似下面的内容:
"/myapp/conf/webx.xml" matched [resource-alias pattern="/myapp/conf"], at "resources.xml", beanName="resourceLoadingService"
"/webroot/WEB-INF/webx.xml" matched [resource pattern="/webroot"], at "resources.xml", beanName="resourceLoadingService"
l  列出所有可用的资源定义和别名的pattern
String[] patterns = resourceLoadingService.getPatterns(true);
2.10.在非Web环境中使用ResourceLoading服务
Webx3_resourceLoading.png
在非Web环境中使用ResourceLoading服务的最好方法,是创建ResourceLoadingXmlApplicationContext作为Spring容器。
ApplicationContext context = new ResourceLoadingXmlApplicationContext(
                                                          newFileSystemResource("beans.xml"));
只要beans.xml中包含<resource-loading>的配置,就会自动启用ResourceLoading服务,并取代Spring原来的ResourceLoader机制。
3.ResourceLoader参考
ResourceLoading服务的核心是ResourceLoader。ResourceLoader担负了装载某一种类型的资源的具体任务。例如FileResourceLoader负责装载文件系统的资源;WebappResourceLoader负责装载WEB应用中的资源等等。
当你需要新的资源装载方式时,你所要做的,就是实现一种新的ResourceLoader。例如,你想从数据库中装载资源,那么就可以实现一个DatabaseResourceLoader。
3.1.FileResourceLoader
FileResourceLoader的功能是:从文件系统中装载资源。
l  最简单的用法
<resource pattern="/my/virtual">
    <res-loaders:file-loader />
</resource>
这样file-loader会从哪里装载资源呢?
答案是:从当前配置文件所在的目录中装载。假如上述资源配置所在的配置文件是c:/myapp/conf/resources.xml,那么file-loader就会从c:/myapp/conf/myFile.xml文件中装载/my/virtual/myFile.xml资源。
这样做的思路源自于Apache的一个项目:Ant。Ant是一个广为使用的build工具。每一个Ant项目,都有一个build.xml,在里面定义了很多target,诸如编译项目、打包等。通常我们都会把build.xml这个文件放在项目的根目录中,然后build.xml中的命令全是使用相对于build.xml所在的项目根目录计算出来的相对路径。例如:
<!-- build.xml -->
<project basedir=".">
    ...
    <target ...>
        <copy todir="bin">
            <fileset dir="src"/>
        </copy>
    </target>
    ...
</project>
在上面的Ant脚本中,bin、src目录全是相对于build.xml所在目录的相对目录。这样做的好处是,当你把项目移到不同的环境中,你也无需改变配置文件和脚本。
FileResourceLoader采用了和Ant完全类似的想法。
l  指定basedir
<resource pattern="/my/virtual">
    <res-loaders:file-loader basedir="${my.basedir}" />
</resource>
FileResourceLoader当然也支持指定basedir根目录。这样,它就会从指定的basedir的子目录中查找资源。
一般来说,我们需要利用Spring Property Placeholder来设置basedir。在上面的例子中,我们可以在系统启动时,指定JVM参数:-Dmy.basedir=c:/mydata。在不同的系统环境中,必须指定正确的basedir,否则,<file-loader>有可能找不到资源。
l  搜索多个路径
<resource pattern="/my/virtual">
    <res-loaders:file-loader basedir="...">
        <res-loaders:path>relativePathToBasedir</res-loaders:path>
        <res-loaders:path type="absolute">c:/absolutePath</res-loaders:path>
    </res-loaders:file-loader>
</resource>
FileResourceLoader支持搜索多个路径,类似于操作系统在PATH环境变量所指定的路径中,搜索可执行文件;也类似于Java在CLASSPATH参数所指定的路径中,搜索classes。
搜索路径可以是相对路径,也可以是绝对路径。相对路径是相对于basedir,如果basedir不存在,则相对于当前resource-loading所在的配置文件的路径。
3.2.WebappResourceLoader
WebappResourceLoader的功能是:从当前WEB应用中装载资源,也就是从ServletContext对象中装载资源。
<resource pattern="/my/virtual">
    <res-loaders:webapp-loader />
</resource>
3.3.ClasspathResourceLoader
ClasspathResourceLoader的功能是:从classpath中装载资源,也就是从当前的ClassLoader对象中装载资源。
<resource pattern="/my/virtual">
    <res-loaders:classpath-loader />
</resource>
3.4.SuperResourceLoader
SuperResourceLoader的功能是:调用ResourceLoading服务来取得资源。它有点像Java里面的super操作符。
l  取得新名字所代表的资源
<resource pattern="/my/virtual">
    <res-loaders:super-loader basedir="/webroot/WEB-INF" />
</resource>
这个操作类似于<resource-alias>。
如果在当前context的ResourceLoading服务中找不到资源,它会前往parent context中查找。
l  在parent context中查找资源
<resource pattern="/my/virtual">
    <res-loaders:super-loader />
</resource>
如果你不指定name参数,那么SuperResourceLoader会直接去parent context中查找资源,而不会在当前context的ResourceLoading服务中找。
3.5.关于ResourceLoader的其它考虑
以上所有的ResourceLoader都被设计成可以在任何环境中工作,即使当前环境不适用,也不会报错。
l  WebappResourceLoader可以兼容非WEB环境
在非WEB环境中,例如单元测试环境、你直接通过XmlApplicationContext创建的Spring环境,WebappResourceLoader也不会出错 —— 只不过它找不到任何资源而已。
l  SuperResourceLoader可以工作于非级联的环境
也就是说,即使parent context不存在,或者parent context中没有配置ResourceLoading服务,SuperResourceLoader也是可以工作的。
这样,同一套资源配置文件,可以被用于所有环境。
4.总结
ResourceLoading服务提供了一套高度可扩展的、强大的资源装载机制。这套机制和Spring ResourceLoader无缝连接。使用它并不需要特殊的技能,只要掌握Spring的风格即可。