SpringFramework核心技术二:Resources(加载资源的几种方式)

Resources的介绍

java.net.URL不幸的是,对于各种URL前缀,Java的标准类和标准处理程序不足以满足所有对低级资源的访问。例如,没有URL可用于访问需要从类路径获取的资源的标准化实现,或者相对于某个资源的获取 ServletContext。尽管可以为专用URL 前缀注册新的处理程序(类似于诸如前缀的现有处理程序http:),但这通常非常复杂,并且URL界面仍然缺少某些期望的功能,例如检查资源是否存在的方法指出。

一、资源的接口

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():返回一个boolean指示这个资源是否实际存在的物理形式。
  • isOpen():返回一个boolean指示此资源是否表示具有打开流的句柄。如果true,则InputStream不能被多次读取,并且必须一次只能读取,然后关闭以避免资源泄漏。将false用于所有通常的资源实现,除了InputStreamResource。
  • getDescription():返回此资源的描述,用于处理资源时的错误输出。这通常是完全限定的文件名或资源的实际URL。

其他方法允许您获取表示资源的实际URL或File对象(如果底层实现兼容并支持该功能)。

该Resource抽象需要资源时Spring自身广泛使用,在许多方法签名的参数类型。在某些Spring API中的其他方法(比如各种ApplicationContext实现的构造函数),可以使用一种 String以未修改的或简单的形式创建Resource适合该上下文实现的String路径,或通过路径上的特殊前缀来允许调用者指定Resource必须创建和使用特定的实现。

尽管Resource接口在Spring和Spring中被大量使用,但是在您自己的代码中使用它作为一个通用工具类来访问资源,即使您的代码不知道或关心其他任何部件的春天。虽然这会将你的代码结合到Spring上,但它实际上只是将它连接到这套小实用程序类,这些实用程序类可用作更强大的替代品URL,并且可以认为它与您用于此目的的任何其他库相当。

重要的是要注意,Resource抽象并不取代功能:它尽可能包装它。例如,UrlResource封装一个URL,并使用包装URL来完成其工作。

二、Spring内置Resource接口的实现

Resource在Spring 中有很多实现可以直接使用:

1.UrlResource对象

所述UrlResource包裹一个java.net.URL,并且可以被用于访问任何对象,该对象是通过URL正常访问,如文件,一个HTTP靶,FTP对象等的所有URL具有标准化的String表示,以使得适当的标准化的前缀被用来指示从另一个URL类型。这包括file:访问文件系统路径,http:通过HTTP协议 ftp:访问资源,通过FTP访问资源等。

A UrlResource是由Java代码使用UrlResource构造函数显式创建的,但是当您调用一个API方法时,通常会隐式创建它,该方法需要一个String 用于表示路径的参数。对于后一种情况,JavaBeans PropertyEditor将最终决定Resource要创建哪种类型。如果路径字符串包含一些众所周知的(就是它)前缀,例如classpath:,它将Resource为该前缀创建适当的专用。但是,如果它不能识别前缀,它会认为这只是一个标准的URL字符串,并且会创建一个UrlResource。

2.使用ClassPathResource

这个类代表了一个应该从classpath中获取的资源。这使用线程上下文类加载器,给定的类加载器或给定的类来加载资源。
此Resource实现支持解析,就java.io.File好像类路径资源驻留在文件系统中一样,但不支持放置在jar中并且尚未扩展(通过servlet引擎或任何环境)扩展到文件系统的classpath资源。
为了解决这个问题,各种Resource实现总是支持解析java.net.URL。
A ClassPathResource是由Java代码使用ClassPathResource 构造函数显式创建的,但是当您调用一个API方法时,通常会隐式创建它,该方法需要一个String用于表示路径的参数。
对于后一种情况,JavaBeans PropertyEditor将识别classpath:字符串路径上的特殊前缀,并ClassPathResource在这种情况下创建一个。

扫描二维码关注公众号,回复: 1855157 查看本文章

3.FileSystemResource

这是一个句柄的Resource实现java.io.File。它显然支持作为一个File和作为一个决议URL。

4.ServletContextResource

这是资源的Resource实现ServletContext,解释相关Web应用程序根目录中的相对路径。
这总是支持流访问和URL访问,但只允许java.io.File在Web应用程序归档文件展开并且资源物理位于文件系统上时进行访问。不管它是否被扩展,并在文件系统上这样,或者直接从JAR或其他地方像DB(可以想象)访问,实际上都依赖于Servlet容器。

5.InputStreamResource

Resource一个给定的实现InputStream。只有在没有具体的Resource实施方式的情况下才能使用。特别是,如果可能的话,更喜欢 ByteArrayResource或者任何基于文件的Resource实现。
相对于其他Resource的实现,这是一个描述符已经 打开资源-因此返回true的isOpen()。如果您需要将资源描述符保存在某处,或者您需要多次读取流,请不要使用它。

6.ByteArrayResource

这是Resource给定字节数组的一个实现。它ByteArrayInputStream为给定的字节数组创建一个 。
从任何给定的字节数组中加载内容是很有用的,而不必求助于单次使用InputStreamResource。

三、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,等等。

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

另一方面,ClassPathResource通过指定特殊的classpath:前缀,你也可以强制使用,而不管应用程序的上下文类型如何:

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

同样,可以UrlResource通过指定任何标准 java.net.URL前缀来强制使用a :

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

下表总结了将Strings 转换为Resources 的策略:

Prefix Example Example
classpath: classpath:com/myapp/config.xml Loaded from the classpath
file: file:///data/config.xml Loaded as a URL, from the filesystem. [3]
http: http://myserver/logo.png Loaded as a URL.
(none) /data/config.xml Depends on the underlying ApplicationContext.

四、 The ResourceLoaderAware interface

该ResourceLoaderAware接口是一个特殊的标记接口,它希望被提供有对象ResourceLoader参考。

public interface ResourceLoaderAware {

    void setResourceLoader(ResourceLoader resourceLoader);
}

当一个类实现ResourceLoaderAware并被部署到应用程序上下文中(作为一个Spring管理的bean)时,它被ResourceLoaderAware应用程序上下文识别。然后,应用程序上下文将调用 setResourceLoader(ResourceLoader),提供自己作为参数(请记住,Spring中的所有应用程序上下文实现该ResourceLoader接口)。

当然,因为a ApplicationContext是a ResourceLoader,所以bean也可以实现ApplicationContextAware接口并直接使用提供的应用程序上下文来加载资源,但是一般来说,ResourceLoader如果只需要这些,最好使用专用 接口。这些代码只会耦合到资源加载界面,它可以被认为是一个实用程序界面,而不是整个Spring ApplicationContext界面。

从Spring 2.5开始,您可以依靠自动装配ResourceLoader来实现ResourceLoaderAware接口。“传统” constructor和 byType自动装配模式(如自动装配协作者所描述的)现在能够分别为ResourceLoader构造函数参数或设置方法参数提供类型的依赖关系。为了获得更大的灵活性(包括自动装配字段和多个参数方法的能力),请考虑使用新的基于注释的自动装配功能。
下面这一句才是关键:
在这种情况下,只要字段,构造函数或方法带有 注释,ResourceLoader就会自动装入字段,构造函数参数或方法参数,该参数需要ResourceLoader类型@Autowired。有关更多信息,请参阅@Autowired。

五、资源作为依赖关系

如果bean本身要通过某种动态过程来确定并提供资源路径,那么bean可能会使用该ResourceLoader 接口来加载资源。考虑加载某种模板的例子,其中需要的特定资源取决于用户的角色。如果资源是静态的,那么ResourceLoader 完全消除接口的使用是有意义的,并且让bean暴露Resource它需要的属性,并期望它们被注入到它中。

什么使注入这些属性变得微不足道的是,所有的应用程序上下文都注册并使用一个特殊的JavaBeans PropertyEditor,它可以将String路径转换为Resource对象。因此,如果myBean具有类型的模板属性Resource,则可以使用该资源的简单字符串进行配置,如下所示:

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

需要注意的是资源路径没有前缀,是因为它本身是要在应用程序上下文中使用的ResourceLoader,这个资源会通过一个被加载 ClassPathResource,FileSystemResource或ServletContextResource(如适用)根据确切类型的上下文的。
如果需要强制使用特定Resource类型,则可以使用前缀。以下两个示例显示如何强制a ClassPathResource和a UrlResource(后者用于访问文件系统文件)

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

六、Application contexts and Resource paths(应用程序上下文和资源路径)

1.构建应用程序上下文

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

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

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

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

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

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

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

实际上会从类路径中加载它的bean定义。但是,它仍然是一个 FileSystemXmlApplicationContext。如果随后将其用作a ResourceLoader,则任何前缀不固定的路径仍将被视为文件系统路径。

2.构造ClassPathXmlApplicationContext实例 - 快捷方式

在ClassPathXmlApplicationContext提供了多种构造方法以便于实例。其基本思想是只提供一个字符串数组,它只包含XML文件本身的文件名(没有前导路径信息),另一个也提供一个Class; 在ClassPathXmlApplicationContext 将从给定类的路径信息。

有一个例子希望能够清楚地说明这一点。考虑一下这样的目录布局:

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

一个ClassPathXmlApplicationContext由在’services.xml’和中定义的bean组成的 ‘daos.xml’实例可以像这样被实例化…

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

3.应用程序上下文构造器资源路径中的通配符

应用上下文构造器值中的资源路径可以是简单的路径(如上所示),其具有到目标资源的一对一映射,或者可以包含特殊的“classpath *:”前缀和/或内部Ant-样式正则表达式(使用Spring的PathMatcher实用程序进行匹配)。后者都是有效的通配符

此机制的一个用途是在执行组件式应用程序组装时。所有组件都可以将上下文定义片段“发布”到众所周知的位置路径,并且当使用前缀相同的路径创建最终的应用上下文时 classpath*:,所有组件片段将被自动拾取。

请注意,此通配符特定于在应用程序上下文构造函数中使用资源路径(或PathMatcher直接使用实用程序类层次结构时),并在构建时解析。它与Resource类型本身无关。无法使用classpath*:前缀来构造实际Resource的资源,因为资源一次只能指向一个资源。

3.1. Ant-style Patterns(“/”分割的样式)

当路径位置包含Ant样式时,例如:

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

解析器遵循更复杂但定义的过程来尝试解析通配符。它为直到最后一个非通配符段的路径生成一个资源,并从中获取一个URL。如果此URL不是jar:URL或特定zip:于容器的变体(例如,在WebLogic,wsjarWebSphere等中),java.io.File则从其中获得a 并通过遍历文件系统来解析通配符。在jar URL的情况下,解析器可以从中获取java.net.JarURLConnection或手动解析jar URL,然后遍历jar文件的内容来解析通配符。

3.2 对便携性的影响

如果指定的路径已经是文件URL(无论是显式地还是隐式地,因为基础ResourceLoader是文件系统),那么通配符将保证以完全便携的方式工作。

如果指定的路径是类路径位置,则解析器必须通过Classloader.getResource()调用获取最后一个非通配符路径段URL 。由于这只是路径的一个节点(不是最后的文件),所以ClassLoader在这种情况下,它实际上是未定义的(在 javadoc中)确切返回了哪种URL。实际上,它始终java.io.File代表目录,类路径资源解析为文件系统位置,或者某种类型的jar URL,其中classpath资源解析为jar位置。但是,这个操作仍然存在可移植性问题。

如果为最后一个非通配符段获取了一个jar URL,那么解析器必须能够java.net.JarURLConnection从其中获取a 或者手动解析jar URL,以便能够遍历jar的内容并解析通配符。这可以在大多数环境中使用,但在其他环境中会失败,强烈建议您在依赖特定环境之前彻底测试来自瓶子的资源的通配符解决方案。

4.类路径*:前缀

在构建基于XML的应用程序上下文时,位置字符串可能使用特殊的classpath*:前缀:

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

这个特殊的前缀指定必须获得与给定名称匹配的所有类路径资源(在内部,这基本上是通过ClassLoader.getResources(…​)调用发生的 ),然后进行合并以形成最终的应用程序上下文定义。

通配符类路径依赖于getResources()底层类加载器的方法。由于现在大多数应用程序服务器都提供它们自己的类加载器实现,所以在处理jar文件时行为可能会有所不同。一个简单的测试,以检查是否classpath*使用类加载器从classpath中的jar中加载文件: getClass().getClassLoader().getResources("<someFileInsideTheJar>")。使用具有相同名称但位于两个不同位置的文件进行此测试。如果返回的结果不正确,请检查应用程序服务器文档以了解可能影响类加载器行为的设置。

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

5.其他有关通配符的说明

请注意classpath*:,除非实际目标文件驻留在文件系统中,否则与Ant样式结合使用时,只能在模式启动前至少有一个根目录可靠地运行。这意味着类似的模式 classpath*:*.xml可能不会从jar文件的根目录中检索文件,而只能从扩展目录的根目录中检索文件。
Spring检索类路径条目的能力源于JDK的 ClassLoader.getResources()方法,该方法仅返回传入的空字符串的文件系统位置(指示可能的根搜索)。Spring也评估 URLClassLoader运行时配置和jar文件中的“java.class.path”清单,但这并不保证会导致可移植行为。

类路径包的扫描要求类路径中存在相应的目录条目。在使用Ant构建JAR时,请确保不要 激活JAR任务的仅文件开关。此外,在某些环境中,类路径目录可能不会基于安全策略公开,例如JDK 1.7.0_45和更高版本上的独立应用程序(这需要在您的清单中设置“受信任的库”;请参阅 http://stackoverflow.com/questions/ 19394570 / java-jre-7u45-breaks-classloader-getresources)。
在JDK 9的模块路径(Jigsaw)中,Spring的类路径扫描一般按预期工作。将资源放入专用目录也是非常值得推荐的,这样可以避免前面提到的在搜索jar文件根目录级别时的可移植性问题。

classpath:如果要在多个类路径位置中使用要搜索的根包,则不保证具有资源的Ant样式模式能够找到匹配的资源。这是因为资源如

com/mycompany/package1/service-context.xml

may be in only one location, but when a path such as

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

七、FileSystemResource警告

一个FileSystemResource未连接到FileSystemApplicationContext(即FileSystemApplicationContext不实际的ResourceLoader)将把绝对和相对路径如你所愿。相对路径是相对于当前工作目录的,而绝对路径是相对于文件系统的根。

为了向后兼容(历史)的原因然而,这改变时 FileSystemApplicationContext是ResourceLoader。在 FileSystemApplicationContext简单地让所有绑定的FileSystemResource情况下,把所有的位置路径为相对的,他们是否开始与斜线与否。实际上,这意味着以下内容是等同的:(区别在于conf前面是否需要”/”)

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,只是强制使用的UrlResource,通过使用file:URL前缀。

// 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");

好啦。Spring在加载资源的几种方式,就是目前这些东西了。

猜你喜欢

转载自blog.csdn.net/wd2014610/article/details/80677212