8. Resources

8.1 Introduction

不幸的是,Java 的标准java.net.URL类和用于各种 URL 前缀的标准处理程序不足以满足所有对低级资源的访问。例如,没有标准化的URL实现可用于访问需要从 Classpath 或相对于ServletContext获得的资源。尽管可以为专用的URL前缀注册新的处理程序(类似于现有的针对http:的前缀的处理程序),但这通常相当复杂,并且URL接口仍然缺少某些理想的功能,例如用于检查是否存在URL的方法。指向的资源。

8.2 资源界面

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界面中一些最重要的方法是:

其他方法允许您获取表示资源的实际URLFile对象(如果基础实现兼容并且支持该功能)。

Resource抽象在 Spring 本身中被广泛使用,在需要资源时,它是许多方法签名中的参数类型。某些 Spring API 中的其他方法(例如,各种ApplicationContext实现的构造函数)采用String,该形式以无装饰或简单的形式用于创建适合该上下文实现的Resource,或通过String路径上的特殊前缀来允许调用者指定必须创建和使用特定的Resource实现。

虽然Resource接口在 Spring 和 Spring 上经常使用,但是在您自己的代码中单独用作通用工具类来访问资源实际上非常有用,即使您的代码不知道或不关心其他任何代码Spring的一部分。虽然这将您的代码耦合到 Spring,但实际上仅将其耦合到这套 Util 小类,这些 Util 类是URL的更强大的替代品,可以被认为等同于您将用于此目的的任何其他库。

重要的是要注意Resource抽象不能代替功能:它尽可能地包装它。例如,UrlResource包裹一个 URL,并使用包裹的URL来完成其工作。

8.3 内置资源实现

在 Spring 中直接提供了许多Resource实现:

8.3.1 UrlResource

UrlResource包装java.net.URL,可用于访问通常可通过 URL 访问的任何对象,例如文件,HTTP 目标,FTP 目标等。所有 URL 均具有标准化的String表示形式,因此可以使用适当的标准化前缀用来表示另一种 URL 类型。这包括file:用于访问文件系统路径,http:用于通过 HTTP 协议访问资源,ftp:用于通过 FTP 访问资源等。

UrlResource是由 Java 代码使用UrlResource构造函数显式创建的,但通常在调用带有String参数(表示路径)的 API 方法时隐式创建。对于后一种情况,JavaBeans PropertyEditor将最终决定要创建的Resource类型。如果路径字符串包含一些众所周知的前缀(例如classpath:),它将为该前缀创建一个适当的专用Resource。但是,如果无法识别前缀,它将假定这只是一个标准 URL 字符串,并会创建UrlResource

8.3.2 ClassPathResource

此类表示应从 Classpath 获取的资源。这使用线程上下文类加载器,给定的类加载器或给定的类来加载资源。

如果 Classpath 资源驻留在文件系统中,则此Resource实现支持以java.io.File的解析方式,但不支持驻留在 jar 中并且尚未(通过 servlet 引擎或任何环境将其扩展到)文件系统的 Classpath 资源。为了解决这个问题,各种Resource实现始终支持将解析作为java.net.URL

ClassPathResource是由 Java 代码使用ClassPathResource构造函数显式创建的,但通常在调用带有String参数(表示路径)的 API 方法时隐式创建。对于后一种情况,JavaBeans PropertyEditor将识别字符串路径上的特殊前缀classpath:,并在这种情况下创建ClassPathResource

8.3.3 FileSystemResource

这是java.io.File个句柄的Resource实现。显然,它支持FileURL分辨率。

8.3.4 ServletContextResource

这是ServletContext资源的Resource实现,解释了相关 Web 应用程序根目录中的相对路径。

这始终支持流访问和 URL 访问,但是仅在扩展 Web 应用程序归档且资源实际位于文件系统上时才允许java.io.File访问。它是在文件系统上扩展还是像这样扩展,或者直接从 JAR 或诸如 DB 之类的其他地方访问(可以想象),实际上都取决于 Servlet 容器。

8.3.5 InputStreamResource

给定InputStreamResource实现。仅在没有特定的Resource实现适用时才应使用此选项。特别是,在可能的情况下,最好使用ByteArrayResource或任何基于文件的Resource实现。

与其他Resource实现相反,这是已经打开的资源的 Descriptors-因此从isOpen()返回true。如果需要将资源 Descriptors 保存在某个地方,或者需要多次读取流,请不要使用它。

8.3.6 ByteArrayResource

这是给定字节数组的Resource实现。它为给定的字节数组创建一个ByteArrayInputStream

这对于从任何给定的字节数组加载内容很有用,而不必求助于一次性InputStreamResource

8.4 ResourceLoader

ResourceLoader接口是由可以返回(即加载)Resource实例的对象实现的。

public interface ResourceLoader {

    Resource getResource(String location);

}

所有应用程序上下文均实现ResourceLoader接口,因此所有应用程序上下文均可用于获取Resource实例。

当您在特定的应用程序上下文中调用getResource()并且指定的位置路径没有特定的前缀时,您将获得适合该特定应用程序上下文的Resource类型。例如,假设针对ClassPathXmlApplicationContext实例执行了以下代码段:

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

返回的将是ClassPathResource;如果对FileSystemXmlApplicationContext实例执行了相同的方法,则将获得FileSystemResource。对于WebApplicationContext,您将获得ServletContextResource,依此类推。

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

另一方面,您也可以通过指定特殊的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("http://myhost.com/resource/path/myTemplate.txt");

下表总结了将String s 转换为Resource s 的策略:

表 8.1. 资源字符串

Prefix Example Explanation
classpath: classpath:com/myapp/config.xml 从 Classpath 加载。
file: file:///data/config.xml 从文件系统作为URL加载。 [1]
http: http://myserver/logo.png 载入为URL
(none) /data/config.xml 取决于基础的ApplicationContext
[1]但另请参见第 8.7.3 节“ FileSystemResource 警告”

8.5 ResourceLoaderAware 界面

ResourceLoaderAware接口是一个特殊的标记接口,用于标识希望提供ResourceLoader参考的对象。

public interface ResourceLoaderAware {

    void setResourceLoader(ResourceLoader resourceLoader);
}

当一个类实现ResourceLoaderAware并部署到应用程序上下文中(作为 Spring 托管 bean)时,该类被应用程序上下文识别为ResourceLoaderAware。然后,应用程序上下文将调用setResourceLoader(ResourceLoader),并提供自身作为参数(请记住,Spring 中的所有应用程序上下文都实现ResourceLoader接口)。

当然,由于ApplicationContextResourceLoader,因此 bean 也可以实现ApplicationContextAware接口并直接使用提供的应用程序上下文来加载资源,但是通常,如果需要的话,最好使用专用的ResourceLoader接口。该代码将仅耦合到资源加载接口,该接口可被视为 Util 接口,而不是整个 Spring ApplicationContext接口。

从 Spring 2.5 开始,您可以依靠ResourceLoader的自动装配来替代实现ResourceLoaderAware接口。现在,“传统” constructorbyType自动装配模式(如第 7.4.5 节“自动装配合作者”中所述)能够分别为构造函数参数或 setter 方法参数提供ResourceLoader类型的依赖项。为了获得更大的灵 Active(包括自动装配字段和使用多个参数方法的能力),请考虑使用新的基于 Comments 的自动装配功能。在这种情况下,只要所讨论的字段,构造函数或方法带有@Autowired注解,则ResourceLoader将自动连接到期望ResourceLoader类型的字段,构造函数参数或方法参数中。有关更多信息,请参见第 7.9.2 节“ @Autowired”

8.6 将资源作为依赖项

如果 Bean 本身将通过某种动态过程来确定并提供资源路径,那么对于 Bean 来说,使用ResourceLoader接口加载资源可能是有意义的。以加载某种模板为例,其中所需的特定资源取决于用户的角色。如果资源是静态的,则完全消除对ResourceLoader接口的使用是有意义的,只是让 Bean 公开所需的Resource属性,并期望将其注入到其中。

然后注入这些属性很简单,原因是所有应用程序上下文都注册并使用了特殊的 JavaBeans PropertyEditor,它们可以将String路径转换为Resource对象。因此,如果myBean具有类型为Resource的模板属性,则可以使用该资源的简单字符串对其进行配置,如下所示:

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

请注意,资源路径没有前缀,因此,由于应用程序上下文本身将用作ResourceLoader,因此根据上下文的确切类型,资源本身将通过ClassPathResourceFileSystemResourceServletContextResource加载(视情况而定) 。

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

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

8.7 应用程序上下文和资源路径

8.7.1 构造应用程序上下文

应用程序上下文构造函数(针对特定的应用程序上下文类型)通常采用字符串或字符串数组作为资源(例如构成上下文定义的 XML 文件)的位置路径。

当这样的位置路径没有前缀时,从该路径构建并用于加载 Bean 定义的特定Resource类型取决于特定的应用程序上下文,并且适合于特定的应用程序上下文。例如,如果您按以下方式创建ClassPathXmlApplicationContext

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

Bean 定义将使用ClassPathResource从 Classpath 中加载。但是,如果您按以下方式创建FileSystemXmlApplicationContext

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

Bean 定义将从文件系统位置(在这种情况下相对于当前工作目录)加载。

请注意,在位置路径上使用特殊的 classpath 前缀或标准 URL 前缀将覆盖为加载定义而创建的默认类型Resource。所以这个FileSystemXmlApplicationContext

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

构造 ClassPathXmlApplicationContext 实例-快捷方式

ClassPathXmlApplicationContext公开了许多构造函数以启用方便的实例化。基本思想是,仅提供一个字符串数组,仅包含 XML 文件本身的文件名(不包含前导路径信息),并且提供ClassClassPathXmlApplicationContext将从提供的类派生路径信息。

希望有一个例子可以使这一点变得清楚。考虑如下目录布局:

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。

8.7.2 应用程序上下文构造函数资源路径中的通配符

应用程序上下文构造函数值中的资源路径可以是简单路径(如上所示),该路径具有到目标资源的一对一 Map,或者可以包含特殊的“ classpath *:”前缀和/或内部 Ant-样式正则表达式(使用 Spring 的PathMatcherUtil 进行匹配)。后者都是有效的通配符

这种机制的一种用途是在进行组件样式的应用程序组装时。所有组件都可以将上下文定义片段“发布”到一个众所周知的位置路径,当使用以classpath*:为前缀的相同路径创建最终应用程序上下文时,所有组件片段都将自动被拾取。

请注意,该通配符特定于在应用程序上下文构造函数中使用资源路径(或在直接使用PathMatcherUtil 类层次结构时),并且在构造时已解决。它与Resource类型本身无关。不能使用classpath*:前缀构造实际的Resource,因为资源一次仅指向一个资源。

Ant-style Patterns

当路径位置包含 Ant 样式的模式时,例如:

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

解析程序遵循一个更复杂但已定义的过程来尝试解析通配符。它为到达最后一个非通配符段的路径生成资源,并从中获取 URL。如果此 URL 不是jar: URL 或特定于容器的变体(例如,WebLogic 中的zip:,WebSphere 中的wsjar等),则从中获取java.io.File并通过遍历文件系统来解析通配符。对于 jar URL,解析器会从中获取java.net.JarURLConnection或手动解析 jar URL,然后遍历 jar 文件的内容来解析通配符。

对便携性的影响

如果指定的路径已经是文件 URL(由于基ResourceLoader是文件系统 1,则是显式的或隐式的),那么通配符可以以完全可移植的方式工作。

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

如果为最后一个非通配符段获取了 jar URL,则解析程序必须能够从中获取java.net.JarURLConnection,或者手动解析 jar URL,才能遍历 jar 的内容并解析通配符。这在大多数环境中都可以使用,但在其他环境中却不能使用,因此强烈建议您在依赖特定环境之前,对来自 jars 的资源的通配符解析进行彻底测试。

classpath *:前缀

在构造基于 XML 的应用程序上下文时,位置字符串可以使用特殊的classpath*:前缀:

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

这个特殊的前缀指定必须获取与给定名称匹配的所有 Classpath 资源(内部,这实际上是通过ClassLoader.getResources(…)调用发生的),然后合并以形成最终的应用程序上下文定义。

Note

通配符 Classpath 依赖于基础类加载器的getResources()方法。由于当今大多数应用程序服务器提供其自己的类加载器实现,因此行为可能有所不同,尤其是在处理 jar 文件时。检查classpath*是否有效的简单测试是使用类加载器从 ClasspathgetClass().getClassLoader().getResources("<someFileInsideTheJar>")的 jar 中加载文件。尝试对具有相同名称但位于两个不同位置的文件进行此测试。如果返回了不合适的结果,请检查应用程序服务器文档中可能影响类加载器行为的设置。

classpath*:前缀也可以在其余位置路径(例如classpath*:META-INF/*-beans.xml)中与PathMatcher模式组合。在这种情况下,解析策略非常简单:在最后一个非通配符路径段上使用ClassLoader.getResources()调用以获取类加载器层次结构中的所有匹配资源,然后在每个资源之外使用与上述相同的 PathMatcher 解析策略通配符子路径。

与通配符有关的其他说明

请注意,classpath*:与 Ant 样式的模式结合使用时,除非模式文件实际存在于目标文件中,否则在模式启动之前,它只能与至少一个根目录可靠地一起工作。这意味着类似classpath*:*.xml的模式可能不会从 jar 文件的根目录检索文件,而只会从扩展目录的根目录检索文件。

Spring 检索 Classpath 条目的能力源于 JDK 的ClassLoader.getResources()方法,该方法仅返回传入的空字符串的文件系统位置(指示可能要搜索的根目录)。 Spring 也会评估 jar 文件中的URLClassLoader运行时配置和“ java.class.path”Lists,但这不能保证会导致可移植行为。

Note

扫描 Classpath 包需要在 Classpath 中存在相应的目录条目。使用 Ant 构建 JAR 时,请确保您不激活 JAR 任务的仅文件开关。同样,在某些环境下,例如基于安全策略,可能不会公开 Classpath 目录。 JDK 1.7.0_45 及更高版本上的独立应用程序(需要在 Lists 中设置“受信任的库”;请参阅http://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)。

如果要搜索的根包在多个 Classpath 位置中可用,则不能保证具有classpath:资源的 Ant 样式模式会找到匹配的资源。这是因为诸如

com/mycompany/package1/service-context.xml

可能只在一个位置,但是当诸如

classpath:com/mycompany/**/service-context.xml

用于尝试解析它,解析器将使用getResource("com/mycompany");返回的(第一个)URL。如果此基本包节点存在于多个类加载器位置,则实际的最终资源可能不在其下方。因此,在这种情况下,最好在相同的 Ant 样式模式下使用“``”,它将搜索包含根包的所有 Classpath 位置。

8.7.3 FileSystemResource 警告

未连接到FileSystemApplicationContextFileSystemResource(即FileSystemApplicationContext不是实际的ResourceLoader)将按您期望的那样对待绝对路径与相对路径。相对路径是相对于当前工作目录的,而绝对路径是相对于文件系统的根的。

但是,出于向后兼容性(历史)的原因,当FileSystemApplicationContextResourceLoader时,情况会有所变化。 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");
FileSystemXmlApplicationContext ctx = ...;
ctx.getResource("/some/resource/path/myTemplate.txt");

实际上,如果需要 true 的绝对文件系统路径,最好放弃使用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");
上一章 首页 下一章