2.8. 应用程序上下文和资源路径

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

2.8.1. 构建应用程序上下文

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

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

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

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

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

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

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

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

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

构造ClassPathXmlApplicationContext实例——快捷方式

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

考虑以下目录布局:

com/
  example/
    services.xml
    repositories.xml
    MessengerService.class

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

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

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

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

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

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

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

Ant样式的模式

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

/WEB-INF/*-context.xml
com/mycompany/**/applicationContext.xml
file:C:/some/path/*-context.xml
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获取最后一个非通配符路径段 URL 。Classloader.getResource()由于这只是路径的一个节点(不是末尾的文件),因此实际上未定义(在 ClassLoaderjavadoc 中)在这种情况下返回的 URL 类型。在实践中,它始终是java.io.File表示目录(类路径资源解析为文件系统位置的位置)或某种 jar URL(类路径资源解析为 jar 位置的位置)。尽管如此,此操作仍存在可移植性问题。

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

classpath*:前缀

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

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

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

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

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

与通配符有关的其他说明

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

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

类路径包的扫描需要类路径中存在相应的目录条目。使用 Ant 构建 JAR 时,不要激活 JAR 任务的files-only开关。此外,根据某些环境中的安全策略,类路径目录可能不会暴露——例如,JDK 1.7.0_45 及更高版本上的独立应用程序(这需要在清单中设置“可信库”。请参阅 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; 。如果此基础包节点存在于多个 ClassLoader 位置,则所需的资源可能不存在于找到的第一个位置。因此,在这种情况下,您应该更喜欢使用 classpath*: 和相同的 Ant 样式模式,该模式会搜索包含 com.mycompany 基础包的所有类路径位置:classpath*:com/mycompany/**/service-context.xml

2.8.3. FileSystemResource注意事项

未附加到 FileSystemResourceFileSystemApplicationContext(即,当 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");

实际上,如果您需要真正的绝对文件系统路径,则应避免将绝对路径与 FileSystemResourceFileSystemXmlApplicationContext 一起使用,并通过使用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");

最后更新于