Appearance
Spring 核心:资源
Tip:基于 Spring Core 5.3.30 版本。
1. 简介
遗憾的是,Java 的标准 java.net.URL 类和各种 URL 前缀的标准处理器对于所有低级资源的访问并不完全足够。例如,没有标准化的 URL 实现可以用来访问需要从类路径或相对于 ServletContext 获取的资源。虽然可以为专门的 URL 前缀注册新的处理器(类似于现有的处理 http: 等前缀的处理器),但这通常相当复杂,而且 URL 接口仍然缺乏一些期望的功能,例如检查指向的资源是否存在的方法。
2. Resource 接口
Spring 的 Resource 接口位于 org.springframework.core.io. 包中,旨在为抽象访问低级资源提供更强大的接口。以下列表提供了 Resource 接口的概述。有关更多详细信息,请参阅 Resource 的 JavaDoc。
Java
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();
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
正如 Resource 接口的定义所示,它扩展了 InputStreamSource 接口。以下列表显示了 InputStreamSource 接口的定义:
Java
public interface InputStreamSource {
InputStream getInputStream() throws IOException;
}1
2
3
2
3
Resource 接口中一些最重要的方法包括:
getInputStream():定位并打开资源,返回一个用于从资源中读取的InputStream。预期每次调用都会返回一个新的InputStream。关闭流的责任在于调用者;exists():返回一个布尔值,表示此资源是否实际存在于物理形式中;isOpen():返回一个布尔值,表示此资源是否代表一个打开的流的句柄。如果为真,InputStream不能被多次读取,必须只读取一次然后关闭,以避免资源泄漏。对于所有常见的资源实现,都返回假,InputStreamResource是个例外;getDescription():返回此资源的描述,用于在使用资源时进行错误输出。这通常是完全限定的文件名或资源的实际 URL;
其他方法允许你获取代表资源的实际 URL 或 File 对象(如果底层实现兼容并支持该功能)。
Resource 接口的一些实现还实现了扩展的 WritableResource 接口,用于支持写入的资源。
Spring 本身广泛使用 Resource 抽象,作为许多方法签名中的参数类型,当需要资源时。Spring 的一些 API 中的其他方法(如各种 ApplicationContext 实现的构造函数)接受一个字符串,该字符串在未装饰或简单形式下用于创建适合该上下文实现的 Resource,或者通过在字符串路径上的特殊前缀,让调用者指定必须创建和使用特定的 Resource 实现。
虽然 Resource 接口在 Spring 中和由 Spring 使用得很多,但实际上它作为一个通用的实用类在你自己的代码中使用非常方便,用于访问资源,即使你的代码不知道或不关心 Spring 的其他部分。虽然这将你的代码与 Spring 耦合,但实际上只是将它与这个小型的实用类集合耦合,它可以作为 URL 的更强大的替代品,并可以被认为等同于你会用于此目的的任何其他库。
Note:
Resource抽象并不替代功能。在可能的情况下,它会对功能进行封装。例如,UrlResource封装了一个URL,并使用封装的URL来完成其工作。
3. 内置的 Resource 实现
Spring 包含了几个内置的 Resource 实现:
UrlResourceClassPathResourceFileSystemResourcePathResourceServletContextResourceInputStreamResourceByteArrayResource
要获取 Spring 中可用的 Resource 实现的完整列表,请参阅 Resource JavaDoc 中的 “所有已知实现类” 部分。
3.1. UrlResource
UrlResource 封装了一个 java.net.URL,可以用来访问通常可以通过 URL 访问的任何对象,如文件、HTTPS 目标、FTP 目标等。所有的 URL 都有一个标准化的字符串表示,因此使用适当的标准化前缀来区分一个 URL 类型和另一个 URL 类型。这包括:file: 用于访问文件系统路径、https: 用于通过 HTTPS 协议访问资源、ftp: 用于通过 FTP 访问资源,等等。
UrlResource 是通过 Java 代码显式使用 UrlResource 构造函数创建的,但通常在你调用一个接受表示路径的字符串参数的 API 方法时隐式创建。对于后一种情况,JavaBeans PropertyEditor 最终决定创建哪种类型的 Resource。如果路径字符串包含一个众所周知的(对属性编辑器来说)前缀(如 classpath:),它会创建一个适合该前缀的专门的 Resource。然而,如果它不识别前缀,它假定字符串是一个标准的 URL 字符串,并创建一个 UrlResource。
3.2. ClassPathResource
这个类代表了一个应该从类路径中获取的资源。它使用线程上下文类加载器、给定的类加载器或给定的类来加载资源。
如果类路径资源位于文件系统中,那么这个 Resource 实现支持作为 java.io.File 进行解析,但不支持那些位于 Jar 中且没有被(由 Servlet 引擎或任何环境)扩展到文件系统的类路径资源。为了解决这个问题,各种 Resource 实现总是支持作为 java.net.URL 进行解析。
ClassPathResource 是通过 Java 代码显式使用 ClassPathResource 构造函数创建的,但通常在你调用一个接受表示路径的字符串参数的 API 方法时隐式创建。对于后一种情况,JavaBeans PropertyEditor 识别到字符串路径上的特殊前缀 classpath:,并在这种情况下创建一个 ClassPathResource。
3.3. FileSystemResource
这是一个针对 java.io.File 句柄的 Resource 实现。它还支持 java.nio.file.Path 句柄,应用 Spring 的标准基于字符串的路径转换,但通过 java.nio.file.Files API 执行所有操作。对于纯 java.nio.path.Path 支持,请使用 PathResource。FileSystemResource 支持作为 File 和 URL 进行解析。
3.4. PathResource
这是一个针对 java.nio.file.Path 句柄的 Resource 实现,通过 Path API 执行所有操作和转换。它支持作为 File 和 URL 进行解析,并且还实现了扩展的 WritableResource 接口。PathResource 实际上是一个纯粹基于 java.nio.path.Path 的 FileSystemResource 的替代品,具有不同的 createRelative 行为。
3.5. ServletContextResource
这是一个用于 ServletContext 资源的 Resource 实现,它可以解释相对于相关 Web 应用程序根目录的相对路径。
它始终支持流访问和 URL 访问,但只有当 Web 应用程序存档被展开并且资源物理位于文件系统上时,才允许 java.io.File 访问。无论它是否被展开并位于文件系统上,还是直接从 JAR 或其他地方(如数据库)访问,实际上都取决于 Servlet 容器。
3.6. InputStreamResource
InputStreamResource 是针对给定 InputStream 的 Resource 实现。只有在没有适用的特定 Resource 实现时,才应使用它。特别是,如果可能的话,应优先考虑 ByteArrayResource 或任何基于文件的 Resource 实现。
与其他 Resource 实现相比,这是一个已经打开的资源的描述符。因此,它从 isOpen() 返回 true。如果你需要在某处保留资源描述符,或者如果你需要多次读取流,那么不应使用它。
3.7. ByteArrayResource
这是针对给定字节数组的 Resource 实现。它为给定的字节数组创建一个 ByteArrayInputStream。
它对于从任何给定的字节数组加载内容都很有用,无需求助于一次性使用的 InputStreamResource。
4. ResourceLoader 接口
ResourceLoader 接口是为了被能够返回(即,加载)Resource 实例的对象所实现的。以下列表显示了 ResourceLoader 接口的定义:
Java
public interface ResourceLoader {
Resource getResource(String location);
ClassLoader getClassLoader();
}1
2
3
4
2
3
4
所有的应用程序上下文都实现了 ResourceLoader 接口。因此,所有的应用程序上下文都可以用来获取 Resource 实例。
当你在特定的应用程序上下文上调用 getResource(),并且指定的位置路径没有特定的前缀时,你会得到一个适合该特定应用程序上下文的 Resource 类型。例如,假设以下代码片段是针对 ClassPathXmlApplicationContext 实例运行的:
Java
Resource template = ctx.getResource("some/resource/path/myTemplate.txt");针对 ClassPathXmlApplicationContext,该代码会返回一个 ClassPathResource。如果相同的方法在 FileSystemXmlApplicationContext 实例上运行,它将返回一个 FileSystemResource。对于 WebApplicationContext,它将返回一个 ServletContextResource。对于每个上下文,它也会以类似的方式返回适当的对象。
因此,你可以以适合特定应用程序上下文的方式加载资源。
另一方面,你也可以通过指定特殊的 classpath: 前缀来强制使用 ClassPathResource,无论应用程序上下文的类型是什么,如下例所示:
Java
Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");同样,你可以通过指定任何标准的 java.net.URL 前缀来强制使用 UrlResource。以下示例使用了 file 和 https 前缀:
Java
Resource template = ctx.getResource("file:///some/resource/path/myTemplate.txt");Java
Resource template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt");以下表格总结了将 String 对象转换为 Resource 对象的策略:
| 前缀 | 示例 | 解释 |
|---|---|---|
classpath: | classpath:com/myapp/config.xml | 从类路径加载 |
file: | file:///data/config.xml | 作为 URL 从文件系统加载。另请参见 FileSystemResource 的注意事项 |
https: | https://myserver/logo.png | 作为 URL 加载 |
| (none) | /data/config.xml | 取决于底层的 ApplicationContext |
5. ResourcePatternResolver 接口
ResourcePatternResolver 接口是 ResourceLoader 接口的扩展,它定义了一种策略,用于将位置(location)模式(例如,Ant 风格的路径模式)解析为 Resource 对象。
Java
public interface ResourcePatternResolver extends ResourceLoader {
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
Resource[] getResources(String locationPattern) throws IOException;
}1
2
3
4
5
2
3
4
5
如上所述,这个接口还定义了一个特殊的 classpath*: 资源前缀,用于从类路径中匹配所有资源。请注意,在这种情况下,资源位置预期是一个没有占位符的路径 —— 例如,classpath*:/config/beans.xml。类路径中的 JAR 文件或不同的目录可能包含多个具有相同路径和相同名称的文件。请参阅应用程序上下文构造函数资源路径中的通配符及其子节以获取有关 classpath*: 资源前缀的通配符支持的更多详细信息。
可以检查传入的 ResourceLoader(例如,通过 ResourceLoaderAware 语义提供的)是否也实现了这个扩展接口。
PathMatchingResourcePatternResolver 是一个独立的实现,可以在 ApplicationContext 外部使用,也被 ResourceArrayPropertyEditor 用于填充 Resource[] Bean 属性。PathMatchingResourcePatternResolver 能够将指定的资源位置路径解析为一个或多个匹配的 Resource 对象。源路径可能是一个简单的路径,它与目标 Resource 有一对一的映射,或者可能包含特殊的 classpath*: 前缀和/或内部的 Ant 风格的正则表达式(使用 Spring 的 org.springframework.util.AntPathMatcher 实用程序进行匹配)。后两者都有效地作为通配符。
Tip:在任何标准的
ApplicationContext中,默认的ResourceLoader实际上是PathMatchingResourcePatternResolver的一个实例,它实现了ResourcePatternResolver接口。对于ApplicationContext实例本身也是如此,它也实现了ResourcePatternResolver接口,并委托给默认的PathMatchingResourcePatternResolver。
6. ResourceLoaderAware 接口
ResourceLoaderAware 接口是一个特殊的回调接口,用于识别期望被提供 ResourceLoader 引用的组件。下面的列表显示了 ResourceLoaderAware 接口的定义:
Java
public interface ResourceLoaderAware {
void setResourceLoader(ResourceLoader resourceLoader);
}1
2
3
2
3
当一个类实现了 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。
Note:为了加载包含通配符或使用特殊
classpath*:资源前缀的资源路径的一个或多个Resource对象,可以考虑将ResourcePatternResolver的一个实例自动装配到你的应用程序组件中,而不是ResourceLoader。
7. 资源作为依赖项
如果 Bean 本身将通过某种动态过程确定并提供资源路径,那么 Bean 使用 ResourceLoader 或 ResourcePatternResolver 接口加载资源可能是有意义的。例如,考虑加载某种模板,其中所需的特定资源取决于用户的角色。如果资源是静态的,那么完全消除使用 ResourceLoader 接口(或 ResourcePatternResolver 接口)是有意义的,让 Bean 暴露它需要的 Resource 属性,并期望它们被注入。
然后注入这些属性的琐碎之处在于,所有应用程序上下文都注册并使用一个特殊的 JavaBeans PropertyEditor,它可以将字符串路径转换为 Resource 对象。例如,下面的 MyBean 类有一个类型为 Resource 的 template 属性。
Java
package example;
public class MyBean {
private Resource template;
public setTemplate(Resource template) {
this.template = template;
}
// ...
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
在 XML 配置文件中,template 属性可以使用一个简单的字符串来配置该资源,如下面的示例所示:
XML
<bean id="myBean" class="example.MyBean">
<property name="template" value="some/resource/path/myTemplate.txt"/>
</bean>1
2
3
2
3
请注意,资源路径没有前缀。因此,由于应用程序上下文本身将被用作 ResourceLoader,所以资源是通过 ClassPathResource、FileSystemResource 或 ServletContextResource 加载的,具体取决于应用程序上下文的确切类型。
如果你需要强制使用特定的 Resource 类型,你可以使用一个前缀。下面的两个示例展示了如何强制使用 ClassPathResource 和 UrlResource(后者用于访问文件系统中的文件):
XML
<property name="template" value="classpath:some/resource/path/myTemplate.txt">XML
<property name="template" value="file:///some/resource/path/myTemplate.txt"/>如果 MyBean 类被重构用于注解驱动的配置,myTemplate.txt 的路径可以存储在一个名为 template.path 的键下 —— 例如,在一个提供给 Spring Environment 的属性文件中(参见环境抽象)。然后,模板路径可以通过 @Value 注解使用属性占位符进行引用(参见使用 @Value)。Spring 将检索模板路径的值作为字符串,一个特殊的 PropertyEditor 将把字符串转换为一个 Resource 对象,以便注入到 MyBean 的构造函数中。下面的示例演示了如何实现这一点。
Java
@Component
public class MyBean {
private final Resource template;
public MyBean(@Value("${template.path}") Resource template) {
this.template = template;
}
// ...
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
如果我们想要支持在类路径的多个位置(例如,在类路径的多个 Jar 文件中)下的同一路径发现的多个模板,我们可以使用特殊的 classpath*: 前缀和通配符来定义一个 templates.path 键,如 classpath*:/config/templates/*.txt。如果我们按照以下方式重新定义 MyBean 类,Spring 将把模板路径模式转换为一个 Resource 对象数组,注入到 MyBean 的构造函数中。
Java
@Component
public class MyBean {
private final Resource[] templates;
public MyBean(@Value("${templates.path}") Resource[] templates) {
this.templates = templates;
}
// ...
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
8. 应用程序上下文和资源路径
这一部分介绍了如何使用资源创建应用程序上下文,包括适用于 XML 的快捷方式,如何使用通配符,以及其他详细信息。
8.1. 构建应用程序上下文
应用程序上下文构造函数(针对特定的应用程序上下文类型)通常接受一个字符串或字符串数组作为资源的位置路径,例如构成上下文定义的 XML 文件。
当这样的位置路径没有前缀时,从该路径构建出来的特定 Resource 类型以及用于加载 Bean 定义的方式,都会根据特定的应用程序上下文进行选择和适配。例如,考虑以下创建 ClassPathXmlApplicationContext 的示例:
Java
ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");Bean 定义是从类路径加载的,因为使用了 ClassPathResource。然而,考虑以下创建 FileSystemXmlApplicationContext 的示例:
Java
ApplicationContext ctx = new FileSystemXmlApplicationContext("conf/appContext.xml");现在,Bean 定义是从文件系统位置加载的(在这种情况下,相对于当前工作目录)。
请注意,位置路径上特殊的 classpath 前缀或标准的 URL 前缀会覆盖默认创建的用于加载 Bean 定义的 Resource 类型。考虑以下示例:
Java
ApplicationContext ctx = new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");使用 FileSystemXmlApplicationContext 从类路径加载 Bean 定义。然而,它仍然是一个 FileSystemXmlApplicationContext。如果它随后被用作 ResourceLoader,任何没有前缀的路径仍然会被视为文件系统路径。
8.1.1. 构造 ClassPathXmlApplicationContext 实例的快捷方式
ClassPathXmlApplicationContext 提供了许多构造函数,以便于方便地实例化。基本的思想是,你只需要提供一个字符串数组,该数组仅包含 XML 文件本身的文件名(不包括前导路径信息),并且还提供一个 Class。然后,ClassPathXmlApplicationContext 从提供的类中推导出路径信息。
考虑以下目录布局:
Text
com/
example/
services.xml
repositories.xml
MessengerService.class1
2
3
4
5
2
3
4
5
以下示例展示了如何实例化一个由在类路径上的名为 services.xml 和 repositories.xml 的文件中定义的 Bean 组成的 ClassPathXmlApplicationContext 实例:
Java
ApplicationContext ctx = new ClassPathXmlApplicationContext(new String[] {"services.xml", "repositories.xml"}, MessengerService.class);有关各种构造函数的详细信息,请参阅 ClassPathXmlApplicationContext 的 JavaDoc。
8.2. 应用程序上下文构造函数资源路径中的通配符
应用程序上下文构造函数中的资源路径可能是简单的路径(如前面所示),每个路径都与目标 Resource 一一对应,或者可能包含特殊的 classpath*: 前缀或内部的 Ant 风格模式(通过使用 Spring 的 PathMatcher 实用程序进行匹配)。后两者实际上都是通配符。
这种机制的一个用途是当你需要进行组件式应用程序组装时。所有组件都可以将上下文定义片段发布到一个众所周知的位置路径,当使用相同的路径(前缀为 classpath*:)创建最终的应用程序上下文时,所有的组件片段都会自动被捕获。
请注意,这种通配符是专门针对在应用程序上下文构造函数中使用资源路径的(或者当你直接使用 PathMatcher 实用程序类层次结构时),并在构造时解析。它与 Resource 类型本身无关。你不能使用 classpath*: 前缀来构造一个实际的 Resource,因为一个 Resource 一次只指向一个资源。
8.2.1. Ant 风格的模式
路径位置可以包含 Ant 风格的模式,如下例所示:
Text
/WEB-INF/*-context.xml
com/mycompany/**/applicationContext.xml
file:C:/some/path/*-context.xml
classpath:com/mycompany/**/applicationContext.xml1
2
3
4
2
3
4
当路径位置包含 Ant 风格的模式时,解析器会遵循更复杂的程序来尝试解析通配符。它为路径生成一个 Resource,直到最后一个非通配符段,并从中获取一个 URL。如果这个 URL 不是 jar: URL 或特定于容器的变体(如 WebLogic 中的 zip:,WebSphere 中的 wsjar 等),那么就从中获取一个 java.io.File,并通过遍历文件系统来解析通配符。在 Jar URL 的情况下,解析器要么从中获取一个 java.net.JarURLConnection,要么手动解析 Jar URL,然后遍历 Jar 文件的内容来解析通配符。
8.2.1.1. 对可移植性的影响
如果指定的路径已经是一个 file URL(无论是因为基础 ResourceLoader 是一个文件系统的,还是明确指定的),那么通配符保证以完全可移植的方式工作。
如果指定的路径是一个 classpath 位置,解析器必须通过调用 Classloader.getResource() 来获取最后一个非通配符路径段的 URL。由于这只是路径的一个节点(而不是最后的文件),在 ClassLoader 的 JavaDoc 中实际上并未明确指出在这种情况下返回的 URL 的确切类型。在实践中,它总是一个代表目录的 java.io.File(其中类路径资源解析为文件系统位置)或某种类型的 Jar URL(其中类路径资源解析为 Jar 位置)。然而,这个操作仍然存在可移植性问题。
如果为最后一个非通配符段获取了一个 Jar URL,解析器必须能够从中获取一个 java.net.JarURLConnection 或手动解析 Jar URL,以便能够遍历 Jar 的内容并解析通配符。这在大多数环境中都能工作,但在其他一些环境中会失败,我们强烈建议在依赖它之前,对来自 Jar 的资源的通配符解析在你的特定环境中进行彻底的测试。
8.2.2. classpath*: 前缀
在构造基于 XML 的应用程序上下文时,位置字符串可以使用特殊的 classpath*: 前缀,如下例所示:
Java
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath*:conf/appContext.xml");这个特殊的前缀指定必须获取所有与给定名称匹配的类路径资源(在内部,这基本上是通过调用 ClassLoader.getResources(…) 来实现的),然后合并以形成最终的应用程序上下文定义。
Tip:通配符类路径依赖于底层
ClassLoader的getResources()方法。由于现在大多数应用服务器都提供了自己的ClassLoader实现,所以行为可能会有所不同,特别是在处理 Jar 文件时。检查classpath*是否工作的一个简单测试是使用ClassLoader从类路径上的 Jar 中加载一个文件:getClass().getClassLoader().getResources("<someFileInsideTheJar>")。尝试使用在两个不同位置的具有相同名称的文件进行此测试 —— 例如,具有相同名称和相同路径但在类路径上的不同 Jar 中的文件。如果返回了不适当的结果,请检查应用服务器文档中可能影响ClassLoader行为的设置。
你也可以将 classpath*: 前缀与位置路径的其余部分中的 PathMatcher 模式结合使用(例如,classpath*:META-INF/*-beans.xml)。在这种情况下,解析策略相当简单:在最后一个非通配符路径段上使用 ClassLoader.getResources() 调用来获取类加载器层次结构中的所有匹配资源,然后,对每个资源,使用前面描述的相同的 PathMatcher 解析策略用于通配符子路径。
8.2.3. 与通配符相关的其他注意事项
请注意,当 classpath*: 与 Ant 风格的模式结合使用时,除非实际的目标文件位于文件系统中,否则只有在模式开始之前至少有一个根目录时,它才能可靠地工作。这意味着,像 classpath*:*.xml 这样的模式可能无法从 Jar 文件的根目录检索文件,而只能从展开目录的根目录检索文件。
Spring 检索类路径条目的能力源自 JDK 的 ClassLoader.getResources() 方法,该方法只返回空字符串的文件系统位置(表示可能要搜索的根)。Spring 也评估 URLClassLoader 运行时配置和 Jar 文件中的 java.class.path 清单,但这并不能保证导致可移植的行为。
Note
扫描类路径包需要类路径中存在相应的目录条目。当你使用 Ant 构建 JARs 时,不要激活 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 风格模式不能保证找到匹配的资源。考虑以下资源位置的示例:
Text
com/mycompany/package1/service-context.xml现在考虑一下某人可能用来尝试找到该文件的 Ant 风格路径:
Text
classpath:com/mycompany/**/service-context.xml这样的资源可能只存在于类路径的一个位置,但是当使用像前面的示例那样的路径尝试解析它时,解析器会使用 getResource("com/mycompany") 返回的(第一个)URL。如果这个基础包节点存在于多个 ClassLoader 位置,那么所需的资源可能不存在于找到的第一个位置。因此,在这种情况下,你应该更倾向于使用 classpath*: 与相同的 Ant 风格模式,它搜索包含 com.mycompany 基础包的所有类路径位置:classpath*:com/mycompany/**/service-context.xml。
8.3. FileSystemResource 的注意事项
一个没有附加到 FileSystemApplicationContext 的 FileSystemResource(也就是说,当 FileSystemApplicationContext 不是实际的 ResourceLoader 时)会按照你所期望的方式处理绝对路径和相对路径。相对路径是相对于当前工作目录的,而绝对路径是相对于文件系统的根的。
然而,出于向后兼容(历史)的原因,当 FileSystemApplicationContext 是 ResourceLoader 时,这会发生变化。FileSystemApplicationContext 强制所有附加的 FileSystemResource 实例将所有位置路径视为相对路径,无论它们是否以前导斜杠开始。在实践中,这意味着以下的示例是等价的:
Java
ApplicationContext ctx = new FileSystemXmlApplicationContext("conf/context.xml");Java
ApplicationContext ctx = new FileSystemXmlApplicationContext("/conf/context.xml");以下的示例也是等价的(尽管它们应该是不同的,因为一个情况是相对的,另一个情况是绝对的):
Java
FileSystemXmlApplicationContext ctx = ...;
ctx.getResource("some/resource/path/myTemplate.txt");1
2
2
Java
FileSystemXmlApplicationContext ctx = ...;
ctx.getResource("/some/resource/path/myTemplate.txt");1
2
2
实际上,如果你需要真正的绝对文件系统路径,你应该避免在 FileSystemResource 或 FileSystemXmlApplicationContext 中使用绝对路径,并通过使用 file: URL 前缀强制使用 UrlResource。以下示例展示了如何做到这一点:
Java
// actual context type doesn't matter, the Resource will always be UrlResource
ctx.getResource("file:///some/resource/path/myTemplate.txt");1
2
2
Java
// force this FileSystemXmlApplicationContext to load its definition via a UrlResource
ApplicationContext ctx =
new FileSystemXmlApplicationContext("file:///conf/context.xml");1
2
3
2
3