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

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

**2.8.1. 构建应用程序上下文**

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

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

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

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

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

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

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

```java
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.xml`和 `repositories.xml`（位于类路径上）的文件中定义的 bean 组成的 `ClassPathXmlApplicationContext` 实例：

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

有关各种构造函数的详细信息，请参阅[`ClassPathXmlApplicationContext`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/context/support/ClassPathXmlApplicationContext.html) 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()`由于这只是路径的一个节点（不是末尾的文件），因此实际上未定义（在 `ClassLoader`javadoc 中）在这种情况下返回的 URL 类型。在实践中，它始终是`java.io.File`表示目录（类路径资源解析为文件系统位置的位置）或某种 jar URL（类路径资源解析为 jar 位置的位置）。尽管如此，此操作仍存在可移植性问题。

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

**`classpath*:`前缀**

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

```java
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.path`jar 文件中的清单，但这并不保证会导致可移植行为。

类路径包的扫描需要类路径中存在相应的目录条目。使用 Ant 构建 JAR 时，不要激活 JAR 任务的`files-only`开关。此外，根据某些环境中的安全策略，类路径目录可能不会暴露——例如，JDK 1.7.0\_45 及更高版本上的独立应用程序（这需要在清单中设置“可信库”。请参阅 [https:// /stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources](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`注意事项**

未附加到 `FileSystemResource` 的 `FileSystemApplicationContext`（即，当 `FileSystemApplicationContext`不是实际的`ResourceLoader`时）按照您的预期处理绝对和相对路径。相对路径是相对于当前工作目录的，而绝对路径是相对于文件系统的根目录的。

然而，出于向后兼容性（历史）原因，当 `FileSystemApplicationContext` 是 ResourceLoader 时，这种情况会发生变化。 `FileSystemApplicationContext` 强制所有附加的`FileSystemResource`实例将所有位置路径视为相对路径，无论它们是否以前导斜杠开头。实际上，这意味着以下示例是等效的：

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

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

以下示例也是等价的（即使它们不同是有意义的，因为一种情况是相对的，另一种是绝对的）：

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

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

实际上，如果您需要真正的绝对文件系统路径，则应避免将绝对路径与 `FileSystemResource` 或 `FileSystemXmlApplicationContext` 一起使用，并通过使用`file:`URL 前缀强制使用 `UrlResource`。以下示例展示了如何执行此操作：

```java
// actual context type doesn't matter, the Resource will always be UrlResource
ctx.getResource("file:///some/resource/path/myTemplate.txt");
```

```java
// force this FileSystemXmlApplicationContext to load its definition via a UrlResource
ApplicationContext ctx =
    new FileSystemXmlApplicationContext("file:///conf/context.xml");
```
