Application Contexts and Resource Paths

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

构建应用上下文

应用程序上下文构造函数(对于一个特定的应用程序上下文类型)通常需要一个字符串或字符串数组作为资源的位置路径,例如构成上下文定义的 XML 文件。

当这样的位置路径没有前缀时,从该路径建立并用于加载 Bean 定义的特定资源类型取决于并适合于特定的应用环境。例如,考虑下面的例子,它创建了一个 ClassPathXmlApplicationContext:

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

由于使用了 ClassPathResource,所以 bean 定义是从 classpath 中加载的。但是,请看下面的例子,它创建了一个 FileSystemXmlApplicationContext:

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

现在,Bean 的定义是从文件系统的一个位置(在这种情况下,相对于当前工作目录)加载的。

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

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

使用 FileSystemXmlApplicationContext 会从 classpath 中加载 Bean 定义。但是,它仍然是一个 FileSystemXmlApplicationContext。如果它随后被用作资源加载器,任何未加前缀的路径仍被视为文件系统路径。

构建 ClassPathXmlApplicationContext 实例的快捷方式

ClassPathXmlApplicationContext 公开了一些构造函数,以方便实例化。基本的想法是,你可以只提供一个字符串数组,其中只包含 XML 文件本身的文件名(没有领先的路径信息),同时提供一个类。然后ClassPathXmlApplicationContext 从提供的类中导出路径信息。

请考虑以下目录布局:

  1. com/
  2. example/
  3. services.xml
  4. repositories.xml
  5. MessengerService.class

下面的例子显示了如何实例化一个 ClassPathXmlApplicationContext 实例,该实例由名为 services.xml 和repositories.xml 的文件(在 classpath 上)中定义的 bean 组成。

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

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

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

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

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

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

Ant 风格

路径位置可以包含 Ant 风格的模式,如下面的例子所示:

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

当路径位置包含一个 Ant 风格的模式时,解析器遵循一个更复杂的程序来尝试解析通配符。它为路径产生一个资源,直到最后一个非通配符段,并从中获得一个 URL。如果这个 URL 不是一个 jar: URL 或容器特定的变体(如 WebLogic 中的 zip:,WebSphere 中的 wsjar,等等),则从它那里获得 java.io.File 并通过遍历文件系统来解决通配符。在 jar: URL 的情况下,解析器要么从中获取 java.net.JarURLConnection,要么手动解析 jar: URL,然后遍历 jar 文件的内容以解析通配符。

对可移植性的影响

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

如果指定的路径是 classpath 位置,解析器必须通过调用 Classloader.getResource() 获得最后的非通配符路径段 URL。由于这只是路径的一个节点(而不是最后的文件),实际上没有定义(在 ClassLoader 的 javadoc 中)在这种情况下到底会返回什么样的 URL。在实践中,它总是一个代表目录的java.io.File(在 classpath 资源解析到文件系统位置的情况下)或某种 jar URL(在 classpath 资源解析到 jar 位置的情况下)。不过,在这个操作上还是有一个可移植性问题。

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

classpath*:前缀

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

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

这个特殊的前缀指定了所有符合给定名称的 classpath 资源必须被获取(在内部,这基本上是通过调用ClassLoader.getResources(...)发生的),然后合并形成最终的应用程序上下文定义。

:::info 通配符 classpath 依赖于底层 ClassLoader 的 getResources()方法。由于现在大多数应用服务器都提供自己的 ClassLoader 实现,行为可能会有所不同,特别是在处理 jar 文件时。检查 classpath*是否有效的一个简单测试是使用 ClassLoader 从 classpath 上的 jar 中加载一个文件:getClass().getClassLoader().getResources("<someFileInsideTheJar>")。试着用具有相同名称但位于两个不同位置的文件进行这个测试—例如,具有相同名称和相同路径但位于 classpath 上不同 jar 中的文件。如果返回的结果不合适,请检查应用服务器文档中可能影响 ClassLoader 行为的设置。 :::

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

与通配符有关的其他说明

请注意,classpath*:与 Ant 风格的模式相结合时,只有在模式开始前至少有一个根目录时才能可靠地工作,除非实际目标文件位于文件系统中。这意味着像 classpath*:*.xml这样的模式可能不会从 jar 文件的根目录中检索文件,而只能从扩展目录的根目录中检索。

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

:::info 扫描 classpath 包需要 classpath 中存在相应的目录项。当你用 Ant 构建 JAR 时,不要激活 JAR 任务的纯文件开关。另外,在某些环境中,classpath 目录可能不会根据安全策略被暴露出来—例如,JDK 1.7.0_45 及以上版本的独立应用程序(这需要在清单中设置 ‘Trusted-Library’ 。见https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)。

在 JDK 9 的模块路径(Jigsaw)上,Spring 的 classpath 扫描一般都能如期进行。这里也强烈建议将资源放到一个专门的目录中,以避免前面提到的搜索 jar 文件根层的可移植性问题。 :::

如果要搜索的根包在多个 classpath 位置上可用,则不保证能找到匹配的资源,带有 classpath 的 Ant 风格模式:classpath:。考虑一下下面这个资源位置的例子:

  1. com/mycompany/package1/service-context.xml

现在考虑一个 Ant 风格的路径,有人可能会用它来试图找到这个文件:

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

这样的资源可能只存在于 classpath 中的一个位置,但是当像前面的例子那样的路径被用来试图解析它时,解析器根据 getResource("com/mycompany");返回的(第一个)URL 来工作。如果这个基础包节点存在于多个 ClassLoader 位置,所需的资源可能不存在于找到的第一个位置。因此,在这种情况下,你应该更倾向于使用 classpath*:与 Ant 风格的模式,它可以搜索所有包含 com.mycompany 基础包的 classpath 位置:classpath*:com/mycompany/**/service-context.xml

FileSystemResource

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

然而,出于向后兼容(历史)的原因,当 FileSystemApplicationContext 为 ResourceLoader 时,情况会发生变化。FileSystemApplicationContext 强制所有附加的 FileSystemResource 实例将所有位置路径视为相对路径,无论它们是否以前导斜线(/)开始。在实践中,这意味着以下例子是等同的:

  1. ApplicationContext ctx =
  2. new FileSystemXmlApplicationContext("conf/context.xml");
  3. 等同于
  4. ApplicationContext ctx =
  5. new FileSystemXmlApplicationContext("/conf/context.xml");

下面的例子也是等价的(尽管它们的不同是有道理的,因为一种情况是相对的,另一种是绝对的):

  1. FileSystemXmlApplicationContext ctx = ...;
  2. ctx.getResource("some/resource/path/myTemplate.txt");
  3. 等同于
  4. FileSystemXmlApplicationContext ctx = ...;
  5. ctx.getResource("/some/resource/path/myTemplate.txt");

在实践中,如果需要真正的绝对文件系统路径,应避免使用 FileSystemResource 或 FileSystemXmlApplicationContext 的绝对路径,而是通过使用 file.URL 前缀强制使用 UrlResource。URL 前缀来强制使用 UrlResource。下面的例子展示了如何做到这一点:

  1. // 实际的上下文类型并不重要,资源将始终是 UrlResource。
  2. ctx.getResource("file:///some/resource/path/myTemplate.txt");
  3. // 强制该 FileSystemXmlApplicationContext 通过 UrlResource 加载其定义
  4. ApplicationContext ctx =
  5. new FileSystemXmlApplicationContext("file:///conf/context.xml");