Spring5中文文档【12】核心之Resources资源

1. 前言

本系列基于最新5.3.10版本,大部分内容copy于官方文档…
官方文档地址

资源是一个经济学名词,比如一国或一定地区内拥有的物力、财力、人力等各种物质的总称。

在应用程序中,Resources资源代表整个应用程序逻辑及物理存在的可用的数据总和,比如URL、class文件等。本章介绍 Spring 如何处理资源以及如何在 Spring 中使用资源。

2. Resources

2.1 Resource接口

Resource接口位于org.springframework.core.io.包中,扩展了InputStreamSource 接口,Resource接口主要用于对低级资源的访问。

public interface Resource extends InputStreamSource {
    boolean exists();

    default boolean isReadable() {
        return this.exists();
    }

    default boolean isOpen() {
        return false;
    }

    default boolean isFile() {
        return false;
    }

    URL getURL() throws IOException;

    URI getURI() throws IOException;

    File getFile() throws IOException;

    default ReadableByteChannel readableChannel() throws IOException {
        return Channels.newChannel(this.getInputStream());
    }

    long contentLength() throws IOException;

    long lastModified() throws IOException;

    Resource createRelative(String var1) throws IOException;

    @Nullable
    String getFilename();

    String getDescription();
}

Resource接口中一些最重要的方法是:

  • getInputStream(): 定位并打开资源,返回一个InputStream用于读取资源。预计每次调用都会返回一个新的 InputStream. 关闭流是调用者的责任。

  • exists(): 返回一个boolean指示该资源是否以物理形式实际存在。

  • isOpen(): 返回一个boolean指示此资源是否表示具有打开流的句柄。如果true,InputStream则不能多次读取,必须只读取一次然后关闭以避免资源泄漏。返回false所有常用资源实现,但InputStreamResource.

  • getDescription():返回此资源的描述,用于在使用资源时输出错误。这通常是完全限定的文件名或资源的实际 URL。

  • 其他方法可以获取表示资源的实际URL或File对象(如果底层实现兼容并支持该功能)。

2.2 内置的Resource实现类

Spring 包括几个内置的Resource实现:

  • UrlResource

  • ClassPathResource

  • FileSystemResource

  • PathResource

  • ServletContextResource

  • InputStreamResource

  • ByteArrayResource

2.2.1 UrlResource(通过URL地址获取资源)

UrlResource包装了java.net包下的URI、URL,并且可用于URL 访问的任何对象,例如文件、HTTPS 目标、FTP 目标等。

public class UrlResource extends AbstractFileResolvingResource {
    @Nullable
    private final URI uri;
    private final URL url;
    public String getFilename() {
        return StringUtils.getFilename(this.getCleanedUrl().getPath());
    }

    public String getDescription() {
        return "URL [" + this.url + "]";
    }
    // .....
}

所有 URL都采用String表示形式,以便使用适当的标准化前缀来指示一种 URL 类型和另一种 URL 类型。包括 :

  • file:访问文件系统路径
  • https:通过 HTTPS 协议访问资源
  • ftp:通过 FTP 访问资源等
2.2.2 ClassPathResource(通过类路径获取资源文件)

ClassPathResource表示应从类路径中获取的资源。它使用线程上下文类加载器、给定的类加载器或给定的类来加载资源。

public class ClassPathResource extends AbstractFileResolvingResource {

	private final String path;

	@Nullable
	private ClassLoader classLoader;

	@Nullable
	private Class<?> clazz;
	// .....
}

如果类路径资源在程序文件系统中,而不是在依赖的jar包中,则此资源实现支持解析为java.io.File。为了解决这个问题,各种资源实现始终支持解析为java.net.URL。

2.2.3 FileSystemResource(通过文件系统获取资源)

pring提供的FileSystemResource类用于访问文件系统资源。使用FileSystemResource来访问文件系统资源并没有太大的优势,因为Java提供的File类也可用于访问文件系统资源。

  /*默认从文件系统的当前路径加载book.xml资源*/
  FileSystemResource fsr = new FileSystemResource("book.xml");

FileSystemResource实例可使用FileSystemResource构造器显示地创建,但更多的时候它都是隐式创建。执行Spring的某个方法时,该方法接受一个代表资源路径的字符串参数,当Spring识别该字符串参数中包含file:前缀后,系统将会自动创建FileSystemResource对象。

2.2.4 PathResource(通过Path获取资源)

PathResource 实际上是一种java.nio.path.Path的替代品,通过{@code Path}API执行所有操作和转换,支持解析为{@link File}和{@link URL}。

public class PathResource extends AbstractResource implements WritableResource {

	private final Path path;

	public PathResource(Path path) {
		Assert.notNull(path, "Path must not be null");
		this.path = path.normalize();
	}
	// ......
}
2.2.5 ServletContextResource(获取ServletContext环境下的资源)

ServletContextResource由spring MVC提供,是从Web 应用程序根目录ServletContext中的相对路径的资源实现。

public class ServletContextResource extends AbstractFileResolvingResource implements ContextResource {

	private final ServletContext servletContext;

	private final String path;
	// .......
}
2.2.6 InputStreamResource(获取输入流封装的资源)

只有在没有其他特定的{@code Resource}实现时才应使用,首选{@link ByteArrayResource}或基于文件的{@code Resource}实现。

如果需要,请不要使用{@code InputStreamResource}将资源描述符保留在某个位置,或者如果需要从流中读取多次。

public class InputStreamResource extends AbstractResource {

	private final InputStream inputStream;

	private final String description;

	private boolean read = false;
	// .... 
}
2.2.7. ByteArrayResource(获取字节数组封装的资源)

ByteArrayResource代表byte[]数组资源,对于getInputStream将返回一个ByteArrayInputStream,可多次读取数组资源,即isOpen()永远返回false。

public class ByteArrayResource extends AbstractResource {

	private final byte[] byteArray;

	private final String description;
	// ...... 
}

2.3 ResourceLoader接口

ResourceLoader接口是加载资源的策略接口(如类路径或文件系统资源)。

public interface ResourceLoader {
	/** 用于从类路径加载的伪URL前缀:“classpath:”*/
	String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
	Resource getResource(String location);
	@Nullable
	ClassLoader getClassLoader();
}

所有应用程序上下文(application context)都实现该ResourceLoader接口。因此,可以使用所有应用上下文来获取Resource实例。

当您调用特定的应用程序上下文的getResource()方法,并且指定的位置路径没有特定的前缀时,您将返回适合该特定应用程序上下文的Resource类型。例如,假设以下代码片段是针对一个ClassPathXmlApplicationContext实例运行的:

Resource template = ctx.getResource("some/resource/path/myTemplate.txt");

针对ClassPathXmlApplicationContext,该代码返回一个ClassPathResource。如果对一个FileSystemXmlApplicationContext实例运行相同的方法,它将返回一个FileSystemResource. 对于WebApplicationContext,它将返回 ServletContextResource。类似地,它会为每个上下文返回适当的对象。

因此,您可以以适合特定应用程序上下文的方式加载资源。

另一方面,ClassPathResource也可以通过指定特殊classpath:前缀来强制使用,而不管应用程序上下文类型如何,如下例所示:

Resource template = ctx.getResource("classpath: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从文件系统加载。
https: https://myserver/logo.png 加载为URL.
(none) /data/config.xml 取决于底层ApplicationContext。

2.4 ResourcePatternResolver接口

ResourcePatternResolver接口继承自ResourceLoader接口,定义用于解决位置路径(例如,一个Ant风格的路径案)获取Resource对象。

public interface ResourcePatternResolver extends ResourceLoader {

    /**
     * 在所有根目录下搜索文件的伪URL的前缀
     * 与ResourceLoader中classpath不同的地方在于,此前缀会在所有的JAR包的根目录下搜索指定文件。
     */
    String CLASSPATH_ALL_URL_PREFIX = "classpath*:";

    /**
     * 返回指定路径下所有的资源对象。
     * 返回的对象集合应该有Set的语义,也就是说,对于同一个资源,只应该返回一个资源对象
     */
    Resource[] getResources(String locationPattern) throws IOException;

}

如上所示,该接口定义了一个CLASSPATH_ALL_URL_PREFIX 常量"classpath*:",作为类路径中的所有匹配的资源前缀。

2.5 ResourceLoaderAware接口

ResourceLoaderAware 是一个特殊的标记接口,用来标记提供 ResourceLoader 引用的对象。

public interface ResourceLoaderAware {

    void setResourceLoader(ResourceLoader resourceLoader);
}

当将一个 ResourceLoaderAware 接口的实现类部署到应用上下文时(此类会作为一个 spring 管理的 bean), 应用上下文会识别出此为一个 ResourceLoaderAware 对象,并将自身作为一个参数来调用 setResourceLoader() 函数,如此,该实现类便可使用 ResourceLoader 获取 Resource 实例来加载你所需要的资源。(附:为什么能将应用上下文作为一个参数来调用 setResourceLoader() 函数呢?不要忘了,在前文有谈过,spring 的所有上下文都实现了 ResourceLoader 接口)。

当然了,一个 bean 若想加载指定路径下的资源,除了刚才提到的实现 ResourcesLoaderAware 接口之外(将 ApplicationContext 作为一个 ResourceLoader 对象注入),bean 也可以实现 ApplicationContextAware 接口,这样可以直接使用应用上下文来加载资源。但总的来说,在需求满足都满足的情况下,最好是使用的专用 ResourceLoader 接口,因为这样代码只会与接口耦合,而不会与整个 spring ApplicationContext 耦合。与 ResourceLoader 接口耦合,抛开 spring 来看,就是提供了一个加载资源的工具类接口。

从 spring 2.5 开始,除了实现 ResourceLoaderAware 接口,也可采取另外一种替代方案——依赖于 ResourceLoader 的自动装配。”传统”的 constructor 和 bytype 自动装配模式都支持 ResourceLoader 的装配(可参阅 Section 5.4.5, “自动装配协作者” )——前者以构造参数的形式装配,后者以 setter 方法中参数装配。若为了获得更大的灵活性(包括属性注入的能力和多参方法),可以考虑使用基于注解的新注入方式。使用注解 @Autowiring 标记 ResourceLoader 变量,便可将其注入到成员属性、构造参数或方法参数中( @autowiring 详细的使用方法可参考Section 3.9.2, “@Autowired”.)。

2.6 资源依赖

如果bean本身将通过某种动态过程来确定和提供资源路径,那么bean可以使用ResourceLoader接口来加载资源。 j假设以某种方式加载一个模板,其中需要的特定资源取决于用户的角色。 如果资源是静态的,那么完全消除ResourceLoader接口的使用是有意义的,只需让bean公开它需要的Resource属性,那么它们就会以你所期望的方式被注入。

什么使得它们轻松注入这些属性,是所有应用程序上下文注册和使用一个特殊的JavaBeans PropertyEditor,它可以将String路径转换为Resource对象。 因此,如果myBean具有“资源”类型的模板属性,则可以使用该资源的简单字符串进行配置,如下所示:

<bean id="myBean" class="...">
<property name="template" value="some/resource/path/myTemplate.txt"/>
bean>

请注意,资源路径没有前缀,因为应用程序上下文本身将用作ResourceLoader,资源本身将通过ClassPathResource,FileSystemResource或ServletContextResource(根据需要)加载,具体取决于上下文的确切类型。

如果需要强制使用特定的资源类型,则可以使用前缀。 以下两个示例显示如何强制使用ClassPathResource和UrlResource(后者用于访问文件系统文件)。

<property name="template" value="classpath:some/resource/path/myTemplate.txt">
<property name="template" value="file:///some/resource/path/myTemplate.txt"/>

2.7 应用上下文和资源路径

2.7.1 构造应用上下文

(某一特定)应用上下文的构造器通常可以使用字符串或字符串数组所指代的(多个)资源(如 xml 文件)来构造当前上下文。

当指定的位置路径没有带前缀时,那从指定位置路径创建的 Resource 类型(用于后续加载 bean 定义),取决于所使用应用上下文。举个列子,如下所创建的 ClassPathXmlApplicationContext :

ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");

会从类路径加载 bean 的定义,因为所创建的 Resource 实例是 ClassPathResource.但所创建的是 FileSystemXmlApplicationContext 时,

ApplicationContext ctx = new FileSystemXmlApplicationContext("conf/appContext.xml");

则会从文件系统加载 bean 的定义,这种情况下,资源路径是相对工作目录而言的。

注意:若位置路径带有 classpath 前缀或 URL 前缀,会覆盖默认创建的用于加载 bean 定义的 Resource 类型,比如这种情况下的 FileSystemXmlApplicationContext

ApplicationContext ctx = new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");

,实际是从类路径下加载了 bean 的定义。可是,这个上下文仍然是 FileSystemXmlApplicationContext,而不是 ClassPathXmlApplicationContext,在后续作为 ResourceLoader 来使用时,不带前缀的路径仍然会从文件系统中加载。

构造 ClassPathXmlApplicationContext 实例 – 快捷方式

ClassPathXmlApplicationContext 提供了多个构造函数,以利于快捷创建 ClassPathXmlApplicationContext 的实例。最好莫不过使用只包含多个 xml 文件名(不带路径信息)的字符串数组和一个 Class 参数的构造器,所省略路径信息 ClassPathXmlApplicationContext 会从 Class 参数 获取:

下面的这个例子,可以让你对个构造器有比较清晰的认识。试想一个如下类似的目录结构:

com/
  foo/
    services.xml
    daos.xml
    MessengerService.class

由 services.xml 和 daos.xml 中 bean 所组成的 ClassPathXmlApplicationContext,可以这样来初始化:

ApplicationContext ctx = new ClassPathXmlApplicationContext(
                new String[] {"services.xml", "daos.xml"}, MessengerService.class);

欲要知道 ClassPathXmlApplicationContext 更多不同类型的构造器,请查阅 Javadocs 文档。

2.7.2 使用通配符构造应用上下文

从前文可知,应用上下文构造器的中的资源路径可以是单一的路径(即一对一地映射到目标资源);另外资源路径也可以使用高效的通配符——可包含 classpath*:前缀 或 ant 风格的正则表达式(使用 spring 的 PathMatcher 来匹配)。

通配符机制的其中一种应用可以用来组装组件式的应用程序。应用程序里所有组件都可以在一个共知的位置路径发布自定义的上下文片段,则最终应用上下文可使用 classpath*: 在同一路径前缀(前面的共知路径)下创建,这时所有组件上下文的片段都会被自动组装。

谨记,路径中的通配符特定用于应用上下文的构造器,只会在应用构造时有效,与其 Resource 自身类型没有任何关系。不可以使用 classpth*:来构造任一真实的 Resource,因为一个资源点一次只可以指向一个资源。(如果直接使用 PathMatcher 的工具类,也可以在路径中使用通配符)

Ant 风格模式

以下是一些使用了 Ant 风格的位置路径:

/WEB-INF/*-context.xml
  com/mycompany/**/applicationContext.xml
  file:C:/some/path/*-context.xml
  classpath:com/mycompany/**/applicationContext.xml

当位置路径使用了 ant 风格,解释器会遵循一套复杂且预定义的逻辑来解释这些位置路径。解释器会先从位置路径里获取最靠前的不带通配符的路径片段,使用这个路径片段来创建一个 Resource ,并从 Resource 里获取其 URL,若所获取到 URL 前缀并不是 “jar:”,或其他特殊容器产生的特殊前缀(如 WebLogic 的 zip:,WebSphere wsjar),则从 Resource 里获取 java.io.File 对象,并通过其遍历文件系统。进而解决位置路径里通配符;若获取的是 “jar:”的 URL ,解析器会从其获取一个 java.net.JarURLConnection 或手动解析此 URL,并遍历 jar 文件的内容进而解决位置路径的通配符。

对可移植性的影响

如果指定的路径已经是文件URL(显式地或隐含地,因为基本的ResourceLoader是一个文件系统的,那么通配符将保证以完全可移植的方式工作。

如果指定的路径是类路径位置,则解析器必须通过Classloader.getResource()调用获取最后一个非通配符路径段URL。 由于这只是路径的一个节点(而不是最后的文件),在这种情况下,它实际上是未定义的(在ClassLoader javadocs中)返回的是什么样的URL。 实际上,它始终是一个java.io.File,它表示类路径资源解析为文件系统位置的目录或某种类型的jar URL,其中类路径资源解析为一个jar位置。 尽管如此,这种操作仍然存在可移植性问题。

如果为最后一个非通配符段获取了一个jar URL,解析器必须能够从中获取java.net.JarURLConnection,或者手动解析jar URL,以便能够遍历该jar的内容,然后解析 通配符。 这将在大多数环境中正常工作,但在其他环境中将会失败,并且强烈建议您在依赖它之前,彻底地在您的特定环境中彻底测试来自jar的资源的通配符解析。

classpath: 的可移植性

当构造基于 xml 文件的应用上下文时,位置路径可以使用 classpath*:前缀:

ApplicationContext ctx = new ClassPathXmlApplicationContext(“classpath*:conf/appContext.xml”);
classpath*:的使用表示类路径下所有匹配文件名称的资源都会被获取(本质上就是调用了 ClassLoader.getResources(…) 方法),接着将获取到的资源组装成最终的应用上下文。

通配符路径依赖了底层 classloader 的 getResource 方法。可是现在大多数应用服务器提供了自身的 classloader 实现,其处理 jar 文件的形式可能各有不同。要在指定服务器测试 classpath*: 是否有效,简单点可以使用 getClass().getClassLoader().getResources(“”) 去加载类路径 jar包里的一个文件。尝试在两个不同的路径加载名称相同的文件,如果返回的结果不一致,就需要查看一下此服务器中与 classloader 行为设置相关的文档。

在位置路径的其余部分,classpath*: 前缀可以与 PathMatcher 结合使用,如:” classpath*:META-INF/*-beans.xml”。这种情况的解析策略非常简单:取位置路径最靠前的无通配符片段,调用 ClassLoader.getResources() 获取所有匹配的类层次加载器可加载的的资源,随后将 PathMacher 的策略应用于每一个获得的资源(起过滤作用)。

通配符的补充说明

除非所有目标资源都存于文件系统,否则classpath*:和 ant 风格模式的结合使用,都只能在至少有一个确定根包路径的情况下,才能达到预期的效果。换句话说,就是像 classpath*.xml 这样的 pattern 不能从根目录的 jar 文件中获取资源,只能从根目录的扩展目录获取资源。此问题的造成源于 jdk ClassLoader.getResources() 方法的局限性——当向 ClassLoader.getResources() 传入空串时(表示搜索潜在的根目录),只能获取的文件系统的文件位置路径,即获取不了 jar 中文件的位置路径。

如果在多个类路径上存在所搜索的根包,那使用 classpath: 和 ant 风格模式一起指定的资源不保证找到匹配的资源。因为使用如下的 pattern classpath:com/mycompany/**/service-context.xml
去搜索只在某一个路径存在的指定资源com/mycompany/package1/service-context.xml
时,解析器只会对 getResource(“com/mycompany”) 返回的(第一个) URL 进行遍历和解释,则当在多个类路径存在基础包节点 “com/mycompany” 时(如在多个 jar 存在这个基础节点),解析器就不一定会找到指定资源。因此,这种情况下建议结合使用 classpath*: 和 ant 风格模式,classpath*:会让解析器去搜索所有包含基础包节点的类路径。

2.7.3 FileSystemResource 注意事项

FileSystemResource 没有依附 FileSystemApplicationContext,因为 FileSystemApplicationContext 并不是一个真正的 `ResourceLoader。FileSystemResource 并没有按约定规则来处理绝对和相对路径。相对路径是相对与当前工作而言,而绝对路径则是相对文件系统的根目录而言。

然而为了向后兼容,当 FileSystemApplicationContext 是一个 ResourceLoader 实例时,我们做了一些改变 —— 不管 FileSystemResource` 实例的位置路径是否以 / 开头, FileSystemApplicationContext 都强制将其作为相对路径来处理。事实上,这意味着以下例子等效:

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: 的 UrlResource。

// Resource 只会是 UrlResource,与上下文的真实类型无关
ctx.getResource("file:///some/resource/path/myTemplate.txt");
// 强制 FileSystemXmlApplicationContext 通过 UrlResource 加载资源
ApplicationContext ctx = new FileSystemXmlApplicationContext("file:///conf/context.xml");

你可能感兴趣的:(Spring,Framework系列,spring,java)