Spring资源

1 简介

Java的标准java.net.URL类和各种URL前缀的标准处理器不足以满足所有的低级别资源访问。例如没有标准化的URL实现用于访问从类路径上获取的资源,或者与ServletContext相关的资源。尽管可以为特定的URL前缀注册新的处理器(与已有的前缀处理器例如http:相似),这一般是很复杂的,并且URL接口仍然缺乏一些需要的方法,例如检查指向的资源是否存在的方法。

2 Resource接口

Spring的Resource接口是一个更强大的接口,用于对低级资源的抽象访问。

public interface Resource extends InputStreamSource {

    boolean exists();

    boolean isOpen();

    URL getURL() throws IOException;

    File getFile() throws IOException;

    Resource createRelative(String relativePath) throws IOException;

    String getFilename();

    String getDescription();

}
public interface InputStreamSource {

    InputStream getInputStream() throws IOException;

}

Resource接口中的一些最重要的方法包括:

  • getInputStream():定位并且打开资源,返回用于读取资源的InputStream。期望每次调用都会返回一个新的InputStream。需要调用者关闭流。
  • exists():返回一个布尔值指示这个资源是否以物理形式存在;
  • isOpen():返回一个布尔值指示这个资源是否表示具有打开流的句柄。如果为true,InputStream不能被多重读取,并且必须被读取一次然后关闭来避免资源泄露。对于所有通常的资源实现,除了InputStreamResource之外,它是false。
  • getDescription():返回资源的描述,用于使用资源时的错误输出。它经常是全限定的文件名活着资源的实际URL。

其它方法允许你获取一个代表资源的实际URL或文件对象(如果底层实现兼容并且在功能上支持)。

Resource抽象在Spring自身被广泛的使用,在许多方法签名中当需要一个资源时它作为一个参数类型。在一些Spring API的其它方法中(例如各种ApplicationContext实现的构造方法),使用一个字符串,以未装饰或简单的形式用于创建适合该上下文实现的Resource,或者通过字符串路径中特定的前缀允许调用者指定必须创建和使用的特定Resource实现。

在Resource接口被Spring广泛使用的同时,它实际上在用户编写的代码中作为一般的实用工具类也是非常有用的,主要用于访问资源,甚至当代码不知道或不关心Spring的任何其它部分时。尽管这将代码和Spring耦合在一起,它实际上仅仅耦合了一小部分工具类,作为URL的功能更丰富的替代,可以等价于出于这个目使用的其它任何库。

要注意的是,资源抽象不是取代而是包装它。例如URLResource包装URL,并且使用被包装的URL完成它的工作。

3 内建的Resource实现

在Spring中提供了一些开箱即用的Resource实现。

3.1 UrlResource

UrlResource包装了java.net.URL,用于访问通常可通过URL访问的任何对象,例如文件、HTTP目标、FTP目标等。所有URL有一个标准化的字符串表示方法,适当的标准化前缀用于指示URL类型。file:表示访问文件系统路径,http:用于通过HTTP协议访问资源,ftp:用于通过FTP访问资源等等。

UrlResource对象在Java代码中使用构造函数显式创建,但是经常被隐式创建,通过调用使用一个String参数表示一个路径的API方法。对于后一种情况,一个JavaBean PropertyEditor最终会决定创建什么类型的Resource。如果路径字符串中包含一些众所周知的前缀例如classpath:,它会为该前缀创建一个恰当的资源。如果不能识别前缀,它会假定这仅仅是一个标准URL字符串,并且创建UrlResource。

3.2 ClassPathResource

这个类代表从类路径获取的资源。它或者使用线程上下文类加载器,一个给定的类加载器,或者使用一个用户给定的加载资源的类。

如果类路径资源位于文件系统,这个Resouce实现支持将其解析为java.io.File;但是不支持位于jar文件中且尚未解压(通过servlet引擎或其它环境)到文件系统中的类路径资源。为了解决这个问题各种资源实现总是支持解析为java.net.URL。

ClassPathResource在Java代码中使用ClassPathResource构造函数现实创建;但是经常被隐式创建,通过调用用一个String参数表示一个路径的API方法。对于后一种情况,一个JavaBean PropertyEditor会识别字符串路径的特定前缀classpath:,然后创建一个ClassPathResource。

3.3 FileSystemResource

用于处理java.io.File的Resource实现。它支持解析为一个File或URL。

3.4 ServletContextResource

这个Resource实现用于ServletContext资源,使用相应web应用程序的根目录来解析相关的路径。

它支持流读取和URL读取,但是仅仅当web应用压缩文件被解压并且资源存在于文件系统时允许访问java.io.File。压缩文件是否被解压到文件系统,还是直接从JAR访问或者从其它地方例如DB访问,实际上依赖于Servlet容器。

3.5 InputStreamResource

用于给定的InputStream的Resource实现。只有在没有具体的资源实现适用的情况下才应该使用。实际上,应尽可能优先使用ByteArrayResource或任何基于文件的Resource实现。与其它资源实现相反,这是一个对已经打开资源的描述符——因此isOpen()方法返回true。如果需要将资源描述符保存在某处或者需要多次读取流,请不要使用它。

3.6 ByteArrayResource

这个Resource实现用于一个给定的byte数组。它为给定的数组创建一个ByteArrayInputStream。

从任何给定的byte数组读取内容而不需使用一次性的InputStreamResource是有用的.

4 ResourceLoader

ResourceLoader被能够返回(即加载)Resource实例的对象实现。

public interface ResourceLoader {

    Resource getResource(String location);

}

所有应用上下文都实现了ResourceLoader接口,因此所有应用上下文可以用于获取Resource实例。

当在特定的应用上下文上调用getResource(),并且给定的位置路径没有特定的前缀,它会返回与具体应用上下文相关的Resource类型。例如,假定下面的代码段有ClassPathXmlApplicationContext实例执行:

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

会返回一个ClassPathResource对象;如果同样的方法被FileSystemXmlAPplicationContext实例执行,会返回FileSystemResource。对于WebApplicationContext,则返回ServletContextResource等等。

因此可以以适合于特定应用程序上下文的方式加载资源。

另一方面,也可以通过指定classpath:前缀来强制返回ClassPathResource,而不考虑应用上下文类型:

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

相似的,可以指定任何一个标准的java.net.URL前缀来强制返回UrlResource:

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

下面的表格总结了String转换为Resource的规则:

前缀例子解释
classpath:classpath:com/myapp/config.xml从类路径加载
file:file:///data/config.xml作为URL加载,从文件系统
http:http://myserver/logo.png作为URL加
(没有前缀)/data/config.xml依赖于底层的ApplicationContext

5 ResourceLoaderAware接口

ResourceLoaderAware接口是一个特殊的标记接口,识别希望获取ResourceLoader引用的对象。

public interface ResourceLoaderAware {

    void setResourceLoader(ResourceLoader resourceLoader);
}

当一个类实现了ResourceLoaderAware接口并且被部署到应用上下文中(作为一个Spring管理的bean),它被应用上下文识别为ResourceLoaderAware。然后应用上下文将会调用setResourceLoader(ResourceLoader resourceLoader)方法,将自身作为参数提供给该方法(记住,Spring中所有的应用上下文都实现了ResourceLoader接口)。

当然,由于一个ApplicationContext是一个ResourceLoader,bean也可以实现ApplicationContextAware接口并且使用被提供的应用上下文直接用于加载资源,但是一般情况下,最好如果仅仅是需要加载资源使用特定的ResourceLoader接口。这样代码仅仅会与被认为是工具接口的资源加载接口耦合,而不是耦合于整个Spring ApplicationContext接口。

从Spring 2.5开始可以使用ResourceLoader的自动装配作为实现ResourceLoaderAware接口的一种替代方法。传统的构造函数和通过类型的自动装配模式现在可以为构造函数参数和setter方法参数提供ResourceLoader依赖。对于更加灵活的方式(包括自动装配域和多个参数的方法),可以使用新的基于注解的自动装配特征。在这种情况下,ResourceLoader可以被自动注入到期待类型为ResourceLoader的域、构造函数参数或者方法参数中,只有上述域、方法或者构造函数带有@Autowired注解。

6 资源作为依赖

如果一个bean自身要通过某种动态决定过程决定和提供资源路径,bean使用ResourceLoader接口加载资源是很有意义的。作为一例子,考虑以某种模式加载一个模版,其中所需的特定资源取决于用户角色。如果资源是静态的,完全消除对ResourceLoader的使用是有意义的,只要让bean暴露它需要的Resource属性,并且期望它们被注入到bean中。

所有应用上下文都会注册和作为一个特殊JavaBean使用的PropertyEdtior将String路径转换为Resource对象,它使得注入这些Resource依赖变的简单。所以如果myBean有一个Resource类型的模版属性,它可以被配置为一个简单的字符串,如下所示:

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

请注意资源路径没有前缀,由于应用上下文自身将会被用做ResourceLoader,资源自身被加载为ClassPathResource、FileSystemResource或者ServletContextResource取决于具体的上下文类型。

如果需要强制指定资源的类型,可以使用前缀。下面的两个例子展示了如何强制使用ClassPathResource和UrlResource(后一种用于获取文件系统中的文件):

<property name="template" value="classpath:some/resource/path/myTemplate.txt">
<property name="template" value="file:///some/resource/path/myTemplate.txt"/>

7 应用上下文和资源路径

7.1 构造应用上下文

一个应用上下文的构造函数(用于特定的应用上下文类型)一般使用一个字符串或字符串数组作为资源(例如XML文件)的位置路径,这些资源组成了上下文的定义。

当这样的位置路径没有前缀时,特定的Resource类型从这个路径构建并且用于加载bean定义,Resource类型依赖并适用于特定的应用上下文。例如,如果如下创建一个ClassPathXmlApplicationContext:

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

bean定义将会从类路径加载,因为使用了ClassPathResource。但是如果创建一个FileSystemXmlApplicationContext如下:

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

bean定义将会从文件系统位置加载,在这种情况下相对于当前工作目录。

请注意,在位置路径前使用特殊的类路径前缀或标准URL前缀将会覆盖默认的被创建用于加载bean定义的Resource类型。所以,这个FileSystemXmlApplicationContext

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

将会实际从类路径加载bean定义。然而,它仍然是一个FileSystemXmlApplicationContext。如果后来被作为ResourceLoader使用,任何无前缀的路径将仍然会作为文件系统路径被处理。

构造ClassPathXmlApplicationContext实例 —— 快捷方式

ClassPathXmlApplicationContext提供了系列构造函数用于快速实例化。基本的方法是替狗一个字符串数组包含了XML文件自身的文件名(没有路径信息),并且也可以提供一个Class;ClassPathXmlApplicationContext将会从被提供的类判断路径信息。

一个例子会使其清晰,考虑一个目录布局如下所示:

com/
	foo/
		services.xml
		daos.xml
		MessengerService.class

一个ClassPathXmlApplicationContext实例可以像下面这样被实例化,它是由在services.xml和daos.xml中的bean定义组成。

ApplicationContext ctx = new ClassPathXmlApplicationContext(
    new String[] {"services.xml", "daos.xml"}, MessengerService.class);

可以参考ClassPathXmlApplicationContext的javadoc获取各种构造函数的详细信息。

7.2 应用上下文构造函数的资源路径中的通配符

在应用上下文构造函数的参数值中的资源路径可以是简单路径(如上所示),与目标资源是一一映射的关系,或者可以包含特殊的"classpath*:"前缀和内部的Ant风格的正则表达式(使用Spring的PathMatcher工具匹配)。后者都是有效的通配符。

这种机制的一个用途是组件样式应用组装。所有组件可以发布上下文定义片段到一个众所周知的位置路径,并且当最终的应用上下文使用通过classpath*前缀的相同路径被创建时,所有的组件片段都会被自动打包。

_自己的补充:应用于不同类路径下的相同文件,比如resource1.jar中有一个com.text.rs.jarAppcontext.xml,而resource2.jar中也有一个com.text.rs.jarAppcontext.xml。通过

ApplicationContext ctx = new ClassPathXmlApplicationContext( "classpath*:com/test/rs/jarAppcontext.xml")

可以将两个jar中的同名文件都加载进来。而如果写成下面的代码,就只能找到其中的一个xml文件(顺序取决于jar包的加载顺序)

ApplicationContext ctx = new ClassPathXmlApplicationContext( "classpath:com/test/rs/jarAppcontext.xml");

_

请注意,此通配符特定于在应用上下文的构造函数中的资源路径使用(或当直接使用PathMatcher工具类层次结构时),并且在构造时被解析。与Resource类型本身没有任何关系。不能够使用classpath*:前缀构造实际的Resource,因为一个Resource同时仅仅指定一个资源。

Ant风格模式

当路径位置包含Ant风格模式,例如:

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

解析器遵循更复杂但定义的过程来尝试解析通配符。它为最后一个非通配符段生成资源并从中获取一个URL。如果URL不是以jar:开头或容器特定的变体(例如WebLogic中的zip:,webSphere中的wsjar等),那么将产生一个java.io.File并且用于通过遍历文件系统来解析通配符。在jar URL的情况下,解析器或者获取一个java.net.JarURLConnection或者手动解析jar URL然后遍历jar文件的内容来解析通配符。

对可移植性的影响

如果指定的路径是文件URL(显式的或隐式的),由于基本的ResourceLoader是文件系统资源加载器,通配符可以保证以完全可移植的方式工作。

如果指定的路径是一个类路径位置,那么解析器通过调用ClassLoader.getResource()获取最后一段非通配符路径段的URL。因为这仅仅是路径的一个节点(不是最后的文件),在这种情况下对于返回什么类型的URL实际上是未定义的。在实践中,经常是一个java.io.File对象代表目录,类路径资源解析为一个文件系统位置;或某一类的jar URL,类路径资源解析为jar位置。尽管如此,这种操作仍然存在可移植性问题。

如果从最后一个非通配符段获取到一个jar URL,解析器必须能够从它获取java.net.JarURLConnetcion,或者手动解析jar URL,能够遍历jar的内容,并解析通配符。这在大多数环境中工作良好,但是在一些其它环境会失效;强烈建议jar的通配符资源解析在特定的环境中被全面测试。

Classpath*: 可移植的classpath*前缀

当构建一个基于XML的应用上下文,一个位置字符串可以使用特殊的classpath*: 前缀:

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

这个特定的前缀指定所有匹配给定名称的类路径资源必须被获取(在内部,这实际上通过ClassLoader.getResources(...)方法调用完成),然后合并形成最终的应用上下文定义。

通配符的类路径依赖于底层类加载器的getReosources()方法。由于现今大多数应用服务器都提供它们自己的类加载器实现,处理jar文件的行为也许会不同。用于测试classpath*是否工作的一个简单方法是使用类加载器从类路径下的jar加载文件:getClass().getClassLoader().getResources("<someFileInsideTheJar>")。使用相同名字但是被放置于不同位置的文件测试。如果返回了不正确的结果,查看应用服务器的文档找到影响类加载器行为的设置。

classpath*: 前缀也可以与位置路径其余部分中的PathMatcher模式组合使用,例如classpath*:META-INF/*-beans.xml。在这种情况下解析策略很简单:ClassLoader.getResources()调用被用于从最后一个非通配符路径段获取所有在类加载器层次结构中匹配的资源,然后上文所述的PathMatcher解析策略用于子路径的通配符资源解析。

有关通配符的其它说明

请注意,classpath*:与Ant风格模式在模式开始前至少一个根目录时有效,除非实际的目标文件位于文件系统。这意味着像classpath*:*.xml这样的模式不能从jar文件中检索文件,而只能从扩展目录的根目录中检索文件。这是由JDK的ClassLoader.getResources()方法的局限性造成的,当传入空字符串时它仅仅返回文件系统位置(表明要搜索的根目录)。

classpath:资源中的Ant风格模式如果要搜索的根包卫浴多个类路径位置不保证能够找到匹配的资源。因为如下的一个资源:

com/mycompany/package1/service-context.xml

也许位于一个路径,但是当路径如下:

classpath:com/mycompany/**/service-context.xml

被解析时,解析器将会使用getResource("com/mycompany")返回的(第一个)URL工作。如果这个基础包节点在多个类加载器位置中存在,实际的资源可能未在其中。因此,在这种情况下优先使用"classpath*:"和相同的Ant风格模式,它会搜索包含根包的所有类路径位置。

7.3 文件系统资源注意事项

未附加到FileSystemApplicationContext的FileSystemResource(即FileSystemApplicationContext并不是实际的ResourceLoader),FileSystemResource将按照期望来处理绝对路径和相对路径。相对路径与当前工作路径相关,而绝对路径与文件系统的根目录相关。

由于向后的兼容系的原因,当FileSystemApplicationContext是一个ResourceLoader时它将改变。FileSystemApplicationContext简单的强制所有的FileSystemResource实例作为相对路径处理,不论它们是否以斜杠开头。在实践中,这意味下面的写法等价:

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

下面的写法也等价:(即使他们是不同的,因为一个是相对的,另一个是绝对的。)

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

在实践中,如果需要绝对文件系统路径,最好放弃将FileSystemResource/FileSystemXmlApplicationContext与绝对路径一起使用,使用带file: URL前缀的URLResource即可。

// actual context type doesn't matter, the Resource will always be UrlResource
ctx.getResource("file:///some/resource/path/myTemplate.txt");
// force this FileSystemXmlApplicationContext to load its definition via a UrlResource
ApplicationContext ctx =
    new FileSystemXmlApplicationContext("file:///conf/context.xml");

转载于:https://my.oschina.net/u/2453016/blog/1505709

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值