4.7 应用上下文和资源路径
4.7.1 构造应用上下文
(某一特定)应用上下文的构造器通常可以使用字符串或字符串数组所指代的(多个)资源(如 xml 文件)来构造当前上下文。
当指定的位置路径没有带前缀时,那从指定位置路径创建的 Resource 类型(用于后续加载 bean 定义),取决于所使用应用上下文。举个列子,如下所创建的 ClassPathXmlApplicationContext :
ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");
会从类路径加载 bean 的定义,因为所创建的 Resource 实例是 ClassPathResource.但所创建的是 FileSystemXmlApplicationContext 时,
ApplicationContext ctx = new FileSystemXmlApplicationContext("conf/appContext.xml");
则会从文件系统加载 bean 的定义,这种情况下,资源路径是相对工作目录而言的。
注意:若位置路径带有 classpath 前缀或 URL 前缀,会覆盖默认创建的用于加载 bean 定义的 Resource 类型,比如这种情况下的 FileSystemXmlApplicationContext
ApplicationContext ctx = new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");
,实际是从类路径下加载了 bean 的定义。可是,这个上下文仍然是 FileSystemXmlApplicationContext,而不是 ClassPathXmlApplicationContext,在后续作为 ResourceLoader 来使用时,不带前缀的路径仍然会从文件系统中加载。
构造 ClassPathXmlApplicationContext 实例 – 快捷方式
ClassPathXmlApplicationContext 提供了多个构造函数,以利于快捷创建 ClassPathXmlApplicationContext 的实例。最好莫不过使用只包含多个 xml 文件名(不带路径信息)的字符串数组和一个 Class 参数的构造器,所省略路径信息 ClassPathXmlApplicationContext 会从 Class 参数 获取:
下面的这个例子,可以让你对个构造器有比较清晰的认识。试想一个如下类似的目录结构:
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);
欲要知道 ClassPathXmlApplicationContext 更多不同类型的构造器,请查阅 Javadocs 文档。
4.7.2 使用通配符构造应用上下文
从前文可知,应用上下文构造器的中的资源路径可以是单一的路径(即一对一地映射到目标资源);另外资源路径也可以使用高效的通配符——可包含 classpath*:前缀 或 ant 风格的正则表达式(使用 spring 的 PathMatcher 来匹配)。
通配符机制的其中一种应用可以用来组装组件式的应用程序。应用程序里所有组件都可以在一个共知的位置路径发布自定义的上下文片段,则最终应用上下文可使用 classpath*: 在同一路径前缀(前面的共知路径)下创建,这时所有组件上下文的片段都会被自动组装。
谨记,路径中的通配符特定用于应用上下文的构造器,只会在应用构造时有效,与其 Resource 自身类型没有任何关系。不可以使用 classpth*:来构造任一真实的 Resource,因为一个资源点一次只可以指向一个资源。(如果直接使用 PathMatcher 的工具类,也可以在路径中使用通配符)
Ant 风格模式
以下是一些使用了 Ant 风格的位置路径:
/WEB-INF/*-context.xml
com/mycompany/**/applicationContext.xml
file:C:/some/path/*-context.xml
classpath:com/mycompany/**/applicationContext.xml
当位置路径使用了 ant 风格,解释器会遵循一套复杂且预定义的逻辑来解释这些位置路径。解释器会先从位置路径里获取最靠前的不带通配符的路径片段,使用这个路径片段来创建一个 Resource ,并从 Resource 里获取其 URL,若所获取到 URL 前缀并不是 “jar:”,或其他特殊容器产生的特殊前缀(如 WebLogic 的 zip:,WebSphere wsjar),则从 Resource 里获取 java.io.File 对象,并通过其遍历文件系统。进而解决位置路径里通配符;若获取的是 “jar:”的 URL ,解析器会从其获取一个 java.net.JarURLConnection 或手动解析此 URL,并遍历 jar 文件的内容进而解决位置路径的通配符。
对可移植性的影响
如果指定的路径已经是文件URL(显式地或隐含地,因为基本的ResourceLoader是一个文件系统的,那么通配符将保证以完全可移植的方式工作。
如果指定的路径是类路径位置,则解析器必须通过Classloader.getResource()调用获取最后一个非通配符路径段URL。 由于这只是路径的一个节点(而不是最后的文件),在这种情况下,它实际上是未定义的(在ClassLoader javadocs中)返回的是什么样的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");
classpath*:的使用表示类路径下所有匹配文件名称的资源都会被获取(本质上就是调用了 ClassLoader.getResources(…) 方法),接着将获取到的资源组装成最终的应用上下文。
通配符路径依赖了底层 classloader 的 getResource 方法。可是现在大多数应用服务器提供了自身的 classloader 实现,其处理 jar 文件的形式可能各有不同。要在指定服务器测试 classpath*: 是否有效,简单点可以使用 getClass().getClassLoader().getResources(“”) 去加载类路径 jar包里的一个文件。尝试在两个不同的路径加载名称相同的文件,如果返回的结果不一致,就需要查看一下此服务器中与 classloader 行为设置相关的文档。
在位置路径的其余部分,classpath*: 前缀可以与 PathMatcher 结合使用,如:” classpath*:META-INF/*-beans.xml”。这种情况的解析策略非常简单:取位置路径最靠前的无通配符片段,调用 ClassLoader.getResources() 获取所有匹配的类层次加载器可加载的的资源,随后将 PathMacher 的策略应用于每一个获得的资源(起过滤作用)。
通配符的补充说明
除非所有目标资源都存于文件系统,否则classpath*:和 ant 风格模式的结合使用,都只能在至少有一个确定根包路径的情况下,才能达到预期的效果。换句话说,就是像 classpath*:*.xml 这样的 pattern 不能从根目录的 jar 文件中获取资源,只能从根目录的扩展目录获取资源。此问题的造成源于 jdk ClassLoader.getResources() 方法的局限性——当向 ClassLoader.getResources() 传入空串时(表示搜索潜在的根目录),只能获取的文件系统的文件位置路径,即获取不了 jar 中文件的位置路径。
如果在多个类路径上存在所搜索的根包,那使用 classpath: 和 ant 风格模式一起指定的资源不保证找到匹配的资源。因为使用如下的 pattern classpath:com/mycompany/**/service-context.xml
去搜索只在某一个路径存在的指定资源com/mycompany/package1/service-context.xml
时,解析器只会对 getResource(“com/mycompany”) 返回的(第一个) URL 进行遍历和解释,则当在多个类路径存在基础包节点 “com/mycompany” 时(如在多个 jar 存在这个基础节点),解析器就不一定会找到指定资源。因此,这种情况下建议结合使用 classpath*: 和 ant 风格模式,classpath*:会让解析器去搜索所有包含基础包节点的类路径。
4.7.3 FileSystemResource 注意事项
FileSystemResource 没有依附 FileSystemApplicationContext,因为 FileSystemApplicationContext 并不是一个真正的 `ResourceLoader。FileSystemResource 并没有按约定规则来处理绝对和相对路径。相对路径是相对与当前工作而言,而绝对路径则是相对文件系统的根目录而言。
然而为了向后兼容,当 FileSystemApplicationContext 是一个 ResourceLoader 实例时,我们做了一些改变 —— 不管 FileSystemResource` 实例的位置路径是否以 / 开头, FileSystemApplicationContext 都强制将其作为相对路径来处理。事实上,这意味着以下例子等效:
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: 的 UrlResource。
// Resource 只会是 UrlResource,与上下文的真实类型无关
ctx.getResource("file:///some/resource/path/myTemplate.txt");
// 强制 FileSystemXmlApplicationContext 通过 UrlResource 加载资源
ApplicationContext ctx = new FileSystemXmlApplicationContext("file:///conf/context.xml");