1.介绍
java标准的 java.net.URL
类和用于各种前缀标准处理程序并不满足访问底层资源
,例如, 没有标准化的URL实现可用于访问需要从类路径获得的或与servlet上下文的资源
。虽然可以为专用URL前缀注册新的处理程序(类似于http:),这通常是相当的复杂,ulr的接口缺失一些功能
,比如检查所指向资源是否存在的方法
2. Resource的接口
spring的Resource 接口是用来抽象访问底层的资源更加有能力的接口
public interface Resource extends InputStreamSource {
boolean exists();
boolean isOpen();
URL getURL() throws IOException;
File getFile() 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(): 返回值为一个布尔值, 该布尔值表示该资源是否是一个打开的流的操作
,如果为true,则刘不能被多次读取,并且只能读取一次,然后关闭资源以避免资源泄露,除了InputStreamResource,其他关于resource 的实现都会返回false
getDescription():返回一个关于该resource的描述
,用于在使用资源时输出错误,这通常为文件的全限定名或者资源的实际的URL
其他的方法允许你获取实际的URL或者标识资源的文件对象(如果底层实现是兼容,并支持这个功能)
在 spring 里,Resource 抽象有着相当广泛的使用,比如,当需要一个资源时,Resource 可以作为方法签名里的一个参数类型。在 spring api 中,有些方法(如各种 ApplicationContext 实现的构造函数)会直接采用普通格式的String 路径来创建合适的 Resource,调用者也可以通过在路径里带上指定的前缀来创建特定 Resource 实现。
Resource 接口(实现)不仅可以被 spring 大量的应用,其也非常适合作为你编程中访问资源的辅助工具类
。当你仅需要使用到 Resource 接口实现时,可以直接忽略 spring 的其余部分。单独使用 Rsourece 实现,会造成代码与 spring 的部分耦合,可也仅耦合了其中一小部分辅助类,而且你可以将 Reource 实现作为 URL 的一种访问底层更为有效的替代,与你引入其他库来达到这种目的是一样的
。
需要注意的是 Resource 实现并没有去重新发明轮子,而是尽可能地采用封装。举个例子,UrlResource 里就封装了一个 URL 对象
,在其内的逻辑就是通过封装的 URL 对象来完成的
。
3. 内置的resources实现
在spring中,提供了一些开箱即用的Resource实现
-
UrlResource
UrlResource
是java.net.URL
的封装,被用来访问url可以访问的任意对象,比如file文件,http targe 和 FTP target等,所有的urls有一个标准的字符串表示,比如使用标准化的前缀,可以得到一个url类型,当中就包括用于访问文件系统路劲的file,用户访问HTTP协议的http,通过 ftp 协议访问资源的ftp等等
-
ClassPathResource
可以使用 ClassPathResource 来获取类路径上的资源
。ClassPathResource 可以使用线程上下文的加载器、调用者提供的加载器或指定的类中的任意一个来加载资源
。
ClassPathResource 可以从类路径上加载资源,其可以使用线程上下文加载器、指定加载器或指定的 class 类型中的任意一个来加载资源
。
当类路径上资源存于文件系统中,ClassPathResource 支持以 java.io.File 的形式访问,可当类路径上的资源存于尚未解压(没有 被Servlet 引擎或其他可解压的环境解压)的 jar 包中,ClassPathResource 就不再支持以 java.io.File 的形式访问。鉴于上面所说这个问题,spring 中各式 Resource 实现都支持以 jave.net.URL 的形式访问。
-
FileSystemResource
这是针对 java.io.File 提供的 Resource 实现
。显然,我们可以使用 FileSystemResource 的 getFile() 函数获取 File 对象,使用 getURL() 获取 URL 对象。
-
ServletContextResource
这是为了获取web 根路径的 ServletContext 资源而提供的 Resource 实现
。
ServletContextResource 完全支持以流和 URL 的方式访问
,可只有当 web 项目是已解压的(不是以 war 等压缩包形式存在)且该 ServletContext 资源存于文件系统里,ServletContextResource 才支持以 java.io.File 的方式访问。至于说到,我们的 web 项目是否已解压和相关的 ServletContext 资源是否会存于文件系统里,这个取决于我们所使用的 Servlet 容器。若 Servlet 容器没有解压 web 项目,我们可以直接以 JAR 的形式的访问
,或者其他可以想到的方式(如访问数据库)等。
-
InputStreamResource
这是针对 InputStream 提供的 Resource 实现。建议,在确实没有找到其他合适的 Resource 实现时,才使用 InputSteamResource。如果可以,尽量选择 ByteArrayResource 或其他基于文件的 Resource 实现来代替
。
与其他 Resource 实现已比较,InputStreamRsource 倒像一个已打开资源的描述符,因此,调用 isOpen() 方法会返回 true
。除了在需要获取资源的描述符或需要从输入流多次读取时,都不要使用 InputStreamResource 来读取资源。
-
ByteArrayResource
这是针对字节数组提供的 Resource 实现。可以通过一个字节数组来创建 ByteArrayResource
。
当需要从字节数组加载内容时,ByteArrayResource 是一个不错的选择,使用 ByteArrayResource 可以不用求助于 InputStreamResource。
4.ResourceLoader 接口
ResourceLoader 接口是用来加载 Resource 对象的,换句话说,就是当一个对象需要获取 Resource 实例时,可以选择实现 ResourceLoader 接口。
public interface ResourceLoader {
Resource getResource(String location);
}
spring 里所有的应用上下文都是实现了 ResourceLoader 接口,因此,所有应用上下文都可以通过 getResource() 方法获取 Resource 实例。
当你在指定应用上下文调用 getResource() 方法时,而指定的位置路径又没有包含特定的前缀,spring 会根据当前应用上下文来决定返回哪一种类型 Resource。举个例子,假设下面的代码片段是通过 ClassPathXmlApplicationContext 实例来调用的
,
Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");
那 spring 会返回一个 ClassPathResource 对象;类似的,如果是通过实例 FileSystemXmlApplicationContext 实例调用的
,返回的是一个 FileSystemResource 对象
;如果是通过 WebApplicationContext 实例的,返回的是一个 ServletContextResource 对象
…… 如上所说,你就可以在指定的应用上下中使用 Resource 实例来加载当前应用上下文的资源。
还有另外一种场景里,如在其他应用上下文里,你可能会强制需要获取一个 ClassPathResource 对象,这个时候,你可以通过加上指定的前缀来实现这一需求,如:
Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");
类似的,你可以通过其他任意的 url 前缀来强制获取 UrlResource 对象:
Resource template = ctx.getResource("file:///some/resource/path/myTemplate.txt");
Resource template = ctx.getResource("http://myhost.com/resource/path/myTemplate.txt")
给出一个表格来总结一下 spring 根据各种位置路径加载资源的策略:
refix | Example | Explanation |
---|---|---|
classpath: | classpath:com/myapp/config.xml |
Loaded from the classpath. |
file: | [file:///data/config.xml](file:///data/config.xml) |
Loaded as a URL , from the filesystem. |
http: | [http://myserver/logo.png](http://myserver/logo.png) |
Loaded as a URL . |
(none) | /data/config.xml |
Depends on the underlying ApplicationContext . |
不管是
ClassPathXmlApplicationContext
,还是FileSystemXmlApplicationContext
,还是XmlWebApplicationContext
,从继承关系可以看出,这些类最终继承了DefaultResourceLoader的这个类
,这些类调用getResource方法的方法,实际上调用的是DefaultResourceLoader.getResource的方法
,getResource