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

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

2.7.1 构造Application Contexts

application context constructor(特定context类型)通常使用string或者string数组作为resources路径的地址,比如定义context 的XML地址。

如果该地址没有前缀,那么该定义bean定义的Resource将会依赖于和他最相近的application context.比如下面的例子会使用ClassPathXmlApplicationContext:

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

这个bean定义将会从classpath加载,因为ClassPathResource被使用。但是下面的例子将会创建FileSystemXmlApplicationContext:

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

现在bean定义从文件系统加载。

如果加上前缀,那么将会覆盖默认的Resource类型。如下:

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

这里虽然使用了FileSystemXmlApplicationContext,但是还是会从classpath中加载。但是如果在后面的代码中,还有需要使用到ResourceLoader的, 那么没有前缀的paths仍然会被认为是filesystem路径。

构造ClassPathXmlApplicationContext-快捷方式

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

考虑下面的文件结构:

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

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

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

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

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

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

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

Ant-style Patterns

下面是 Ant-style patterns 的路径:

/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等),则从该URL获取java.io.File,并通过遍历文件系统来解析通配符。对于JAR URL,解析器要么从中获取java.net.JarURLConnection,要么手动解析JAR URL,然后遍历JAR文件的内容以解析通配符。

对可移植性的影响

如果指定的路径已经是一个文件URL(或者隐式地因为ResourceLoader是一个文件系统,或者显式地指定),则通配符是完全可移植的。

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

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

classpath*:前缀

构造基于XML的application context,路径地址可以使用classpath*: 前缀,如下:

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

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

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

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

通配符的其他关注点

注意,当与Ant样式模式结合使用时,除非实际目标文件位于文件系统中,否则classpath: 在模式开始之前只能在至少有一个根目录的情况下可靠的执行。这意味着classpath:*.xml等模式可能不会从jar文件的根目录中检索文件,而是只从扩展目录的根目录中检索文件。

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

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

Ant-style 模式classpath: resources 并不一定能保证匹配的资源,如果要搜索的根package存在于多个类路径地址中。考虑下面的资源情况:

com/mycompany/package1/service-context.xml

下面是可能用到的Ant-style path 匹配:

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

这样的资源只能位于一个位置,但是当使用前面的示例这样的路径来尝试解析它时,解析程序将使用getResource(“com/mycompany”);返回的(第一个)URL。如果此基本包节点存在于多个类加载器位置中,则实际的最终资源可能找不到。因此,在这种情况下,你应该更喜欢使用具有相同ant样式模式的classpath*:来搜索包含根包的所有类路径位置。

2.7.3 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。以下示例说明了如何执行此操作:

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