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 的getResources() 方法。可是现在大多数应用服务器提供了自身的 classloader 实现,其处理 jar 文件的形式可能各有不同。要在指定服务器测试 classpath*: 是否有效,简单点可以使用 getClass().getClassLoader().getResources("<someFileInsideTheJar>") 去加载类路径 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*:
会让解析器去搜索所有包含基础包节点的类路径。