资源
本章涵盖了 Spring 如何处理资源以及您如何在 Spring 中使用资源。它包括以下主题:
引言
Java 的标准 java.net.URL
类以及各种 URL 前缀的标准处理程序,遗憾的是,对于所有低级资源的访问并不够充分。例如,没有标准化的 URL
实现可以用于访问需要从类路径获取或相对于 ServletContext
的资源。虽然可以为专用的 URL
前缀注册新的处理程序(类似于现有的 http:
等前缀的处理程序),但这通常相当复杂,并且 URL
接口仍然缺乏一些理想的功能,例如检查所指向资源是否存在的方法。
Resource
接口
Spring 的 Resource
接口位于 org.springframework.core.io.
包中,旨在提供一个更强大的接口,以抽象访问低级资源。以下列表提供了 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
,该 String
在未修饰或简单形式下用于创建适合该上下文实现的 Resource
,或者通过 String
路径上的特殊前缀,让调用者指定必须创建并使用特定的 Resource
实现。
虽然 Resource
接口在 Spring 中被广泛使用,并且被 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
这个类表示一个应该从类路径中获取的资源。它使用线程上下文类加载器、给定的类加载器或给定的类来加载资源。
这个 Resource
实现支持将类路径资源解析为 java.io.File
,前提是该资源位于文件系统中,但对于位于 jar 中且未被扩展(通过 servlet 引擎或其他环境)到文件系统的类路径资源则不支持。为了解决这个问题,各种 Resource
实现始终支持将其解析为 java.net.URL
。
ClassPathResource
是通过 Java 代码显式使用 ClassPathResource
构造函数创建的,但在调用一个接受 String
参数(该参数表示路径)的 API 方法时,通常会隐式创建。在后一种情况下,JavaBeans 的 PropertyEditor
识别字符串路径上的特殊前缀 classpath:
,并在这种情况下创建 ClassPathResource
。
FileSystemResource
这是一个 Resource
实现,用于处理 java.io.File
句柄。它还支持 java.nio.file.Path
句柄,应用 Spring 的标准基于字符串的路径转换,但通过 java.nio.file.Files
API 执行所有操作。对于纯 java.nio.path.Path
基于支持,请使用 PathResource
。FileSystemResource
支持作为 File
和 URL
的解析。
PathResource
这是一个 Resource
实现,针对 java.nio.file.Path
句柄,通过 Path
API 执行所有操作和转换。它支持作为 File
和 URL
的解析,并且还实现了扩展的 WritableResource
接口。PathResource
实际上是一个纯粹的 java.nio.path.Path
基础的替代方案,与 FileSystemResource
具有不同的 createRelative
行为。
ServletContextResource
这是一个 Resource
实现,用于 ServletContext
资源,解释相关 web 应用程序根目录内的相对路径。
它始终支持流访问和 URL 访问,但仅在 web 应用程序归档被解压并且资源物理存在于文件系统上时,才允许 java.io.File
访问。是否解压并存在于文件系统上,或者直接从 JAR 或其他地方(如数据库,这也是可以想象的)访问,实际上取决于 Servlet 容器。
InputStreamResource
InputStreamResource
是一个针对给定 InputStream
的 Resource
实现。只有在没有特定的 Resource
实现适用时,才应使用它。特别是,在可能的情况下,优先使用 ByteArrayResource
或任何基于文件的 Resource
实现。
与其他 Resource
实现相比,这是一个已打开资源的描述符。因此,它从 isOpen()
返回 true
。如果您需要将资源描述符保留在某处或需要多次读取流,请不要使用它。
ByteArrayResource
这是一个针对给定字节数组的 Resource
实现。它为给定的字节数组创建一个 ByteArrayInputStream
。
它对于从任何给定的字节数组加载内容非常有用,而无需诉诸于一次性使用的 InputStreamResource
。
ResourceLoader
接口
ResourceLoader
接口旨在由能够返回(即加载)Resource
实例的对象实现。以下列表显示了 ResourceLoader
接口的定义:
public interface ResourceLoader {
Resource getResource(String location);
ClassLoader getClassLoader();
}
所有应用程序上下文都实现了 ResourceLoader
接口。因此,所有应用程序上下文都可以用于获取 Resource
实例。
当你在特定的应用程序上下文上调用 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
。对于每个上下文,它将类似地返回适当的对象。
因此,您可以以适合特定应用上下文的方式加载资源。
另一方面,您也可以通过指定特殊的 classpath:
前缀来强制使用 ClassPathResource
,无论应用程序上下文类型如何,如以下示例所示:
- Java
- Kotlin
Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");
val template = ctx.getResource("classpath:some/resource/path/myTemplate.txt")
同样,您可以通过指定任何标准的 java.net.URL
前缀来强制使用 UrlResource
。以下示例使用 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
对象的策略:
表 1. 资源字符串
前缀 | 示例 | 说明 |
---|---|---|
classpath: | classpath:com/myapp/config.xml | 从类路径加载。 |
file: | file:///data/config.xml | 从文件系统作为 URL 加载。另见 FileSystemResource Caveats。 |
https: | https://myserver/logo.png | 作为 URL 加载。 |
(none) | /data/config.xml | 依赖于底层的 ApplicationContext 。 |
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 文件或类路径中的不同目录可以包含多个具有相同路径和相同名称的文件。有关使用 classpath*:
资源前缀的通配符支持的更多详细信息,请参见 Wildcards in Application Context Constructor Resource Paths 及其子部分。
传入的 ResourceLoader
(例如,通过 ResourceLoaderAware 语义提供的)可以检查它是否也实现了这个扩展接口。
PathMatchingResourcePatternResolver
是一个独立的实现,可以在 ApplicationContext
之外使用,并且也被 ResourceArrayPropertyEditor
用于填充 Resource[]
bean 属性。PathMatchingResourcePatternResolver
能够将指定的资源位置路径解析为一个或多个匹配的 Resource
对象。源路径可以是一个简单路径,与目标 Resource
一对一映射,或者可以包含特殊的 classpath*:
前缀和/或内部的 Ant 风格正则表达式(使用 Spring 的 org.springframework.util.AntPathMatcher
工具进行匹配)。后者实际上都是通配符。
任何标准 ApplicationContext
中的默认 ResourceLoader
实际上是 PathMatchingResourcePatternResolver
的一个实例,该实例实现了 ResourcePatternResolver
接口。 ApplicationContext
实例本身也是如此,它同样实现了 ResourcePatternResolver
接口,并委托给默认的 PathMatchingResourcePatternResolver
。
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。
要加载一个或多个 Resource
对象,针对包含通配符或使用特殊 classpath*:
资源前缀的资源路径,建议在您的应用程序组件中自动注入一个 ResourcePatternResolver 的实例,而不是 ResourceLoader
。
资源作为依赖项
如果 bean 本身将通过某种动态过程来确定和提供资源路径,那么让 bean 使用 ResourceLoader
或 ResourcePatternResolver
接口来加载资源是有意义的。例如,考虑加载某种模板的情况,其中所需的特定资源取决于用户的角色。如果资源是静态的,那么完全消除 ResourceLoader
接口(或 ResourcePatternResolver
接口)的使用是有意义的,让 bean 暴露它所需的 Resource
属性,并期望这些属性被注入。
使得注入这些属性变得简单的是,所有应用程序上下文都注册并使用一个特殊的 JavaBeans PropertyEditor
,它可以将 String
路径转换为 Resource
对象。例如,以下 MyBean
类具有一个类型为 Resource
的 template
属性。
- 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
使用的属性文件中(参见 Environment Abstraction)。然后,可以通过 @Value
注解使用属性占位符引用模板路径(参见 Using @Value)。Spring 将以字符串的形式检索模板路径的值,并且一个特殊的 PropertyEditor
将把字符串转换为一个 Resource
对象,以便注入到 MyBean
构造函数中。以下示例演示了如何实现这一点。
- 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)
如果我们想支持在类路径的多个位置下发现的多个模板 — 例如,在类路径中的多个 jar 文件中 — 我们可以使用特殊的 classpath*:
前缀和通配符来定义 templates.path
键为 classpath*:/config/templates/*.txt
。如果我们将 MyBean
类重新定义如下,Spring 将把模板路径模式转换为可以注入到 MyBean
构造函数中的 Resource
对象数组。
- 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 文件。
当这样的定位路径没有前缀时,从该路径构建的特定 Resource
类型以及用于加载 bean 定义的类型依赖于特定的应用上下文,并且适合该上下文。例如,考虑以下示例,它创建了一个 ClassPathXmlApplicationContext
:
- Java
- Kotlin
ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");
val ctx = ClassPathXmlApplicationContext("conf/appContext.xml")
bean 定义是从类路径加载的,因为使用了 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
从类路径加载 bean 定义。然而,它仍然是一个 FileSystemXmlApplicationContext
。如果随后将其用作 ResourceLoader
,任何未加前缀的路径仍然被视为文件系统路径。
构建 ClassPathXmlApplicationContext
实例 — 快捷方式
ClassPathXmlApplicationContext
提供了多个构造函数,以便于方便地实例化。基本思想是,您只需提供一个字符串数组,该数组仅包含 XML 文件本身的文件名(不包括前导路径信息),并且还需要提供一个 Class
。然后,ClassPathXmlApplicationContext
从提供的类中推导出路径信息。
考虑以下目录结构:
com/
example/
services.xml
repositories.xml
MessengerService.class
以下示例展示了如何实例化一个由名为 services.xml
和 repositories.xml
的文件中定义的 bean 组成的 ClassPathXmlApplicationContext
实例(这些文件位于类路径上):
- 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(…)
实现的),然后合并以形成最终的应用程序上下文定义。
通配符类路径依赖于底层 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 还评估 URLClassLoader
的运行时配置和 jar 文件中的 java.class.path
清单,但这并不保证会导致可移植的行为。
扫描类路径包需要在类路径中存在相应的目录条目。当你使用 Ant 构建 JAR 时,请不要激活 JAR 任务的 files-only
选项。此外,在某些环境中,类路径目录可能不会根据安全策略暴露 — 例如,在 JDK 1.7.0_45 及更高版本的独立应用程序中(这要求在你的清单中设置 'Trusted-Library'。请参见 stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)。
在模块路径(Java 模块系统)上,Spring 的类路径扫描通常按预期工作。在这里,将资源放入专用目录也是高度推荐的,以避免在搜索 JAR 文件根级别时出现上述可移植性问题。
Ant 风格的模式与 classpath:
资源不保证在根包可在多个类路径位置中找到匹配的资源。考虑以下资源位置的示例:
com/mycompany/package1/service-context.xml
现在考虑一个 Ant 风格的路径,某人可能会用来尝试找到那个文件:
classpath:com/mycompany/**/service-context.xml
这样的资源可能仅存在于类路径中的一个位置,但是当使用像前面的例子这样的路径来尝试解析它时,解析器会根据 getResource("com/mycompany");
返回的(第一个)URL 来工作。如果这个基础包节点在多个 ClassLoader
位置中存在,所需的资源可能在找到的第一个位置中不存在。因此,在这种情况下,您应该优先使用 classpath*:
和相同的 Ant 风格模式,这样可以搜索所有包含 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
的绝对路径,并通过使用 file:
URL 前缀强制使用 UrlResource
。以下示例展示了如何做到这一点:
- 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")