Spring 核心技术 - Resources 资源

Spring 学习指南大全
Spring 核心技术

官方文档版本 Version 5.2.22.RELEASE

Resources 资源

本章介绍了 Spring 如何处理资源,以及如何在 Spring 中使用资源。它包括以下主题:

  • 简介
  • Resources(资源)接口
  • 内置资源实现
  • ResourceLoader(资源加载器)
  • ResourceLoaderAware 接口
  • 资源作为依赖项
  • 应用程序上下文和资源路径
简介

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

Resource(资源)接口

Spring 的 Resource(资源)接口是一个更强大的接口,用于抽象对底层资源的访问。以下清单显示了 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();
}

正如 Resource(资源) 接口的定义所示,它扩展了 InputStreamSource 接口。以下清单显示了 InputStreamSource 接口的定义:

public interface InputStreamSource {

    InputStream getInputStream() throws IOException;
}

Resource(资源)接口中一些最重要的方法是:

  • getInputStream():定位并打开资源,返回一个 InputStream 以便从资源中读取。预计每次调用都会返回一个新的 InputStream。关闭流是调用方的责任。
  • exists():返回一个 boolean 类型的值,指示该资源是否以物理形式实际存在。
  • isOpen():返回一个 boolean 值,指示该资源是否代表一个带有打开流的句柄。如果为 true,则不能多次读取 InputStream,只能读取一次,然后关闭以避免资源泄漏。对于除 InputStreamResource 之外的所有常规资源实现,返回 false。
  • getDescription():返回此资源的描述,用于处理资源时的错误输出。这通常是资源的完全限定文件名或实际 URL。

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

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

虽然 Spring 和 Spring 经常使用 Resource 接口,但在您自己的代码中,即使您的代码不知道或不关心 Spring 的任何其他部分,它本身作为一个通用的实用程序类来访问资源也是非常有用的。虽然这将您的代码耦合到了 Spring,但它实际上只是将代码耦合到了这一小组实用程序类,这些实用程序类是 URL 的一个更强大的替代品,可以被视为等同于您将用于此目的的任何其他库。

Resource(资源) 抽象不能取代功能。它尽可能地把它包起来。例如,UrlResource 包装了一个 URL,并使用包装的 URL 来完成它的工作。

内置 Resource(资源) 实现

Spring 包括以下 Resource(资源)实现:

UrlResource

UrlResource 包装了 java.net.URL,可用于访问通常可通过 URL 访问的任何对象,如文件、HTTP 目标、FTP 目标等。所有的 URL 都有一个标准化的字符串表示,这样就可以使用适当的标准化前缀来区分不同的 URL 类型。这包括 file:用于访问文件系统路径,http:用于通过 HTTP 协议访问资源,ftp:用于通过 FTP 访问资源,等等。

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

ClassPathResource

该类表示应该从类路径中获取的资源。它使用 thread context class loader(线程上下文类加载器)、给定的 class loader(类加载器)或给定的 class (类)来加载资源。

如果类路径资源驻留在文件系统中,该 Resource(资源)实现支持解析为 java.io.File,但不支持驻留在 jar 中且尚未(通过 servlet 引擎或任何环境)扩展到文件系统的类路径资源。为了解决这个问题,各种资源实现总是支持解析为 java.net.URL。

ClassPathResource 是由 Java 代码通过显式使用 ClassPathResource 构造函数创建的,但通常是在调用采用表示路径的 String(字符串)参数的 API 方法时隐式创建的。对于后一种情况,JavaBeans PropertyEditor 识别字符串路径上的特殊前缀 classpath:,并在这种情况下创建 ClassPathResource。

FileSystemResource

这是 java.io.File 和 java.nio.file.Path 处理的 Resource(资源)实现。它支持解析为文件和 URL。

ServletContextResource

这是 ServletContext 资源的资源实现,它解释相关 web 应用程序根目录中的相对路径。

它总是支持 stream (流)访问和 URL 访问,但是仅当 web 应用程序归档文件被扩展并且资源在文件系统上时才允许 java.io.File 访问。它是在文件系统上展开,还是直接从 JAR 或数据库(这是可以想象的)之类的地方访问,实际上取决于 Servlet 容器。

InputStreamResource

InputStreamResource 是给定 InputStream 的 Resource(资源)实现。只有在没有适用的特定 Resource(资源)实现时,才应该使用它。特别是,如果可能,最好使用 ByteArrayResource 或任何基于文件的 Resource(资源)实现。

与其他 Resource(资源)实现相比,这是一个已经打开的资源的描述符。因此,它从 isOpen() 返回 true。如果您需要将资源描述符保存在某个地方,或者如果您需要多次读取一个流,请不要使用它。

ByteArrayResource

这是给定字节数组的 Resource(资源) 实现。它为给定的字节数组创建一个 ByteArrayInputStream。

这对于从任何给定的字节数组中加载内容非常有用,而不必求助于单次使用的 InputStreamResource。

ResourceLoader (资源加载器)

ResourceLoader 接口旨在由可以返回(即加载) Resource(资源)实例的对象来实现。以下清单显示了 ResourceLoader 接口定义:

public interface ResourceLoader {

    Resource getResource(String location);
}

所有 application contexts(应用程序上下文) 都实现 ResourceLoader 接口。因此,所有应用程序上下文都可以用来获取资源实例。

当您在特定的应用程序上下文上调用 getResource() 时,如果指定的位置路径没有特定的前缀,您将得到一个适合于该特定应用程序上下文的 Resource(资源) 类型。例如,假设下面的代码片段是针对 ClassPathXmlApplicationContext 实例运行的:

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

针对 ClassPathXmlApplicationContext,该代码返回一个 ClassPathResource。如果对 FileSystemXmlApplicationContext 实例运行相同的方法,它将返回 FileSystemResource。对于 WebApplicationContext,它将返回 ServletContextResource。它同样会为每个上下文返回适当的对象。

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

另一方面,您也可以通过指定特殊的 classpath: 前缀来强制使用 ClassPathResource,而不考虑应用程序上下文类型,如下例所示:

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

类似地,可以通过指定任何标准的 java.net.URL 前缀来强制使用 UrlResource。以下一对示例使用了 file 和 http 前缀:

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

下表总结了将 String(字符串)对象转换为 Resource(资源)对象的策略:

Prefix(前置)例子说明
classpath:classpath:com/myapp/config.xml从类路径加载。
file:file:///data/config.xml作为 URL 从文件系统加载。另请参见后续文章【文件系统资源警告】。
http:https://myserver/logo.png作为 URL 加载。
(none)/data/config.xml取决于基础 ApplicationContext。
ResourceLoaderAware 接口

ResourceLoaderAware 接口是一个特殊的回调接口,它标识那些希望提供 ResourceLoader 引用的组件。以下清单显示了ResourceLoaderAware 接口的定义:

public interface ResourceLoaderAware {

    void setResourceLoader(ResourceLoader resourceLoader);
}

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

因为 ApplicationContext 是一个 ResourceLoader(资源加载器),所以 bean 也可以实现 ApplicationContextAware 接口,并使用提供的应用程序上下文直接加载资源。然而,一般来说,如果这就是您所需要的,那么最好使用专门的 ResourceLoader 接口。代码将只耦合到资源加载接口(可以认为是一个实用程序接口),而不是整个 Spring ApplicationContext 接口。

在应用程序组件中,您还可以依赖 ResourceLoader 的自动装配作为实现 ResourceLoaderAware 接口的替代方法。“传统”构造函数和byType 自动装配模式(如【依赖 - 自动装配协作器】中所述)能够分别为构造函数参数或 setter 方法参数提供 ResourceLoader。要获得更大的灵活性(包括自动关联字段和多参数方法的能力),请考虑使用基于注解的自动关联功能。在这种情况下,ResourceLoader 被自动装配到需要 ResourceLoader 类型的字段、构造函数参数或方法参数中,只要该字段、构造函数或方法带有 @Autowired 注解。有关更多信息,请参见【基于注解的容器配置 - 使用 @Autowired】。

资源作为依赖项

Resources as Dependencies : 资源作为依赖项

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

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

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

请注意,资源路径没有前缀。因此,因为应用程序上下文本身将被用作 ResourceLoader,所以资源本身是通过 ClassPathResource、FileSystemResource 或 ServletContextResource 加载的,这取决于上下文的确切类型。

如果需要强制使用特定的 Resource(资源) 类型,可以使用前缀。以下两个示例显示了如何强制使用 ClassPathResource 和 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 : 应用程序上下文和资源路径

本节介绍如何使用资源创建应用程序上下文,包括使用 XML 的快捷方式、如何使用通配符以及其他细节。

构建应用程序上下文

应用程序上下文构造器(针对特定的应用程序上下文类型)通常将字符串或字符串数组作为资源的位置路径,例如构成 context(上下文) definition (定义) 的 XML 文件。

当这样的位置路径没有前缀时,从该路径构建并用于加载 bean definitions (定义)的特定 Resource(资源)类型取决于并适合于特定的应用程序上下文。例如,考虑下面的示例,该示例创建了一个 ClassPathXmlApplicationContext:

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

bean definitions (定义)是从类路径加载的,因为使用了 ClassPathResource。但是,请考虑下面的示例,该示例创建了一个FileSystemXmlApplicationContext:

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

现在,bean definitions (定义)从文件系统位置加载(在本例中,相对于当前工作目录)。

请注意,在位置路径上使用特殊的类路径前缀或标准的 URL 前缀会覆盖为加载 definitions (定义)而创建的默认 Resource(资源)类型。考虑下面的例子:

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

使用 FileSystemXmlApplicationContext 从类路径加载 bean definitions (定义)。但是,它仍然是一个 FileSystemXmlApplicationContext。如果它随后被用作 ResourceLoader(资源加载器),任何无前缀的路径仍然被视为文件系统路径。

构造 ClassPathXmlApplicationContext 实例-快捷方式

ClassPathXmlApplicationContext 公开了许多构造函数来实现方便的实例化。基本思想是,您可以只提供一个字符串数组,该数组只包含 XML 文件本身的文件名(没有前导路径信息),并且还提供一个 Class(类)。然后,ClassPathXmlApplicationContext 从提供的类中获取路径信息。

考虑以下目录布局:

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

以下示例显示了如何实例化由名为 services.xml 和 daos.xml 的文件(位于类路径中)中定义的 beans 组成的ClassPathXmlApplicationContext 实例:

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

有关各种构造函数的详细信息,请参见 ClassPathXmlApplicationContext javadoc。

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

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

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

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

Ant 样式模式

路径位置可以包含 Ant 样式的模式,如下例所示:

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

当路径位置包含 Ant 样式的模式时,解析器会遵循更复杂的过程来尝试解析通配符。它为直到最后一个非通配符段的路径生成一个 Resource(资源),并从中获取一个 URL。如果这个 URL 不是 jar: URL 或特定于容器的变量(比如 WebLogic 中的 zip: 和 WebSphere 中的wsjar,等等),则从中获取一个 java.io.File,用于通过遍历文件系统来解析通配符。对于 jar URL,解析器要么从中获取 java.net.JarURLConnection,要么手动解析 jar URL,然后遍历 jar 文件的内容以解析通配符。

对可移植性的影响

如果指定的路径已经是一个文件 URL (或者是隐式的,因为基本 ResourceLoader 是一个文件系统 URL,或者是显式的),通配符可以保证以完全可移植的方式工作。

如果指定的路径是 classpath (类路径)位置,解析器必须通过调用 Classloader.getResource() 来获取最后一个非通配符路径段 URL。因为这只是路径的一个节点(不是末尾的文件),所以实际上(在 ClassLoader javadoc 中)没有定义在这种情况下返回哪种 URL。实际上,它总是一个表示目录(classpath resource(类路径资源) 解析为文件系统位置)或某种 jar URL(类路径资源解析为 jar 位置)的 java.io.File。尽管如此,这个操作还是存在可移植性问题。

如果获得了最后一个非通配符段的 jar URL,解析器必须能够从中获得 java.net.JarURLConnection 或手动解析 jar URL,以便能够遍历 jar 的内容并解析通配符。这在大多数环境中确实可行,但在其他环境中会失败,我们强烈建议在您依赖它之前,在您的特定环境中彻底测试来自 jar 的资源的通配符解析。

classpath*: 前缀

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

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

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

通配符类路径依赖于基础类加载器的 getResources() 方法。由于目前大多数应用服务器都提供自己的类加载器实现,行为可能会有所不同,尤其是在处理 jar 文件时。检查 classpath* 是否有效的一个简单测试是使用 classloader (类加载器) 从 classpath(类路径) 上的 jar 中加载文件:getClass().getClassLoader().getResources(“<someFileInsideTheJar>”)。使用名称相同但放置在两个不同位置的文件尝试此测试。如果返回了不适当的结果,请检查应用程序服务器文档中可能影响类加载器行为的设置。

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

与通配符相关的其他说明

请注意,classpath:,当与 Ant 样式的模式结合使用时,在模式开始之前,只能可靠地处理至少一个根目录,除非实际的目标文件驻留在文件系统中。这意味着像 classpath*:*.xml 这样的模式。可能不会从 jar 文件的根目录中检索文件,而只会从扩展目录的根目录中检索文件。

Spring 检索类路径条目的能力源于 JDK 的 ClassLoader.getResources() 方法,该方法只返回空字符串的文件系统位置(表示要搜索的潜在根)。Spring 还评估 URLClassLoader 运行时配置和 jar 文件中的 java.class.path 清单,但这并不保证会导致可移植的行为。

扫描类路径包要求类路径中存在相应的目录条目。当您使用 Ant 构建 JAR 时,不要激活 JAR 任务的仅文件开关。此外,基于某些环境中的安全策略,类路径目录可能不会公开,例如,JDK 1.7.0_45 和更高版本上的独立应用程序(要求在清单中设置 “Trusted-Library(可信库)”,细节查看 https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)。

在 JDK 9 的模块路径( Jigsaw )上,Spring 的类路径扫描通常按预期工作。将资源放在一个专用的目录中也是非常值得推荐的,这样可以避免前面提到的搜索 jar 文件根级的可移植性问题。

带有 classpath:(类路径)的 Ant 样式模式:如果要搜索的根包在多个类路径位置都可用,则不能保证资源能够找到匹配的资源。考虑以下资源位置的示例:

com/mycompany/package1/service-context.xml

现在考虑一个 Ant 风格的路径,有人可能会使用它来尝试查找该文件:

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

这样的资源可能只在一个位置,但是当使用一个路径(比如前面的例子)来尝试解析它时,解析器处理 getResource("com/mycompany ") 返回的(第一个) URL。如果这个基本包节点存在于多个类加载器位置,实际的终端资源可能不在那里。因此,在这种情况下,您应该更喜欢使用 classpath*: 和相同的 Ant 样式模式,该模式搜索包含根包的所有类路径位置。

FileSystemResource 注意事项

未附加到 FileSystemApplicationContext 的 FileSystemResource (即,当 FileSystemApplicationContext 不是实际的 ResourceLoader 时)会像您预期的那样处理绝对路径和相对路径。相对路径相对于当前工作目录,而绝对路径相对于文件系统的根目录。

然而,出于向后兼容性(历史)的原因,当 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。以下示例显示了如何做到这一点:

// 实际的上下文类型无关紧要,资源将始终是 UrlResource
ctx.getResource("file:///some/resource/path/myTemplate.txt");
// 强制此 FileSystemXmlApplicationContext 通过 UrlResource 加载其定义
ApplicationContext ctx = new FileSystemXmlApplicationContext("file:///conf/context.xml");
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值