|
对于最新稳定版本,请使用 Spring Framework 7.0.6! |
资源
本章介绍 Spring 如何处理资源,以及如何在 Spring 中使用资源。内容包括以下主题:
< Introduction >
Java 标准的 java.net.URL 类以及针对各种 URL 前缀的标准处理器,不幸的是,并不足以满足所有对底层资源的访问需求。例如,目前没有标准化的 URL 实现可用于访问需要从类路径(classpath)中获取的资源,或相对于 ServletContext 的资源。尽管可以为特定的 URL 前缀注册新的处理器(类似于已有的 http: 等前缀的处理器),但这一过程通常相当复杂,而且 URL 接口仍然缺少一些理想的功能,例如用于检查所指向资源是否存在的方法。
这Resource接口
Spring 的位于 org.springframework.core.io. 包中的 Resource 接口旨在成为一个更强大的接口,用于抽象对底层资源的访问。以下列表提供了 Resource 接口的概述。有关更多详细信息,请参阅 Resource 的 Javadoc。
public interface Resource extends InputStreamSource {
boolean exists();
boolean isReadable();
boolean isOpen();
boolean isFile();
URL getURL() throws IOException;
URI getURI() throws IOException;
File getFile() throws IOException;
ReadableByteChannel readableChannel() throws IOException;
long contentLength() throws IOException;
long lastModified() throws IOException;
Resource createRelative(String relativePath) throws IOException;
String getFilename();
String getDescription();
}
如 Resource 接口的定义所示,它扩展了 InputStreamSource 接口。以下代码清单展示了 InputStreamSource 接口的定义:
public interface InputStreamSource {
InputStream getInputStream() throws IOException;
}
Resource 接口中一些最重要的方法包括:
-
getInputStream():定位并打开资源,返回一个用于从该资源读取数据的InputStream。每次调用都应返回一个新的InputStream。调用方负责关闭该流。 -
exists():返回一个boolean值,指示该资源在物理形式上是否实际存在。 -
isOpen():返回一个boolean值,指示此资源是否表示一个带有已打开流的句柄。如果返回true,则该InputStream不能被多次读取,必须仅读取一次,然后关闭以避免资源泄漏。对于所有常规的资源实现,此方法均返回false,只有InputStreamResource例外。 -
getDescription():返回此资源的描述信息,用于在处理该资源时输出错误信息。这通常是资源的完整限定文件名或实际 URL。
其他方法允许您获取表示该资源的实际URL或File对象(前提是底层实现兼容并支持该功能)。
Resource 接口的某些实现还实现了扩展的 WritableResource 接口,以支持对资源进行写入操作。
Spring 框架本身在许多需要资源的方法签名中广泛使用 Resource 抽象作为参数类型。此外,某些 Spring API 中的其他方法(例如各种 ApplicationContext 实现类的构造函数)接受一个 String 类型的参数,该字符串在未加修饰或简单形式下会被用于创建适合该上下文实现的 Resource;或者,通过在 String 路径上添加特殊前缀,允许调用者指定必须创建并使用某个特定的 Resource 实现。
尽管 Resource 接口在 Spring 框架内部及其使用者中被广泛使用,但即使你的代码完全不了解或不关心 Spring 的其他部分,单独将它作为通用工具类来访问资源也是非常方便的。
虽然这样做会使你的代码与 Spring 耦合,但实际上仅耦合到这一小组工具类,它们可以作为 URL 的更强大替代品,并且可被视为与其他用于此目的的工具库等效。
Resource 抽象并没有替代原有功能,而是在可能的情况下对其进行封装。例如,UrlResource 封装了一个 URL,并使用被封装的 URL 来完成其工作。 |
内置的Resource实现
Spring 包含多个内置的 Resource 实现:
如需查看 Spring 中可用的完整 Resource 实现列表,请参阅
Resource Javadoc 中的“所有已知实现类”部分。
UrlResource
UrlResource 包装了一个 java.net.URL,可用于访问任何通常可通过 URL 访问的对象,例如文件、HTTPS 目标、FTP 目标等。所有 URL 都具有标准化的 String 表示形式,通过使用相应的标准化前缀来区分不同类型的 URL。这包括用于访问文件系统路径的 file:、通过 HTTPS 协议访问资源的 https:、通过 FTP 访问资源的 ftp: 等。
UrlResource 可以通过 Java 代码显式地使用 UrlResource 构造函数来创建,但通常是在调用一个接受 String 参数(该参数用于表示路径)的 API 方法时隐式创建的。在后一种情况下,JavaBeans 的 PropertyEditor 最终决定要创建哪种类型的 Resource。如果路径字符串包含一个已知的(对属性编辑器而言)前缀(例如 classpath:),它就会为该前缀创建一个相应的专用 Resource。然而,如果它无法识别该前缀,则会假定该字符串是一个标准的 URL 字符串,并创建一个 UrlResource。
ClassPathResource
此类表示应从类路径(classpath)中获取的资源。它使用线程上下文类加载器、指定的类加载器或指定的类来加载资源。
此 Resource 实现支持将类路径资源解析为 java.io.File,但前提是该类路径资源位于文件系统中;对于位于 JAR 文件中且尚未被(Servlet 引擎或任何运行环境)解压到文件系统的类路径资源,则不支持此解析方式。为了解决这一问题,各种 Resource 实现始终支持将其解析为 java.net.URL。
ClassPathResource 可通过 Java 代码显式地使用 ClassPathResource 构造函数来创建,但通常在调用接受一个用于表示路径的 String 参数的 API 方法时被隐式创建。在后一种情况下,JavaBeans 的 PropertyEditor 会识别字符串路径中的特殊前缀 classpath:,并据此创建一个 ClassPathResource。
FileSystemResource
这是用于处理 java.io.File 的 Resource 实现。它还支持java.nio.file.Path 处理,应用 Spring 标准的基于字符串的路径转换,但所有操作均通过 java.nio.file.Files API 执行。若需纯基于java.nio.path.Path 的支持,请改用 PathResource。FileSystemResource支持解析为 File 和 URL。
PathResource
这是针对 java.nio.file.Path 句柄的 Resource 实现,通过 Path API 执行所有操作和转换。它支持解析为 File 和 URL,并实现了扩展的 WritableResource 接口。PathResource 实际上是纯基于 java.nio.path.Path 的替代方案,用于取代 FileSystemResource,且具有不同的 createRelative 行为。
ServletContextResource
这是针对 Resource 资源的 ServletContext 实现,它在相关 Web 应用程序的根目录内解析相对路径。
它始终支持流访问和 URL 访问,但仅当 Web 应用程序归档文件被解压且资源实际存在于文件系统上时,才允许 java.io.File 访问。该归档是否被解压并位于文件系统上,还是直接从 JAR 包中访问,抑或从数据库等其他位置(这是可以设想的)访问,实际上取决于 Servlet 容器。
这ResourceLoader接口
ResourceLoader 接口应由能够返回(即加载)Resource 实例的对象实现。以下代码清单展示了 ResourceLoader 接口的定义:
public interface ResourceLoader {
Resource getResource(String location);
ClassLoader getClassLoader();
}
所有应用上下文都实现了 ResourceLoader 接口。因此,所有应用上下文都可用于获取 Resource 实例。
当你在特定的应用上下文(application context)上调用 getResource() 方法,并且指定的位置路径没有特定的前缀时,你会得到一个适合该特定应用上下文的 Resource 类型。例如,假设有以下代码片段在一个 ClassPathXmlApplicationContext 实例上执行:
-
Java
-
Kotlin
Resource template = ctx.getResource("some/resource/path/myTemplate.txt");
val template = ctx.getResource("some/resource/path/myTemplate.txt")
对于 ClassPathXmlApplicationContext,上述代码会返回一个 ClassPathResource。如果对 FileSystemXmlApplicationContext 实例运行相同的方法,则会返回一个 FileSystemResource。对于 WebApplicationContext,则会返回一个 ServletContextResource。同样地,它也会为每种上下文返回相应的对象。
因此,你可以以适合特定应用程序上下文的方式加载资源。
另一方面,您也可以通过指定特殊的 ClassPathResource 前缀来强制使用 classpath:,而不管应用上下文的类型如何,如下例所示:
-
Java
-
Kotlin
Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");
val template = ctx.getResource("classpath:some/resource/path/myTemplate.txt")
同样,你可以通过指定任意标准的 UrlResource 前缀来强制使用 java.net.URL。以下示例使用了 file 和 https 前缀:
-
Java
-
Kotlin
Resource template = ctx.getResource("file:///some/resource/path/myTemplate.txt");
val template = ctx.getResource("file:///some/resource/path/myTemplate.txt")
-
Java
-
Kotlin
Resource template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt");
val template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt")
下表总结了将 String 对象转换为 Resource 对象的策略:
| 前缀 | 例举 | 说明 |
|---|---|---|
classpath: |
|
从类路径加载。 |
文件: |
|
已从文件系统加载为 |
https: |
|
作为 |
(none) |
|
取决于底层的 |
这ResourcePatternResolver接口
ResourcePatternResolver 接口是对 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 文件或不同目录可能包含多个具有相同路径和相同名称的文件。有关 #resources-app-ctx-wildcards-in-resource-paths 资源前缀对通配符支持的更多详细信息,请参阅应用程序上下文构造函数资源路径中的通配符及其子章节。
传入的 ResourceLoader(例如,通过 ResourceLoaderAware 语义提供的)也可以检查其是否实现了此扩展接口。
PathMatchingResourcePatternResolver 是一个独立的实现,可在 ApplicationContext 之外使用,同时也被 ResourceArrayPropertyEditor 用于填充 Resource[] Bean 属性。PathMatchingResourcePatternResolver 能够将指定的资源位置路径解析为一个或多个匹配的 Resource 对象。源路径可以是一个简单路径,与目标 Resource 存在一对一映射;或者也可以包含特殊的 classpath*: 前缀和/或内部 Ant 风格的正则表达式(使用 Spring 的 org.springframework.util.AntPathMatcher 工具进行匹配)。后两种情况实际上都是通配符。
|
在任何标准的 |
这ResourceLoaderAware接口
ResourceLoaderAware 接口是一个特殊的回调接口,用于标识那些期望被注入 ResourceLoader 引用的组件。以下代码清单展示了 ResourceLoaderAware 接口的定义:
public interface ResourceLoaderAware {
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。为了获得更大的灵活性(包括自动装配字段和多参数方法的能力),请考虑使用基于注解的自动装配功能。在这种情况下,只要相关的字段、构造函数或方法带有 @Autowired 注解,ResourceLoader 就会被自动装配到期望 ResourceLoader 类型的字段、构造函数参数或方法参数中。更多信息,请参阅 使用 @Autowired。
要为包含通配符或使用特殊 classpath*: 资源前缀的资源路径加载一个或多个 Resource 对象,建议在应用程序组件中自动注入 ResourcePatternResolver 的实例,而不是使用 ResourceLoader。 |
作为依赖项的资源
如果 Bean 本身将通过某种动态过程来确定并提供资源路径,那么该 Bean 使用 ResourceLoader 或
ResourcePatternResolver 接口来加载资源可能是合理的。例如,考虑加载某种模板的情况,其中所需的特定资源取决于
用户的角色。如果资源是静态的,那么完全消除对 ResourceLoader 接口(或 ResourcePatternResolver 接口)的使用就更为合理,
此时可以让 Bean 暴露其所需的 Resource 属性,并期望这些属性被注入到其中。
之所以能够轻松地注入这些属性,是因为所有的应用上下文都会注册并使用一个特殊的 JavaBeans PropertyEditor,它可以将 String 类型的路径转换为 Resource 对象。例如,下面的 MyBean 类具有一个类型为 template 的 Resource 属性。
-
Java
-
Kotlin
public class MyBean {
private Resource template;
public setTemplate(Resource template) {
this.template = template;
}
// ...
}
class MyBean(var template: Resource)
在 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 对象。以下示例演示了如何实现这一点。
-
Java
-
Kotlin
@Component
public class MyBean {
private final Resource template;
public MyBean(@Value("${template.path}") Resource template) {
this.template = template;
}
// ...
}
@Component
class MyBean(@Value("\${template.path}") private val template: Resource)
如果我们希望支持在类路径(classpath)中的多个位置(例如,类路径中的多个 JAR 文件)下同一路径中发现的多个模板,可以使用特殊的 classpath*: 前缀结合通配符,将 templates.path 键定义为 classpath*:/config/templates/*.txt。如果我们将 MyBean 类重新定义如下,Spring 会将该模板路径模式转换为一个 Resource 对象数组,并将其注入到 MyBean 的构造函数中。
-
Java
-
Kotlin
@Component
public class MyBean {
private final Resource[] templates;
public MyBean(@Value("${templates.path}") Resource[] templates) {
this.templates = templates;
}
// ...
}
@Component
class MyBean(@Value("\${templates.path}") private val templates: Resource[])
应用程序上下文和资源路径
本节介绍如何使用资源创建应用上下文,包括适用于 XML 的快捷方式、如何使用通配符以及其他相关细节。
构建应用上下文
应用程序上下文的构造函数(针对特定的应用程序上下文类型)通常接受一个字符串或字符串数组作为资源的位置路径,例如组成上下文定义的 XML 文件。
当此类位置路径没有前缀时,从该路径构建并用于加载 Bean 定义的特定 Resource 类型取决于具体的应用上下文,并与其相适应。例如,请考虑以下创建 ClassPathXmlApplicationContext 的示例:
-
Java
-
Kotlin
ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");
val ctx = ClassPathXmlApplicationContext("conf/appContext.xml")
Bean 定义是从类路径(classpath)加载的,因为使用了 ClassPathResource。然而,请考虑以下示例,它创建了一个 FileSystemXmlApplicationContext:
-
Java
-
Kotlin
ApplicationContext ctx =
new FileSystemXmlApplicationContext("conf/appContext.xml");
val ctx = FileSystemXmlApplicationContext("conf/appContext.xml")
现在,Bean 定义是从文件系统位置加载的(在本例中,是相对于当前工作目录)。
请注意,在位置路径上使用特殊的 classpath 前缀或标准 URL 前缀会覆盖用于加载 bean 定义的 Resource 的默认类型。请参见以下示例:
-
Java
-
Kotlin
ApplicationContext ctx =
new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");
val ctx = FileSystemXmlApplicationContext("classpath:conf/appContext.xml")
使用 FileSystemXmlApplicationContext 会从类路径(classpath)加载 bean 定义。
然而,它仍然是一个 FileSystemXmlApplicationContext。如果随后将其用作
ResourceLoader,任何未加前缀的路径仍会被视为文件系统路径。
构建中ClassPathXmlApplicationContext实例 — 快捷方式
ClassPathXmlApplicationContext 提供了多个构造函数,以便于便捷地进行实例化。其基本思路是:您可以仅提供一个字符串数组,其中只包含 XML 文件本身的文件名(不含前导路径信息),同时提供一个 Class 对象。ClassPathXmlApplicationContext 随后会从所提供的类中推导出路径信息。
考虑以下目录结构:
com/
example/
services.xml
repositories.xml
MessengerService.class
以下示例展示了如何实例化一个 ClassPathXmlApplicationContext 实例,该实例由类路径下名为 services.xml 和 repositories.xml 的文件中定义的 bean 组成:
-
Java
-
Kotlin
ApplicationContext ctx = new ClassPathXmlApplicationContext(
new String[] {"services.xml", "repositories.xml"}, MessengerService.class);
val ctx = ClassPathXmlApplicationContext(arrayOf("services.xml", "repositories.xml"), MessengerService::class.java)
请参阅 ClassPathXmlApplicationContext
javadoc,了解各种构造函数的详细信息。
应用上下文构造函数资源路径中的通配符
应用程序上下文构造函数参数中的资源路径可以是简单路径(如前所示),每条路径都与一个目标 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 是基于文件系统的,还是显式地指定),通配符功能将保证以完全可移植的方式正常工作。
如果指定的路径是一个 classpath 位置,解析器必须通过调用 Classloader.getResource() 来获取最后一个非通配符路径段的 URL。由于这仅仅是路径中的一个节点(而非末尾的文件),根据 ClassLoader 的 Javadoc,这种情况下返回的 URL 类型实际上是未定义的。在实践中,它通常是一个表示目录的 java.io.File(当 classpath 资源解析为文件系统位置时),或者某种形式的 jar URL(当 classpath 资源解析为 jar 文件位置时)。尽管如此,此操作仍存在可移植性方面的顾虑。
如果为最后一个非通配符段获取了一个 jar URL,解析器必须能够从中获得一个 java.net.JarURLConnection,或者手动解析该 jar URL,以便遍历 jar 文件的内容并解析通配符。这在大多数环境中可以正常工作,但在其他一些环境中会失败。我们强烈建议您在依赖此功能之前,在您的特定环境中对来自 jar 文件的资源通配符解析进行充分测试。
这classpath*:前缀
在构建基于 XML 的应用程序上下文时,位置字符串可以使用特殊的 classpath*: 前缀,如下例所示:
-
Java
-
Kotlin
ApplicationContext ctx =
new ClassPathXmlApplicationContext("classpath*:conf/appContext.xml");
val ctx = ClassPathXmlApplicationContext("classpath*:conf/appContext.xml")
该特殊前缀指定必须获取所有与给定名称匹配的类路径资源(在内部,这本质上是通过调用ClassLoader.getResources(…)实现的),然后将它们合并以形成最终的应用上下文定义。
通配符 classpath 依赖于底层 getResources() 的 ClassLoader 方法。由于如今大多数应用服务器都提供了自己的 ClassLoader 实现,其行为可能会有所不同,尤其是在处理 JAR 文件时。一个简单的测试方法是使用 classpath* 从 classpath 中的 JAR 文件内加载某个文件,以验证 ClassLoader 是否有效:getClass().getClassLoader().getResources("<someFileInsideTheJar>")。请尝试用两个不同位置但名称相同的文件进行此测试——例如,在 classpath 上的不同 JAR 文件中具有相同名称和相同路径的文件。如果返回了不正确的结果,请查阅应用服务器的文档,查看是否有影响 ClassLoader 行为的相关设置。 |
你也可以将 classpath*: 前缀与路径其余部分中的 PathMatcher 模式结合起来使用(例如,classpath*:META-INF/*-beans.xml)。在这种情况下,解析策略相当简单:对路径中最后一个非通配符段调用 ClassLoader.getResources() 方法,以获取类加载器层次结构中所有匹配的资源,然后针对每个资源,使用前面所述的相同 PathMatcher 解析策略来处理通配符子路径。
与通配符相关的其他说明
请注意,classpath*: 与 Ant 风格的模式结合使用时,除非目标文件实际位于文件系统中,否则只有在模式开始前至少包含一个根目录时才能可靠地工作。这意味着像 classpath*:*.xml 这样的模式可能无法从 JAR 文件的根目录中检索文件,而只能从已解压目录的根目录中检索。
Spring 框架检索类路径条目的能力源自 JDK 的
ClassLoader.getResources() 方法,该方法在传入空字符串(表示要搜索的潜在根目录)时仅返回文件系统位置。Spring 还会评估
URLClassLoader 的运行时配置以及 JAR 文件中的 java.class.path 清单,但这种方式无法保证产生可移植的行为。
|
扫描类路径(classpath)中的包需要类路径中存在相应的目录条目。当您使用 Ant 构建 JAR 文件时,请不要启用 JAR 任务的 在 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。
FileSystemResource注意事项
一个未附加到 FileSystemResource 的 FileSystemApplicationContext(即当 FileSystemApplicationContext 不是实际的 ResourceLoader 时),对绝对路径和相对路径的处理方式符合您的预期:相对路径相对于当前工作目录,而绝对路径则相对于文件系统的根目录。
然而,出于向后兼容性(历史原因)的考虑,当 FileSystemApplicationContext 作为 ResourceLoader 时,情况会发生变化。FileSystemApplicationContext 会强制所有关联的 FileSystemResource 实例将所有位置路径都视为相对路径,无论它们是否以斜杠开头。
实际上,这意味着以下示例是等效的:
-
Java
-
Kotlin
ApplicationContext ctx =
new FileSystemXmlApplicationContext("conf/context.xml");
val ctx = FileSystemXmlApplicationContext("conf/context.xml")
-
Java
-
Kotlin
ApplicationContext ctx =
new FileSystemXmlApplicationContext("/conf/context.xml");
val ctx = FileSystemXmlApplicationContext("/conf/context.xml")
以下示例也是等效的(尽管它们看起来应该有所不同,因为一种情况是相对路径,另一种是绝对路径):
-
Java
-
Kotlin
FileSystemXmlApplicationContext ctx = ...;
ctx.getResource("some/resource/path/myTemplate.txt");
val ctx: FileSystemXmlApplicationContext = ...
ctx.getResource("some/resource/path/myTemplate.txt")
-
Java
-
Kotlin
FileSystemXmlApplicationContext ctx = ...;
ctx.getResource("/some/resource/path/myTemplate.txt");
val ctx: FileSystemXmlApplicationContext = ...
ctx.getResource("/some/resource/path/myTemplate.txt")
在实践中,如果你需要真正的绝对文件系统路径,应避免使用 FileSystemResource 或 FileSystemXmlApplicationContext 的绝对路径,而应通过使用 UrlResource URL 前缀来强制使用 file:。以下示例展示了如何实现这一点:
-
Java
-
Kotlin
// actual context type doesn't matter, the Resource will always be UrlResource
ctx.getResource("file:///some/resource/path/myTemplate.txt");
// actual context type doesn't matter, the Resource will always be UrlResource
ctx.getResource("file:///some/resource/path/myTemplate.txt")
-
Java
-
Kotlin
// force this FileSystemXmlApplicationContext to load its definition via a UrlResource
ApplicationContext ctx =
new FileSystemXmlApplicationContext("file:///conf/context.xml");
// force this FileSystemXmlApplicationContext to load its definition via a UrlResource
val ctx = FileSystemXmlApplicationContext("file:///conf/context.xml")