资源抽象(Resources)
本章介绍我们如何处理资源以及如何使用资源(Resource):它包括以下主题:
Introduction
在 Java 中 java.net.URL
用来描述资源,但是对于某些资源,例如:类路径下的资源的访问,需要注册一个处理器
来处理,就像处理 http:
资源一样,之所以可以处理是因为 jdk 内置了。这种机制太复杂了。然而这种机制还缺少一些必要的
功能,例如检查资源的存在与否。
Resource
接口
Resource
接口在 infra.core.io.
包中。它的出现抽象各种资源成为可能。
详细 API 可以查看 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 getName();
}
Resource
接口中一些重要的方法如下:
Some of the most important methods from the Resource
interface are:
-
getInputStream()
: 返回一个InputStream
从资源中读取。每次调用都会返回一个新的InputStream
。调用者负责关闭流。 -
exists()
: 指示该资源是否实际存在。 -
isOpen()
: 返回一个布尔值,指示该资源是否表示具有打开流的句柄。如果为true
,则InputStream
不能被多次读取, 必须只能被读取一次,然后关闭以避免资源泄漏。对于所有通常的资源实现,返回false
,但InputStreamResource
除外。 -
toString()
: 返回对这个资源的描述,一般用于错误展示,包含的信息一般是 URL 地址,文件名之类的
其他方法让你获取代表资源的实际 URL
或 File
对象(如果底层实现兼容并支持该功能)。
Resource
接口的一些实现还实现了扩展的
WritableResource
接口,用于支持向其写入内容。
框架本身广泛使用 Resource
接口,这些用例都可以作为最佳实践。你可以使用该接口在你的应用程序中用来访问资源尽管有耦合。
内置的 Resource
直线
框架内置了 Resource
的几个实现:
UrlResource
UrlResource
包装了 java.net.URL
他能访问 URL 能够访问的所有资源。比如 文件, HTTP 资源,FTP等。
所有 URL 都有标准化的字符串表示形式,以便适当的标准化前缀用于指示一种 URL 类型与另一种 URL 类型。 这包括
file:
用于访问文件系统路径,https:
用于通过以下方式访问资源 HTTPS 协议、ftp:
用于通过 FTP 等访问资源。
UrlResource
一般使用一个包含字符串的构造器构造。这个字符串一般是代表着一条路径。
var resource = new UrlResource("http://localhost:9090");
resource = UrlResource.from("http://localhost:9090");
ClassPathResource
这个实现代表着该资源是一个类路径下的资源。 它用线程上下文类加载器、给定的类加载器或给定的类加载资源。
它的实际底层可能是代表一个文件类型,或者是一个 URL 因为该资源可能存在 JAR 包中。在获取流 getInputStream()
的时候统一使用底层类加载器获取。
ClassPathResource
也是使用带有字符串的构造器构造,字符串标识类路径下的某个资源,一般不包含 classpath:
前缀。
该前缀是使用在 ResourceLoader#getResource
的接口。用来表示我要查找类路径下的资源。
FileSystemResource
该实现是代表着一个文件系统的 java.io.File
对象还支持 java.nio.file.Path
对象。对于纯 java.nio.file.Path
的
操作可以使用 PathResource
。
PathResource
该实现是包装了 java.nio.file.Path
。对资源所有的操作都将转到 java.nio.file.Path
API。
支持解析成 File
和 URL
。它也实现了 WritableResource
接口。
The ResourceLoader
Interface
ResourceLoader
接口用来获取查找 Resource
对象。
public interface ResourceLoader {
Resource getResource(String location);
ClassLoader getClassLoader();
}
所有的 ApplicationContext
都实现了该接口,所以他们都有获取(查找)资源的能力。
特定应用程序上下文上调用 getResource()
时,以及位置路径指定没有特定的前缀,您将返回一个 Resource
类型,即
适合特定的应用程序上下文。 例如,假设以下情况针对 ClassPathXmlApplicationContext
实例运行代码片段:
Resource template = loader.getResource("some/resource/path/myTemplate.txt");
针对 ClassPathXmlApplicationContext
,该代码返回 ClassPathResource
。 如果
对 FileSystemXmlApplicationContext
实例运行相同的方法,它会返回一个 FileSystemResource
。
对于 WebApplicationContext
,它将返回 MockContextResource
。 它同样会为每个上下文返回适当的对象。
因此,你可以使用特定的加载器去获取不同类型的资源。
您也可以强制使用 ClassPathResource
,无论加载器类型,通过指定特殊的 classpath:
前缀,如下所示
Resource template = loader.getResource("classpath:some/resource/path/myTemplate.txt");
用样的,一也可以使用其他带有 URL 性质的地址例如 file
和 https
。
Resource template = loader.getResource("file:///some/resource/path/myTemplate.txt");
Resource template = loader.getResource("https://myhost.com/resource/path/myTemplate.txt");
下面的表总结了从字符串到 Resource
的对应策略。
前缀 | 举例 | 解释 |
---|---|---|
classpath: |
|
从类路径加载。 |
file: |
|
从文件系统加载。 |
https: |
|
从 |
(none) |
|
依赖底层实现. |
The PatternResourceLoader
Interface
PatternResourceLoader
接口继承了(扩展了) ResourceLoader
接口。
他支持 ResourceLoader
的功能以外还支持解析符合通配符的一系列资源。
public interface PatternResourceLoader extends ResourceLoader {
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
void scan(String locationPattern, ResourceConsumer consumer) throws IOException;
Set<Resource> getResources(String locationPattern) throws IOException;
Resource[] getResourcesArray(String locationPattern) throws IOException;
}
@FunctionalInterface
public interface ResourceConsumer {
void accept(Resource t) throws IOException;
}
上面的定义中:scan
作为核心方法,其他的两个方法作为其变种。
上面可以看出,这个接口还定义了一个特殊的 classpath*:
资源前缀,表示从类路径中匹配资源。
classpath*:/config/beans.xml
表示将扫描类路径下所有 JAR 包中的 /config/beans.xml
资源。
classpath*:**/beans.xml
表示将扫描类路径下所有 JAR 包中的 beans.xml
资源。
传入的 ResourceLoader
(例如,通过提供的一个
ResourceLoaderAware
)可以检查是否
它也实现了这个扩展接口。
PathMatchingPatternResourceLoader
是一个可独立 ApplicationContext
之外使用,也用于 ResourceArrayPropertyEditor
填充 Resource[]
bean 属性。 PathMatchingPatternResourceLoader
能够将指定的资源位置路径解析为一个或多个匹配的 Resource
对象。
源路径可以是与目标具有一对一映射的简单路径 Resource
,或者可以包含特殊的 classpath*:
前缀和/或内部
Ant 风格的正则表达式(使用 Infra 进行匹配 infra.util.AntPathMatcher
实用程序)。后两者都有效通配符。
实现了 |
ResourceLoaderAware
接口
ResourceLoaderAware
接口是一个特殊的回调接口,它标识期望提供 ResourceLoader
引用的组件。
以下显示了 ResourceLoaderAware
接口的定义:
public interface ResourceLoaderAware {
void setResourceLoader(ResourceLoader resourceLoader);
}
当一个类实现了 ResourceLoaderAware 接口,并且被 `ApplicationContext
管理,ApplicationContext
将会
在合适的时机调用 setResourceLoader(ResourceLoader)
把自己作为参数传递给该方法。
由于 ApplicationContext
是一个 ResourceLoader
,因此该 bean 还可以实现 ApplicationContextAware
接口
并直接使用提供的应用程序上下文(ApplicationContext
)加载资源。不过,一般来说,最好使用专门的 ResourceLoader
接口。该代码仅与资源加载相关接口(可以被认为是实用程序接口)而不是整个 ApplicationContext
接口。
在应用程序组件中,您还可以依赖 ResourceLoader
的自动装配(实现 ResourceLoaderAware
接口的替代方案)。 传统
的 constructor
和 byType
自动装配模式(如 自动装配 中所述)
能够作为构造器参数或 setter
方法参数。为了获得更大的灵活性(包括能够自动装配字段和多参数方法),考虑使用基于注释的
自动装配功能。 在这种情况下 ResourceLoader
会自动注入到一个字段中。有关详细信息,请参阅
使用 @Autowired
。
为了加载含有通配符或者包含特殊的 classpath*: 资源前缀的一个或多个 Resource 对象的时候,请考虑注入
PatternResourceLoader 对象到你的应用中。
而不是使用 ResourceLoader 。
|
当资源作为依赖项时
如果 bean 本身依赖了某种资源,那么当然可以考虑使用 ResourceLoader
接口或者 PatternResourceLoader
接口去加载资源。
但也可以直接注入一个 Resource
对象。这个资源对象将是动态的(
对于静态的资源获取 使用 ResourceLoader
接口(或 PatternResourceLoader
接口)更好)。
动态的资源底层使用 JavaBeans PropertyEditor
,它可以转换 String
路径到 Resource
对象。
例如,以下 MyBean
类有一个 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>
请注意,资源路径没有前缀。因此 ApplicationContext
本身将被用作 ResourceLoader
,资源可能是
ClassPathResource
、FileSystemResource
或 MockContextResource
,具体取决于
应用程序上下文的确切类型。
如果需要强制使用特定的 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
的 - 例如,
在可供基础设施 Environment
使用的属性文件中 (详见 Environment 接口).
template.path
可以使用 @Value
注解,底层特殊的 PropertyEditor
将会转换字符串到 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
然后就可以注入到以下代码中。
@Component
public class MyBean {
private final Resource[] templates;
public MyBean(@Value("${templates.path}") Resource[] templates) {
this.templates = templates;
}
// ...
}
Application Contexts 和资源路径
本节介绍如何使用 resources 创建 application contexts 包括使用 XML、如何使用通配符以及其他方式。
构造 Application Contexts
一个 application context 构造器(针对特定应用程序上下文类型)通常是接受一个字符串或字符串数组作为资源的位置路径, 例如构成上下文定义的 XML 文件。
当这样的位置路径没有前缀时,得到的 Resource
用于加载 Bean 定义取决于具体的 application context 。
例如以下示例,创建了一个 ClassPathXmlApplicationContext
:
ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");
Bean 定义
从类路径下加载,因为 ClassPathResource
被使用。
然而思考一下下面的例子,使用 FileSystemXmlApplicationContext
:
ApplicationContext ctx =new FileSystemXmlApplicationContext("conf/appContext.xml");
上面的代码将从文件系统路径下加载(在这个例子中,从当前的相对路径开始加载)
值得注意的是如果使用特殊的 classpath
前缀或者是 标准的 URL 前缀,这将会覆盖之前的默认加载位置。
例如:
ApplicationContext ctx =
new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");
使用 FileSystemXmlApplicationContext
从类路径下去加载 Bean 定义
。然而它仍然是 FileSystemXmlApplicationContext
。
如果它被当做 ResourceLoader
接口来使用(使用 getResource(String location)
)那么任何没有前缀的路径仍然当做文件系统路径。
构造 ClassPathXmlApplicationContext
实例 — 快捷方式
ClassPathXmlApplicationContext
提供了几个好用的构造器,基本思想是你可以仅提供一个字符串数组仅包含 XML 文件本身的文件名
(没有前导路径信息)并提供一个 Class
。
一下是资源目录布局:
com/ example/ services.xml repositories.xml MessengerService.class
下面的 ClassPathXmlApplicationContext
实例,在类路径下定义了 services.xml
和 repositories.xml
ApplicationContext ctx = new ClassPathXmlApplicationContext(
new String[] { "services.xml", "repositories.xml" }, MessengerService.class);
其他的构造器使用详见:https://docs.today-tech.cn/today-infrastructure/docs/5.0.0-Draft.2-SNAPSHOT/javadoc-api/infra/context/support/ClassPathXmlApplicationContext.html[ClassPathXmlApplicationContext
]
Application Context 构造器资源路径中的通配符
Application Context 构造函数值中的资源路径可以是简单路径(如前所示),每个都有一个到目标 Resource
的一对一映射,
或者,可能包含特殊的 classpath*:
前缀或内部 Ant 样式模式(通过使用 PathMatcher
实用程序进行匹配)。
后者实际上都是通配符。
此机制的用途之一是当您需要进行组件式应用程序组装时。 所有组件可以将 Context 定义片段发布到众所周知的位置,并且,
当使用前缀相同的 classpath*:
路径创建 Application Context 时,所有组件片段都会自动读取。
需要注意的是,此通配符特定于 application context 中资源路径构造函数的使用(或者当您直接使用 PathMatcher
实用程序类层次结构时)
并且是构建时解析。 它与 Resource
类型本身无关。不能使用 classpath*:
前缀来构造实际的 Resource
,如
一个资源一次仅指向一个资源。
Ant 风格匹配
资源路径可以包含 Ant 样式匹配,如以下示例所示:
/WEB-INF/*-context.xml com/mycompany/**/beans.xml file:C:/some/path/*-context.xml classpath:com/mycompany/**/beans.xml
当路径包含 Ant 样式匹配时,解析器遵循更复杂的尝试解析通配符。直到最后一个非通配符段并从中获取 URL。如果此 URL 不是 jar:
URL 或
特定于容器的变体,从中获取一个 java.io.File
,并通过遍历来解析通配符文件系统。对于 jar URL,解析器要么得到一个
java.net.JarURLConnection
或者手动解析jar URL,然后遍历 jar 文件的内容来解析通配符。
对可移植性的影响
如果指定的路径已经是一个 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
的资源的通配符解析,然后再依赖它。
classpath*:
前缀
当构建基于 XML 的 application context 时,定位字符串可以使用特殊的 classpath*:
前缀,如下例所示:
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath*:conf/appContext.xml");
这个特殊前缀指定了必须获取所有与给定名称匹配的类路径资源(内部实际上是通过调用 ClassLoader.getResources(…)
实现的),
然后将它们合并以形成最终的 application context 定义。
通配符类路径依赖于底层 ClassLoader 的 getResources() 方法。由于大多数应用服务器现在提供自己的 ClassLoader 实现,
因此行为可能会有所不同,特别是在处理 JAR 文件时。一个简单的测试来检查 classpath*: 是否工作是使用 ClassLoader
从类路径中的 JAR 文件中加载文件:getClass().getClassLoader().getResources("<someFileInsideTheJar>") 。
尝试使用位于不同位置的具有相同名称的文件进行此测试,例如,在类路径上的不同 JAR 文件中具有相同名称和相同路径的文件。
如果返回了不适当的结果,请查看应用程序服务器文档以了解可能影响 ClassLoader 行为的设置。
|
另外,您还可以将 classpath*:
前缀与位置路径的其余部分结合使用 PathMatcher
模式(例如,classpath*:META-INF/*-beans.xml
)。
在这种情况下,解析策略相当简单:在最后一个非通配符路径段上使用 ClassLoader.getResources()
调用来获取类加载器层次结构中所有匹配的资源,
然后对每个资源使用前面描述的相同 PathMatcher
解析策略来处理通配符子路径。
其他与通配符相关的注意事项
请注意,当与 Ant 样式模式结合使用时,classpath*:
只能可靠地与至少一个根目录配合使用,而不是匹配开始之前,除非实际的目标文件位于文件系统中。
这意味着诸如 classpath*:*.xml
这样的匹配可能无法从 JAR 文件的根目录中检索文件,而只能从已展开的目录的根目录中检索。
框架检索类路径条目的能力源自 JDK 的 ClassLoader.getResources()
方法,该方法仅对空字符串(表示要搜索的潜在根目录)返回文件系统位置。
框架还会计算 URLClassLoader
的运行时配置和 JAR 文件中的 java.class.path
清单,但这并不能保证可移植性。
在扫描类路径包时,需要类路径中存在相应的目录条目。当您使用 Ant 构建 JAR 文件时,请不要激活 JAR 任务的 `files-only`开关。 此外,基于某些环境中的安全策略,类路径目录可能不会被暴露出来,例如 JDK 1.7.0_45 及更高版本的独立应用程序(这需要在清单中设置 'Trusted-Library')。 在 JDK 9 的模块路径(Jigsaw)上,基础设施类路径扫描通常按预期工作。在这里,将资源放入专用目录也是非常推荐的, 这样可以避免搜索 JAR 文件根目录时出现的可移植性问题。 |
Ant 样式匹配与 classpath:
资源结合使用时,并不能保证在根包在多个类路径位置中都存在时能够找到匹配的资源。考虑以下资源位置的示例:
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
注意事项
一个未附加到 FileSystemApplicationContext
的 FileSystemResource
(也就是说,当 FileSystemApplicationContext
不是实际的 ResourceLoader
时),
会按照您所期望的方式处理绝对和相对路径。相对路径是相对于当前工作目录,而绝对路径是相对于文件系统的根目录。
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
或 FileSystemXmlApplicationContext
中使用绝对路径,并通过使用 file:
URL 前缀强制使用 UrlResource
。以下示例展示了如何这样做:
// actual context type doesn't matter, the Resource will always be UrlResource
ctx.getResource("file:///some/resource/path/myTemplate.txt");
// force this FileSystemXmlApplicationContext to load its definition via a UrlResource
ApplicationContext ctx =
new FileSystemXmlApplicationContext("file:///conf/context.xml");