17、Resources——资源
本章介绍 Spring 如何处理资源,以及如何在 Spring 中使用资源。它包括以下主题:
- 介绍
- Resource
- 内置 Resource
- ResourceLoader
- ResourcePatternResolver
- ResourceLoaderAware
- 资源依赖关系
- 应用程序上下文和资源路径
17.1、介绍
不幸的是,Java 的标准 java.net.URL
类和各种 URL
前缀的标准处理程序不足以满足对所有低级资源的访问。例如,没有标准化的 URL
实现可以用来访问需要从类路径或相对于 ServletContext
获得的资源。虽然可以为专门的 URL
前缀注册新的处理程序(类似于现有的前缀处理程序,如http:
),但这通常相当复杂,而且 URL
接口仍然缺乏一些理想的功能,例如检查所指向的资源是否存在的方法。
17.2、 Resource
接口
位于 org.springframework.core.io.
包中的 Spring Resource
接口旨在成为一个更能抽象访问低级资源的接口。下面的清单提供了 Resource
接口的概述。有关更多细节,请参阅 Resource Java文档。
package org.springframework.core.io;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import org.springframework.lang.Nullable;
public interface Resource extends InputStreamSource {
boolean exists();
default boolean isReadable() {
return this.exists();
}
default boolean isOpen() {
return false;
}
default boolean isFile() {
return false;
}
URL getURL() throws IOException;
URI getURI() throws IOException;
File getFile() throws IOException;
default ReadableByteChannel readableChannel() throws IOException {
return Channels.newChannel(this.getInputStream());
}
long contentLength() throws IOException;
long lastModified() throws IOException;
Resource createRelative(String relativePath) throws IOException;
@Nullable
String getFilename();
String getDescription();
}
正如 Resource
接口的定义所示,它扩展了InputStreamSource
接口。下面的清单显示了 InputStreamSource
接口的定义:
package org.springframework.core.io;
import java.io.IOException;
import java.io.InputStream;
public interface InputStreamSource {
InputStream getInputStream() throws IOException;
}
来自 Resource
接口的一些最重要的方法有:
-
getInputStream()
: 找到并打开资源,返回一个 InputStream 用于从资源中读取。期望每次调用都返回一个新的 InputStream。关闭流是调用者的责任。 -
exists()
: 返回一个布尔值 ,指示此资源是否以物理形式实际存在。 -
isOpen()
: 返回一个布尔值,指示此资源是否表示具有打开流的句柄。如果为true,则不能多次读取InputStream,必须只读取一次,然后关闭以避免资源泄漏。对于所有常见的资源实现返回false,除了InputStreamResource。 -
getDescription()
: 返回此资源的描述,用于处理该资源时的错误输出。这通常是完全限定的文件名或资源的实际URL。
其他方法让你获得表示资源的实际 URL
或 File
对象(如果底层实现兼容并支持该功能)。
Resource
接口的一些实现还为支持写入的资源实现了扩展的 WritableResource 接口。
Spring本身广泛地使用 Resource
抽象,当需要资源时,作为许多方法签名中的参数类型。一些Spring API中的其他方法(例如各种 ApplicationContext
实现的构造函数)接受一个 String
,它以不加修饰或简单的形式用于创建适合于该上下文实现的资源,或者通过 String
路径上的特殊前缀,让调用者指定必须创建和使用特定的Resource
实现。
虽然Spring和通过Spring都大量使用 Resource
接口,但实际上,在你自己的代码中作为一个通用实用程序类使用它非常方便,可以访问资源,即使你的代码不知道或不关心Spring的任何其他部分。虽然这将你的代码与Spring结合在一起,但它实际上只将其与这一小组实用程序类结合在一起,这些实用程序类可以更有效地替代 URL
,并且可以认为与用于此目的的任何其他库等价。
Resource
抽象不能取代功能。它尽可能地把它包起来。例如,UrlResource
包装 URL,并使用包装的URL
完成其工作。
17.3、内置 Resource
实现
Spring包含了几个内置的 Resource
实现:
- UrlResource
- ClassPathResource
- FileSystemResource
- PathResource
- ServletContextResource
- InputStreamResource
- ByteArrayResource
有关Spring中可用的 Resource
实现的完整列表,请参阅 Resource Java文档的 “所有已知的实现类” 一节。。
17.3.1、UrlResource
UrlResource
包装了一个 java.net.URL
,可以用来访问通常可以通过URL访问的任何对象,比如文件、HTTPS目标、FTP目标等。所有URL都有一个标准化的String
表示,这样就可以使用适当的标准化前缀来表示不同的URL类型。包括 file:
用于访问文件系统路径; https:
用于通过https协议访问资源;ftp:
用于通过ftp协议访问资源。
UrlResource
是由Java代码通过显式地使用 UrlResource
构造函数创建的,但通常在调用带有表示路径的 String
参数的API方法时隐式创建。对于后一种情况,JavaBeans PropertyEditor
最终决定创建哪种类型的资源。如果路径字符串包含一个众所周知的(即属性编辑器)前缀 ( 例如 classpath:
),它将为该前缀创建一个适当的专门化资源。但是,如果它不能识别这个前缀,它就假定这个字符串是标准的URL字符串,并创建一个 UrlResource
。
17.3.2、 ClassPathResource
该类表示应该从类路径获得的资源。它使用线程上下文类加载器、给定的类加载器或给定的类来加载资源。
如果类路径资源驻留在文件系统中,则该 Resource
实现支持作为 java.io.File
进行解析,但不支持驻留在jar中的类路径资源,并且没有(由servlet引擎或其他环境)扩展到文件系统中。为了解决这个问题,各种Resource
实现总是以 java.net.URL
的形式支持解析。
ClassPathResource
是由Java代码通过显式地使用 ClassPathResource
构造函数创建的,但通常在调用带有表示路径的 String
参数的API方法时隐式创建。对于后一种情况,JavaBeans PropertyEditor
识别字符串路径上的特殊前缀classpath:
,并在这种情况下创建一个 ClassPathResource
。
17.3.3、 FileSystemResource
这是 java.io.File
句柄的Resource
实现。它还支持 java.nio.file.Path
句柄,应用Spring标准的基于string的路径转换,但通过 java.nio.file.Files
API执行所有操作。对于纯粹的基于 java.nio.path.Path
的支持,使用 PathResource
代替。FileSystemResource
支持以 File
和 URL
的形式进行解析。
17.3.4、 PathResource
这是 java.nio.file.Path
句柄的Resource
实现,通过Path
API执行所有操作和转换。它支持以 File
和 URL
的形式进行解析,还实现了扩展的 WritableResource
接口。PathResource
实际上是一个基于纯 java.nio.path.Path
的替代 FileSystemResource
,具有不同的 createRelative
行为。
17…3.5、 ServletContextResource
这是一个 ServletContext
资源的 Resource
实现,它解释了相关web应用的根目录中的相对路径。
它总是支持流访问和URL访问,但只有当web应用程序存档被扩展并且资源物理上在文件系统上时,才允许 java.io.File
访问。不管它是在文件系统上展开还是直接从JAR或其他地方(如数据库)访问,实际上都依赖于Servlet容器。
17…3.6、 InputStreamResource
InputStreamResource
是给定 InputStream
的 Resource
实现。只有在不适用特定 Resource
实现的情况下才应该使用它。特别是,在可能的情况下,更喜欢 ByteArrayResource
或任何基于文件的 Resource
实现。
与其他 Resource
实现相比,这是一个已经打开的资源的描述符。因此,它从 isOpen()
返回 true
。如果需要将资源描述符保存在某处,或者需要多次读取流,则不要使用它。
17…3.7、 ByteArrayResource
这是给定字节数组的 Resource
实现。它为给定的字节数组创建一个 ByteArrayInputStream
。
对于从任何给定的字节数组加载内容,而不需要使用单一的 InputStreamResource
,它非常有用。
17.4、ResourceLoader
接口
ResourceLoader
接口是由能够返回(即加载) Resource
实例的对象实现的。下面的清单显示了 ResourceLoader
接口定义:
package org.springframework.core.io;
import org.springframework.lang.Nullable;
import org.springframework.util.ResourceUtils;
public interface ResourceLoader {
String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
Resource getResource(String location);
@Nullable
ClassLoader getClassLoader();
}
所有应用程序上下文都实现 ResourceLoader
接口。因此,可以使用所有应用程序上下文来获取 Resource
实例。
当你在特定的应用程序上下文中调用 getResource()
,并且指定的位置路径没有特定的前缀时,你将得到适合该特定应用程序上下文中的 Resource
类型。例如,假设下面的代码片段是针对 ClassPathXmlApplicationContext
实例运行的:
Resource template = ctx.getResource("some/resource/path/myTemplate.txt");
针对 ClassPathXmlApplicationContext
,该代码返回一个 ClassPathResource
。如果对 FileSystemXmlApplicationContext
实例运行相同的方法,它将返回一个 FileSystemResource
。对于 WebApplicationContext
,它将返回一个 ServletContextResource
。它同样会为每个上下文返回适当的对象。
因此,你可以以适合特定应用程序上下文的方式加载资源。
另一方面,通过指定特殊的 classpath:
前缀,也可以强制使用 ClassPathResource
,无论应用程序的上下文类型如何,如下面的例子所示:
Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");
同样的,你可以通过指定任何标准的 java.net.URL
前缀强制使用 UrlResource
。使用 file
和 https
前缀的示例如下:
Resource templateFile = ctx.getResource("file:///some/resource/path/myTemplate.txt");
Resource templateHttps = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt");
下表总结了将 String
对象转换为 Resource
对象的策略:
前缀 | 例子 | 解释 |
classpath: |
| 从类路径加载 |
file: |
| 作为URL从文件系统加载。请查看 FileSystemResource |
https: |
| 加载为 |
(none) |
| 取决于底层 |
17.5、 ResourcePatternResolver
接口
ResourcePatternResolver
接口是对 ResourceLoader
接口的扩展,ResourceLoader
接口定义了将位置模式(例如,Ant风格的路径模式)解析为 Resource
对象的策略。
public interface ResourcePatternResolver extends ResourceLoader {
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
Resource[] getResources(String locationPattern) throws IOException;
}
如上所述,该接口还为类路径中的所有匹配资源定义了一个特殊的 classpath*:
资源前缀。请注意,在这种情况下,资源位置应该是没有占位符的路径 — 例如,classpath*:/config/beans.xml
JAR文件或类路径中的不同目录可以包含具有相同路径和相同名称的多个文件。有关 classpath*:
资源前缀的通配符支持的更多详细信息,请参阅应用程序上下文构造函数资源路径及其子部分中的通配码。
可以检查传入的 ResourceLoader
( 例如,通过 ResourceLoaderAware
语义提供的 ResourceLoader
) 是否也实现了这个扩展接口。
PathMatchingResourcePatternResolver
是一个独立的实现,它可以在 ApplicationContext
之外使用,也可以被 ResourceArrayPropertyEditor
用于填充 Resource[]
Bean属性。PathMatchingResourcePatternResolver
能够将指定的资源位置路径解析为一个或多个匹配的 Resource
对象。源路径可能是一个简单的路径,它有一个到目标 Resource
的一对一映射,或者可能包含特殊的 classpath*:
前缀和/或内部的 Ant 风格正则表达式(使用Spring的 org.springframework.util.AntPathMatcher
实用程序进行匹配)。后者都是有效的通配符。
在任何标准的
ApplicationContext
中,默认的ResourceLoader
实际上是PathMatchingResourcePatternResolver
的一个实例,它实现了ResourcePatternResolver
接口。ApplicationContext
实例本身也是如此,它也实现了ResourcePatternResolver
接口,并委托给默认的PathMatchingResourcePatternResolver
。
17.6、 ResourceLoaderAware
接口
ResourceLoaderAware
接口是一个特殊的回调接口,它标识期望提供 ResourceLoader
引用的组件。下面的清单显示了 ResourceLoaderAware
接口的定义:
package org.springframework.context;
import org.springframework.beans.factory.Aware;
import org.springframework.core.io.ResourceLoader;
public interface ResourceLoaderAware extends Aware {
void setResourceLoader(ResourceLoader resourceLoader);
}
当类实现了 ResourceLoaderAware
并部署到应用程序上下文中(作为Spring管理的Bean)时,应用程序上下文中会将其识别为 ResourceLoaderAware
。然后,应用程序上下文调用 setResourceLoader(ResourceLoader)
,将自身作为参数提供(记住,Spring中的所有应用程序上下文都实现 ResourceLoader
接口)。
由于 ApplicationContext
是一个 ResourceLoader
,Bean还可以实现ApplicationContextAware
接口,并使用提供的应用程序上下文直接加载资源。但是,通常情况下,如果你只需要这个接口,那么最好使用专门的 ResourceLoader
接口。代码将只与资源加载接口(可以认为是一个实用程序接口)耦合,而不是与整个Spring ApplicationContext
接口耦合。
在应用程序组件中,你还可以依赖于 ResourceLoader
的自动装配,作为实现 ResourceLoaderAware
接口的替代方案。传统的constructor
和byType
自动装配模式(如自动装配中所述)能够分别为构造函数参数或setter方法参数提供 ResourceLoader
。为了获得更大的灵活性(包括自动装配字段和多个参数方法的能力),可以考虑使用基于注释的自动装配特性。在这种情况下,ResourceLoader
被自动连接到字段、构造函数参数或方法参数中,只要字段、构造函数或方法携带 @Autowired
注释,这些字段、构造函数或方法参数就需要 ResourceLoader
类型。有关更多信息,请参见 使用`@Autowired。
要为包含通配符或使用特殊
classpath*:
资源前缀的资源路径加载一个或多个Resource
对象,请考虑将 ResourcePatternResolver 实例自动连接到应用程序组件中,而不是ResourceLoader
。
17.7、资源依赖关系
如果Bean本身要通过某种动态流程来确定和提供资源路径,那么Bean使用 ResourceLoader
或 ResourcePatternResolver
接口来加载资源可能是有意义的。例如,考虑加载某种类型的模板,其中所需的特定资源取决于用户的角色。如果资源是静态的,那么完全消除 ResourceLoader
接口(或 ResourcePatternResolver
接口)的使用是有意义的,让Bean公开它需要的 Resource
属性,并期望它们被注入到Bean中。
然后注入这些属性之所以简单,是因为所有应用程序上下文都注册并使用一个特殊的JavaBeans PropertyEditor
,它可以将String
路径转换为Resource
对象。例如,下面的MyBean
类有一个Resource
类型的template
属性。
package example;
public class MyBean {
private Resource template;
public setTemplate(Resource template) {
this.template = template;
}
// ...
}
在XML配置文件中,template
属性可以配置为该资源的一个简单字符串,如下所示:
<bean id="myBean" class="example.MyBean">
<property name="template" value="some/resource/path/myTemplate.txt"/>
</bean>
请注意,资源路径没有前缀。因此,由于应用程序上下文本身将被用作 ResourceLoader
,资源是通过 ClassPathResource
、FileSystemResource
或 ServletContextResource
加载的,这取决于应用程序上下文的确切类型。
如果需要强制使用特定的 Resource
类型,可以使用前缀。下面两个例子展示了如何强制一个 ClassPathResource
和一个 UrlResource
(后者被用来访问文件系统中的一个文件):
<property name="template" value="classpath:some/resource/path/myTemplate.txt">
<property name="template" value="file:///some/resource/path/myTemplate.txt"/>
如果 MyBean
类被重构以用于注解驱动的配置,那么 myTemplate.txt
的路径可以存储在一个名为template.path
的键下。例如,在Spring Environment
可用的属性文件中(参见环境抽象)。然后可以使用属性占位符通过 @Value
注释引用模板路径(请参阅使用@Value)。Spring将以字符串的形式检索模板路径的值,而一个特殊的 PropertyEditor
将把字符串转换为一个要注入到 MyBean
构造函数中的 Resource
对象。下面的示例演示如何实现这一点。
@Component
public class MyBean {
private final Resource template;
public MyBean(@Value("${template.path}") Resource template) {
this.template = template;
}
// ...
}
如果我们想支持在同一路径下发现的多个模板,在类路径中的多个位置 — 例如,在类路径中的多个JAR中 — 我们可以使用特殊的 classpath*:
前缀和通配符将 templates.path
密钥定义为 classpath*:/config/templates/*.txt
。如果我们按如下方式重新定义 MyBean
类,Spring将把模板路径模式转换为一个 Resource
对象数组,可以将其注入 MyBean
构造函数。
@Component
public class MyBean {
private final Resource[] templates;
public MyBean(@Value("${templates.path}") Resource[] templates) {
this.templates = templates;
}
// ...
}
17.8、应用程序上下文和资源路径
本节介绍如何使用资源(包括使用 XML 的快捷方式、如何使用通配符以及其他详细信息)创建应用程序上下文。
17.8.1、构建应用程序上下文
应用程序上下文构造函数(对于特定的应用程序上下文类型)通常采用字符串或字符串数组作为资源的位置路径,例如构成上下文定义的 XML 文件。
当这样的位置路径没有前缀时,从该路径构建并用于加载Bean定义的特定 Resource
类型依赖于并适合于特定的应用程序上下文。例如,考虑下面的例子,它创建了一个 ClassPathXmlApplicationContext
:
ApplicationContext ctx =
new ClassPathXmlApplicationContext("conf/appContext.xml");
Bean定义是从类路径加载的,因为使用了 ClassPathResource
。但是,考虑下面的例子,它创建了一个 FileSystemXmlApplicationContext
:
ApplicationContext ctx =
new FileSystemXmlApplicationContext("conf/appContext.xml");
现在,Bean定义是从文件系统位置加载(在本例中,相对于当前工作目录)。
注意,在位置路径上使用特殊的classpath
前缀或标准的URL前缀会覆盖为加载Bean定义而创建的Resource
的默认类型。考虑下面的例子:
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
下面的例子展示了如何实例化一个ClassPathXmlApplicationContext
实例,它由定义在services.xml
和repository .xml
文件中的Bean组成(它们在类路径中):
ApplicationContext ctx = new ClassPathXmlApplicationContext(
new String[] {"services.xml", "repositories.xml"}, MessengerService.class);
有关各种构造函数的详细信息,请参阅 ClassPathXmlApplicationContext Java文档。
17.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风格模式时,解析器会遵循更复杂的过程来解析通配符。它为路径生成一个 Resource
,直到最后一个非通配符段,并从中获取URL。如果这个URL不是jar:
URL或特定于容器的变体(例如WebLogic中的zip:
, WebSphere中的 wsjar
,等等),则从它获得一个 java.io.File
,并通过遍历文件系统来解析通配符。对于jar URL,解析器要么从中获取 java.net.JarURLConnection
,要么手动解析jar URL,然后遍历jar文件的内容来解析通配符。
对可移植性的影响
如果指定的路径已经是一个 file
URL(隐式地,因为基本的 ResourceLoader
是一个文件系统URL,或者显式地),则可以保证通配符以完全可移植的方式工作。
如果指定的路径是一个classpath
位置,解析器必须通过调用 Classloader.getResource()
来获取最后一个非通配符路径段URL。因为这只是路径的一个节点(而不是最后的文件),所以实际上(在 ClassLoader
Java文档中)在这种情况下返回的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");
这个特殊的前缀指定必须获得与给定名称匹配的所有类路径资源(在内部,这实际上是通过调用ClassLoader.getResources(…)
进行的),然后合并形成最终的应用程序上下文定义。
通配符类路径依赖于底层
ClassLoader
的getResources()
方法。由于现在大多数应用服务器都提供自己的ClassLoader
实现,所以行为可能会有所不同,尤其是在处理jar文件时。检查classpath*
是否工作的一个简单测试是使用ClassLoader
从类路径上的jar中加载文件:getClass().getClassLoader().getResources("<someFileInsideTheJar>")
。使用具有相同名称但位于两个不同位置的文件进行此测试—例如,具有相同名称和相同路径的文件,但位于类路径中的不同jar文件中。如果返回不适当的结果,请检查应用服务器文档中可能影响ClassLoader
行为的设置。
你还可以将 classpath*:
前缀与位置路径的其余部分中的 PathMatcher
模式相结合(例如,classpath*:META-INF/*-beans.xml
)。在这种情况下,解析策略相当简单:在最后一个非通配符路径段上使用 ClassLoader.getResources()
调用,以获得类加载器层次结构中的所有匹配资源,然后在每个资源下,对通配符子路径使用前面描述的相同 PathMatcher
解析策略。
关于通配符的其他注意事项
请注意,classpath*:
,当与Ant风格模式结合使用时,在模式启动之前,只能可靠地使用至少一个根目录,除非实际的目标文件位于文件系统中。这意味着像classpath*:*.xml
这样的模式可能不会从jar文件的根中检索文件,而只能从扩展目录的根中检索文件。
Spring检索类路径条目的能力来源于JDK的 ClassLoader.getResources()
方法,该方法只返回空字符串的文件系统位置(表示要搜索的潜在根)。Spring还在jar文件中评估 URLClassLoader
运行时配置和 java.class.path
清单,但这并不保证会导致可移植的行为。
扫描类路径包需要在类路径中存在相应的目录条目。当你使用 Ant构建 JAR时,不要激活 JAR
files-only
开关。此外,在某些环境中,基于安全策略类路径目录可能不会公开——例如,JDK 1.7.0_45及更高版本上的独立应用程序(这需要在你的清单中设置 “Trusted-Library”。见 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
位置,则所需要的资源可能不存在于找到的第一个位置。因此,在这种情况下,你应该更喜欢使用具有相同Ant风格模式的 classpath*:
,该模式搜索包含 com.mycompany
基本包的所有类路径位置: classpath*:com/mycompany/**/service-context.xml
。
17.8.3、FileSystemResource
注意事项
没有附加到 FileSystemResource
的 FileSystemResource
(也就是说,当 FileSystemResource
不是实际的 FileSystemResource
时)将按照你所期望的方式处理绝对和相对路径。相对路径是相对于当前工作目录的路径,而绝对路径是相对于文件系统根目录的路径。
但是,由于向后兼容性(历史)的原因,当 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
或 FileSystemResource
的绝对路径,并通过使用 file:
URL前缀强制使用 UrlResource
。下面的例子展示了如何这样做:
// 实际的上下文类型并不重要,Resource 始终是 UrlResource
ctx.getResource("file:///some/resource/path/myTemplate.txt");
// 强制这个 FileSystemXmlApplicationContext 通过 UrlResource 加载它的定义
ApplicationContext ctx =
new FileSystemXmlApplicationContext("file:///conf/context.xml");