application context构造器通常使用字符串或字符串数组作为资源(比如组成context定义 的XML文件)的定位路径。
当这样的定位路径没有前缀时,指定的 Resource
类型会通过这个路径来被创建并被用来载入bean的定义,这都取决于你所指定的application context。例如,如果你使用下面的代码来创建ClassPathXmlApplicationContext
:
ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");
这些Bean的定义会通过classpath载入并使用ClassPathResource
。而如果你象下面这么创建FileSystemXmlApplicationContext
:
ApplicationContext ctx = new FileSystemXmlApplicationContext("conf/appContext.xml");
这些Bean的定义会通过文件系统从相对于当前工作目录中被载入。
请注意如果定位路径使用classpath前缀或标准的URL前缀,那它就会覆盖默认的Resource
类型。因此下面的FileSystemXmlApplicationContext
...
ApplicationContext ctx = new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");
...实际上会通过classpath载入其bean定义。然而它仍是个FileSystemXmlApplicationContext
。
如果后面它被当作ResourceLoader
来使用,那么任何没有使用前缀的路径依然会被当作一个文件系统路径。
ClassPathXmlApplicationContext
提供了多种构造方法以便于初始化。但其核心是,如果我们仅仅提供由XML文件名组成的字符串数组(没有完整路径信息), 而且还提供了Class
;那么该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);
欲了解 ClassPathXmlApplicationContext
多种构造方法的细节,请参考它的Javadocs。
Application context构造器中资源路径的值可以是简单的路径(就像上面的那样),即一对一映射到一个目标资源;
或者可以包含特殊的"classpath*:"前缀和Ant风格的正则表达式(用Spring的 PathMatcher
工具来匹配)。
后面的二者都可以使用通配符。
该机制的一个用处就是做组件类型的应用组装。所有的组件都可以用通用的定位路径“发布”context定义片断,
这样当使用相同的 classpath*:
前缀创建最终的application context时,所有的组件片断都会被自动装入。
请注意,这个通配符只在application context构造器的资源路径中
(或直接在类的层次中使用 PathMatcher
工具时)有效,
它会在构造时进行解析。这与 Resource
类型本身没有关联。因为同一时刻只能指向一个资源,所以不能使用
classpath*:
前缀来构造实际的Resource
。
在包含Ant风格的pattern时,例如:
/WEB-INF/*-context.xml com/mycompany/**/applicationContext.xml file:C:/some/path/*-context.xml classpath:com/mycompany/**/applicationContext.xml
解析器会进行一个预先定义的复杂的过程去试图解析通配符。
它根据路径中最后一个非通配符片断产生一个Resource并从中获得一个URL。
如果这个URL不是一个"jar:" URL或特定容器的变量(例如WebLogic中的
"zip:
",WebSphere中的"wsjar
"等等),
那么可以从中获得一个java.io.File
,
并用它从文件系统中解析通配符。如果是一个jar URL,解析器可以从中取得一个
java.net.JarURLConnection
,或者手工解析该jar URL,
随后遍历jar文件以解析通配符。
如果给定的路径已经是一个文件URL(可以是显式的或者是隐式的), 由于基本的ResourceLoader是针对文件系统的,那么通配符一定能够移植。
如果给定的路径是一个classpath的位置,那么解析器必须通过一个
Classloader.getResource()
调用获得最后一个
非通配符路径片断的URL。因为这仅仅是一个路径的节点(不是最终的文件),
所以它并未确切定义(在 ClassLoader
Javadocs里)
此处究竟会返回什么类型的URL。一般情况下,当classpath资源解析为一个文件系统位置时,
返回一个代表目录的 java.io.File
;当解析为jar位置时,
返回某类jar URL。当然,这个操作涉及到可移植性。
如果从最后一个非通配符片断中获得一个jar URL,那么解析器一定能从中取得一个
java.net.JarURLConnection
,或者手动解析jar URL以遍历jar文件,
从而解析通配符。这一操作在大多数环境中能正常工作,不过也有例外,
因此我们强烈建议特定环境中的jar资源通配符解析应在正式使用前要经过彻底测试。
当构造基于XML的application context时,路径字符串可能使用特殊的 classpath*:
前缀:
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath*:conf/appContext.xml");
此前缀表示所有与给定名称匹配的classpath资源都应该被获取(其中,这经常会在调用 ClassLoader.getResources(...)
) 时发生),并接着将那些资源全并成最终的application context定义。
Classpath*: 的可移植性
带通配符的classpath依赖于底层classloader的 getResources()
方法。
现在大多数的应用服务器提供自己的classloader实现,它们在处理jar文件时的行为也许会有所不同。
要测试 classpath*:
是否有效,可以简单地用classloader从classpath中的jar文件里加载一个文件:
getClass().getClassLoader().getResources("<someFileInsideTheJar>")
。
针对两个不同位置但有相同名字的文件来运行测试。如果结果不对,那么就查看一下应用服务器的文档,
特别是那些可能影响classloader行为的设置。
"classpath*:
"前缀也能在位置路径的其他部分结合PathMatcher
pattern一起使用,例如"classpath*:META-INF/*-beans.xml
"。在这里,
解析策略很简单:对最后一个非通配符路径片断使用一个ClassLoader.getResources()调用,
从类加载层次中获得所有满足的资源,随后针对子路径的通配符,
将同一个PathMatcher解析策略运用于每个资源之上。
请注意如果目标文件不是在文件系统中,那么"classpath*:
"
在结合Ant风格的pattern时必须在pattern开始前包含至少一个根路径才能保证其正确性。
像"classpath*:*.xml
"这样的pattern不能从jar文件的根目录取得文件,
而只能从这个根目录的子目录中获得文件。这个问题源自JDK中 ClassLoader.getResources()
方法的一个局限性,即该方法在传入空String(指示要搜索的潜在根目录)时只返回文件系统位置。
如果搜索的根包在多个类路径位置上,带"classpath:
"的Ant风格pattern
资源不能保证一定可以找到匹配的资源。这是因为像
com/mycompany/package1/service-context.xml
这样的资源只可能在一个位置,但如果要解析的是如下路径
classpath:com/mycompany/**/service-context.xml
解析器会排除getResource("com/mycompany")
;返回的(第一个)URL。
如果这个基础包节点存在于多个classloader位置,最终要找的资源未必会被发现。
因此在这种情况中最好在这个Ant风格的pattern中使用"classpath*:
",
这样就会搜索包含根包在内所有类路径。
一个并没有与 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 it's definition via a UrlResource
ApplicationContext ctx =
new FileSystemXmlApplicationContext("file:/conf/context.xml");