20220507 Core - 2. Resources
引言
Java 的标准 Java.net.URL
类和各种 URL 前缀的标准处理程序对于所有对低级资源的访问都不够充分。例如,没有标准化的 URL 实现可用于访问需要从类路径或 ServletContext
获取的资源。虽然可以为专门的 URL 前缀注册新的处理程序(类似于 http:
等前缀的现有处理程序),但这通常相当复杂,并且 URL 接口仍然缺少一些需要的功能,例如检查指向的资源是否存在的方法。
Resource
接口
位于 org.springframework.core.io
包中的 Resource
接口旨在成为一个更强大的接口,用于抽象对低级资源的访问。
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();
}
public interface InputStreamSource {
InputStream getInputStream() throws IOException;
}
Resource
接口中最重要的方法是:
getInputStream()
:定位并打开资源,返回一个InputStream
用于读取资源。预计每次调用都会返回一个新的InputStream
。关闭流是调用者的责任。exists()
:返回一个boolean
指示该资源是否以物理形式实际存在。isOpen()
:返回一个boolean
指示此资源是否表示具有打开流的句柄。如果true
,则不能多次读取InputStream
,必须只读取一次,然后关闭以避免资源泄漏。所有常用Resource
实现返回false
,除了InputStreamResource
getDescription()
:返回此资源的描述,用于在使用资源时输出错误。这通常是完全限定的文件名或资源的实际 URL。
其他方法可让您获取表示资源的实际 URL
或 File
对象(如果底层实现兼容并支持该功能)。
Resource
接口的一些实现还为支持写入的资源实现了 WritableResource
接口
Spring 本身广泛使用 Resource
抽象,当需要 Resource
时,它在许多方法签名中作为参数类型。某些 Spring API 中的方法(如各种 ApplicationContext
实现的构造函数)采用一个字符串,该字符串以未经修饰或简单的形式用于创建适合该上下文实现的资源,或者通过字符串路径上的特殊前缀,让调用方指定必须创建和使用特定的资源实现。
虽然 Resource
接口在 Spring 中被广泛使用,但实际上在您自己的代码中单独用作通用实用程序类用于访问资源非常方便,。
Resource
抽象并没有改变功能。它只是一层包装。例如,UrlResource
包装一个 URL 并使用被包装的 URL
来完成它的工作。
内置 Resource
实现
UrlResource
UrlResource
包装 java.net.URL
并且可用于访问通常可通过 URL 访问的任何对象,例如文件、HTTPS 目标、FTP 目标等。所有 URL 都具有标准化的 String
表示形式,以便使用适当的标准化前缀来指示一种 URL 类型和另一种 URL 类型。这包括
- 访问文件系统路径
file:
- 通过 HTTPS 协议访问资源
https:
- 通过 FTP 访问资源
ftp:
- 其他
UrlResource
由 Java 代码通过显式使用 UrlResource
构造函数创建,但通常在调用 API 方法时隐式创建,该方法采用 String
表示路径的参数。对于隐式创建的情况,JavaBeans PropertyEditor
最终决定创建哪种 Resource
类型。如果路径字符串包含一个众所周知的前缀( 例如classpath:
),它会为该前缀创建一个合适的专用 Resource
。但是,如果它不识别前缀,则假定该字符串是标准 URL 字符串并创建一个UrlResource
ClassPathResource
此类表示应从类路径中获取的资源。它使用线程上下文类加载器、给定的类加载器或给定的类来加载资源。
如果类路径资源驻留在文件系统中,而不是驻留在 jar 中且未扩展到文件系统的类路径资源(通过 servlet 引擎或任何环境),则此资源实现支持解析为 java.io.File
。为了解决这个问题,各种资源实现始终支持解析为 java.net.URL
。
ClassPathResource
由 Java 代码通过显式使用 ClassPathResource
构造函数创建,但通常在调用 API 方法时隐式创建,该方法采用表示路径的 String
参数。对于隐式创建的情况,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
替代方案
ServletContextResource
这是用于解释相关 web 应用程序根目录中的相对路径的 ServletContext
资源的 Resource
实现。
支持流访问和 URL 访问,但仅当 web 应用程序存档文件被展开且资源实际位于文件系统上时,才允许 java.io.File
访问 。它是否被展开并放在文件系统上,或者直接从 JAR 或其他地方(比如数据库)访问,实际上取决于 Servlet 容器。
InputStreamResource
InputStreamResource
是给定 InputStream
的 Resource
实现。只有在没有特定的资源实现可用时才应使用它。相对来说,最好使用 ByteArrayResource
或任何基于文件的 Resource
实现。
与其他 Resource
实现不同,这是已打开资源的描述符。因此,它从 isOpen()
返回 true
。如果需要将资源描述符保留在某个位置,或者需要多次读取流,请不要使用它。
ByteArrayResource
这是给定字节数组的 Resource
实现。它为给定字节数组创建 ByteArrayInputStream
。
它有助于从任何给定字节数组加载内容,而不必求助于一次性的 InputStreamResource
。
ResourceLoader
接口
ResourceLoader
接口旨在由可以返回(即加载)Resource
实例的对象实现。
public interface ResourceLoader {
Resource getResource(String location);
ClassLoader getClassLoader();
}
ApplicationContext
接口继承了 ResourceLoader
接口,因此,可以使用所有应用上下文( ApplicationContext
)来获取 Resource
实例。
在特定应用上下文上调用 getResource()
时,如果指定的位置路径没有特定前缀,则会返回适合该特定应用上下文的 Resource
类型。例如,假设以下代码片段是针对 ClassPathXmlApplicationContext
实例运行的,该代码返回 ClassPathResource
。如果对 FileSystemXmlApplicationContext
实例运行相同的方法,它将返回 FileSystemResource
。对于 WebApplicationContext
,它将返回 ServletContextResource
。
Resource template = ctx.getResource("some/resource/path/myTemplate.txt");
可以通过指定特殊的 classpath:
前缀来强制使用 ClassPathResource
,而不管应用上下文类型如何
Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");
可以通过指定任何标准 java.net.URL
前缀来强制使用 UrlResource
Resource template = ctx.getResource("file:///some/resource/path/myTemplate.txt");
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 |
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 文件或类路径中的不同目录可以包含具有相同路径和相同名称的多个文件。
PathMatchingResourcePatternResolver
是一个独立的实现,可在 ApplicationContext
外部使用,也被 ResourceArrayPropertyEditor
用于填充 Resource[]
bean 属性。PathMatchingResourcePatternResolver
能够将指定的资源位置路径解析为一个或多个匹配的 Resource
对象。源路径可以是一个简单的路径,它具有到目标 Resource
的一对一映射,或者可以包含特殊的 classpath*:
前缀或内部 Ant 样式的正则表达式(使用 Spring 的 org.springframework.util.AntPathMatcher
匹配 )。
任何标准 ApplicationContext
中的默认 ResourceLoader
实际上都是实现 ResourcePatternResolver
接口的 PathMatchingResourcePatternResolver
实例。ApplicationContext
实例本身也是如此,它也实现了 ResourcePatternResolver
接口并委托给默认的 PathMatchingResourcePatternResolver
ResourceLoaderAware
接口
当一个类实现 ResourceLoaderAware
接口并部署到应用上下文中时,它被应用上下文识别,然后应用上下文调用 setResourceLoader(ResourceLoader)
,将自身作为参数提供(注意,Spring 中的所有应用上下文都实现了 ResourceLoader
接口)。
由于 ApplicationContext
是 ResourceLoader
,bean 还可以实现 ApplicationContextAware
接口并直接使用应用上下文来加载资源。但是,如果您只需要 ResourceLoader
接口,则最好使用专用接口。代码将只耦合到资源加载接口而不是整个 Spring ApplicationContext
接口。
在应用程序组件中,您还可以依靠自动装配 ResourceLoader
作为实现 ResourceLoaderAware
接口的替代方法。传统的 constructor
和 byType
自动装配模式都能够注入 ResourceLoader
。为了获得更大的灵活性,请考虑使用基于注解的自动装配功能。只要字段、构造函数或方法带有 @Autowired
注解,ResourceLoader
就会自动装配到需要 ResourceLoader
类型的字段、构造函数参数或方法参数中 。
要为包含通配符或使用特殊 classpath*:
资源前缀的资源路径加载一个或多个 Resource
对象,请考虑注入ResourcePatternResolver
实例而不是 ResourceLoader
Resources 作为依赖项
如果 bean 本身将通过某种动态过程确定资源路径,那么 bean 使用 ResourceLoader
或 ResourcePatternResolver
接口加载资源可能是有意义的。例如,考虑加载某种类型模板,其中所需的特定资源取决于用户的角色。如果资源是静态的,可以完全消除 ResourceLoader
接口(或 ResourcePatternResolver
接口) ,让 bean 公开它需要的 Resource
属性,并将它们被注入其中。
所有应用上下文都注册并使用特殊的 JavaBeans PropertyEditor
,它可以将 String
路径转换为 Resource
对象。
package example;
public class MyBean {
private Resource template;
public setTemplate(Resource template) {
this.template = template;
}
// ...
}
<bean id="myBean" class="example.MyBean">
<property name="template" value="some/resource/path/myTemplate.txt"/>
</bean>
注意,资源路径没有前缀。因此,由于应用上下文本身将用作 ResourceLoader
,资源通过 ClassPathResource
、FileSystemResource
或 ServletContextResource
加载 ,具体取决于应用上下文的确切类型。如果需要强制使用特定 Resource
类型,可以使用前缀。
<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 会将路径模式转换为可以注入 MyBean
构造函数的 Resource
对象数组。
@Component
public class MyBean {
private final Resource[] templates;
public MyBean(@Value("${templates.path}") Resource[] templates) {
this.templates = templates;
}
// ...
}
应用上下文和资源路径
构建应用上下文
应用上下文构造函数通常将字符串或字符串数组作为资源的位置路径,例如构成上下文定义的 XML 文件。
当这样的位置路径没有前缀时,从该路径构建并用于加载 bean 定义的特定 Resource
类型取决于特定的应用上下文。例如,考虑以下示例,它创建了一个 ClassPathXmlApplicationContext
:
ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");
bean 定义是从类路径加载的,因为使用了 ClassPathResource
。
以下示例创建了一个FileSystemXmlApplicationContext
:
ApplicationContext ctx = new FileSystemXmlApplicationContext("conf/appContext.xml");
现在 bean 定义从文件系统位置加载(相对于当前工作目录)。
请注意,在位置路径上使用特殊 classpath
前缀或标准 URL 前缀会覆盖默认 Resource
类型以加载 bean 定义。考虑以下示例:
ApplicationContext ctx = new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");
使用 FileSystemXmlApplicationContext
从类路径加载 bean 定义。然而,它仍然是 FileSystemXmlApplicationContext
。如果它随后用作 ResourceLoader
,则任何未带前缀的路径仍将被视为文件系统路径。
FileSystemXmlApplicationContext ctx = new FileSystemXmlApplicationContext();
System.out.println(ctx.getResource("classpath:ioc/my2.xml").getClass()); // ClassPathResource
System.out.println(ctx.getResource("ioc/my2.xml").getClass()); // FileSystemResource
构建 ClassPathXmlApplicationContext
实例——快捷方式
ClassPathXmlApplicationContext
公开了许多构造函数以方便实例化。基本思想是,您可以只提供一个字符串数组,该数组只包含 XML 文件本身的文件名(不包含前导路径信息),还可以提供一个类。然后,ClassPathXmlApplicationContext
从提供的类派生路径信息。
考虑以下目录布局:
com/
example/
services.xml
repositories.xml
MessengerService.class
以下示例显示了如何实例化由名为 services.xml
和 repositories.xml
(位于类路径中)的文件中定义的 bean 组成的 ClassPathXmlApplicationContext
实例:
ApplicationContext ctx = new ClassPathXmlApplicationContext(
new String[] {"services.xml", "repositories.xml"}, MessengerService.class);
应用上下文构造函数参数里资源路径中的通配符
应用上下文构造函数值中的资源路径可能是简单路径,每个路径都具有到 Resource
目标的一对一映射,或者可能包含特殊 classpath*:
前缀或内部 Ant 样式模式(使用 Spring 的 PathMatcher
匹配)
这种机制的一种用途是当您需要进行组件样式的应用程序组装时。所有组件都可以将上下文定义片段发布到一个众所周知的位置路径,并且,当使用以 classpath*:
为前缀的相同路径创建最终应用上下文时,所有组件片段都会被自动拾取。
注意,通配符特定于应用上下文构造函数中资源路径的使用(或直接使用 PathMatcher
),并在构造时解析。它与 Resource
类型本身无关。不能使用 classpath*:
前缀来构造一个 Resource
,因为一个 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:
或特定于容器的变体(例如 WebLogic 中的 zip:
、WebSphere 中的 wsjar
),则从中获取 java.io.File
并通过遍历文件系统来解析通配符。对于 jar URL,解析器要么从中获取 java.net.JarURLConnection
,要么手动解析 jar URL,然后遍历 jar 文件的内容以解析通配符。
对便携性的影响
如果指定的路径已经是一个 file
URL(隐式地,因为基础 ResourceLoader
是一个文件系统 URL ,或者显式),通配符保证以完全可移植的方式工作。
如果指定的路径是 classpath
位置,解析器必须通过调用 Classloader.getResource()
获取最后一个非通配符路径段 URL 。因为这只是路径的一个节点(而不是最后的文件),所以它实际上是没有定义在这种情况下返回的 URL 的具体类型。实际上,它始终是一个 java.io.File
,表示目录(类路径资源解析为文件系统位置)或某种类型的 jar URL(类路径资源解析为 jar 位置)的文件。尽管如此,这个操作仍然存在可移植性问题。
如果为最后一个非通配符段获取了 jar URL,则解析器必须能够从中获取 java.net.JarURLConnection
或手动解析 jar URL,才能遍历 jar 的内容并解析通配符。这在大多数环境中都有效,但在其他环境中却失败了,我们强烈建议在依赖 JAR 之前,在您的特定环境中对来自 JAR 的资源的通配符解析进行彻底测试。
classpath*:
前缀
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath*:conf/appContext.xml");
classpath*:
前缀指定必须获取与给定名称匹配的所有类路径资源(通过调用 ClassLoader.getResources(…)
),然后合并以形成最终的应用上下文定义。
通配符类路径依赖于底层 ClassLoader
的 getResources()
方法。由于现在大多数应用服务器都提供自己的类加载器实现,因此行为可能会有所不同,特别是在处理 jar 文件时。检查 classpath*
是否有效的一个简单测试是使用 ClassLoader
从类路径上的 jar 中加载文件:getClass().getClassLoader().getResources("<someFileInsideTheJar>")
。使用具有相同名称但位于两个不同位置的文件进行测试,例如,具有相同名称和相同路径但位于类路径上不同 JAR 中的文件。如果返回了不适当的结果,请检查应用程序服务器文档中可能影响 ClassLoader
行为的设置。
您还可以将 classpath*:
前缀与位置路径的其余部分中的 PathMatcher
模式组合在一起(例如 classpath*:META-INF/*-beans.xml
)。在这种情况下,解析策略相当简单:在最后一个非通配符路径段上使用 ClassLoader.getResources()
调用来获取类加载器层次结构中的所有匹配资源,然后在每个资源之外,使用前面描述的相同 PathMatcher
解析策略通配符子路径。
参考源码
org.springframework.util.PathMatcher
org.springframework.util.AntPathMatcher
与通配符相关的其他说明
注意 classpath*:
,当与 Ant 风格的模式结合使用时,除非实际目标文件驻留在文件系统中,否则在模式启动之前,它只能在至少一个根目录下可靠地工作。这意味着诸如 classpath*:*.xml
此类的模式,可能不会从 jar 文件的根目录检索文件,而只能从扩展目录的根目录检索文件。
Spring 检索类路径条目的能力源自 JDK 的 ClassLoader.getResources()
方法,该方法仅返回空字符串的文件系统位置(指示要搜索的潜在根)。Spring 也会评估 URLClassLoader
运行时配置和 jar 文件中的 java.class.path
清单,但这并不能保证可移植
如果要搜索的根包在多个类路径位置可用,则带有 classpath:
资源的 Ant 风格模式不能保证找到匹配的资源。考虑以下资源位置示例:
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
注意事项
未附加到 FileSystemApplicationContext
的 FileSystemResource
(即,当 FileSystemApplicationContext
不是实际的资源加载程序时)会按预期处理绝对路径和相对路径。相对路径是相对于当前工作目录的,而绝对路径是相对于文件系统的根目录的。
但是,出于向后兼容性(历史)原因,当 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");
ctx.getResource("/some/resource/path/myTemplate.txt");
在实践中,如果您需要真正的绝对文件系统路径,您应该避免使用带有 FileSystemResource
或 FileSystemXmlApplicationContext
的绝对路径,并通过使用 file:
URL 前缀强制使用 UrlResource
。以下示例显示了如何执行此操作:
ctx.getResource("file:///some/resource/path/myTemplate.txt");
ApplicationContext ctx = new FileSystemXmlApplicationContext("file:///conf/context.xml");