8. 资源

8.1 简介

遗憾的是,Java 的标准java.net.URL class 和各种 URL 前缀的标准处理程序不足以完全访问 low-level 资源。例如,没有标准化的URL implementation 可用于访问需要从 classpath 或相对于ServletContext获取的资源。虽然可以为专用的URL前缀注册新的处理程序(类似于http:等前缀的现有处理程序),但这通常非常复杂,并且URL接口仍然缺少一些理想的功能,例如检查是否存在的方法指向的资源。

8.2 资源界面

Spring 的Resource接口是一个更强大的接口,用于抽象对 low-level 资源的访问。

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():返回boolean,指示此资源是否表示具有开放流的句柄。如果true,则InputStream不能多次读取,并且必须只读取一次然后关闭以避免资源泄漏。将用于所有常用资源 implementations,的 exception。

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

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

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

虽然接口与 Spring 和 Spring 一起使用很多,但实际上非常有用的是在你自己的 code 中作为通用实用 class 使用,以便访问资源,即使你的 code 不知道或不关心任何其他的 Spring 的部分内容。虽然这会将你的 code 耦合到 Spring,但它实际上只将它耦合到这一小组实用程序 classes,它们作为URL的更强大的替代品,并且可以被认为等同于你将用于此目的的任何其他 library。

重要的是要注意Resource抽象不会取代功能:它尽可能地包装它。对于 example,UrlResource包装了一个 URL,并使用包装的URL来完成其工作。

8.3 Built-in 资源实现

在 Spring 中,有很多Resource __mplement 直接提供。

8.3.1 UrlResource

UrlResource包装java.net.URL,可用于访问通常可通过 URL 访问的任何 object,例如 files,HTTP 目标,FTP 目标等。所有 URL 都具有标准化的String表示,因此适当的标准化前缀是用于表示另一个 URL 类型。这包括file:用于访问文件系统_path,http:用于通过 HTTP 协议访问资源,ftp:用于通过 FTP 访问资源等。

是由 Java code 使用UrlResource构造函数显式创建的,但是当您调用一个带有String参数的 API 方法时,通常会隐式创建UrlResource,该参数用于表示路径。对于后一种情况,JavaBeans PropertyEditor最终将决定创建哪种类型的Resource。如果路径 string 包含一些 well-known(对它,那就是)前缀,例如classpath:,它将为该前缀创建一个适当的专用Resource。但是,如果它不识别前缀,它将假设这只是一个标准的 URL string,并将创建一个UrlResource

8.3.2 ClassPathResource

此 class 表示应从 classpath 获取的资源。它使用线程 context class 加载器,给定的 class 加载器或给定 class 来加载 loading 资源。

如果 class 路径资源驻留在文件系统中,则此Resource implementation 支持解析为java.io.File,但不支持驻留在 jar 中的 classpath 资源,并且尚未(通过 servlet 引擎或任何环境)扩展到文件系统。为了解决这个问题,各种Resource __mplement 总是支持解析为java.net.URL

是由 Java code 使用ClassPathResource构造函数显式创建的,但是当您调用一个带有String参数的 API 方法时,通常会隐式创建ClassPathResource,该参数用于表示路径。对于后一种情况,JavaBeans PropertyEditor将识别 string 路径上的特殊前缀classpath:,并在此情况下创建ClassPathResource

8.3.3 FileSystemResource

这是java.io.File句柄的Resource implementation。它显然支持分辨率为FileURL

8.3.4 ServletContextResource

这是ServletContext资源的Resource implementation,解释相关 web application 根目录中的相对_path。

这始终支持流访问和 URL 访问,但只有在扩展 web application 存档且资源实际位于文件系统上时才允许java.io.File访问。它是否在这样的文件系统上展开,或者直接从 JAR 或其他地方(如 DB)(可以想象)访问,实际上是依赖于 Servlet 容器。

8.3.5 InputStreamResource

给定InputStreamResource implementation。只有在没有特定的Resource implementation 适用的情况下才能使用此选项。特别是,在可能的情况下,更喜欢ByteArrayResource或任何 file-based Resource __mplement。

与其他Resource implementations 相比,这是已打开资源的描述符 - 因此从isOpen()返回true。如果需要将资源描述符保留在某处,或者需要多次读取流,请不要使用它。

8.3.6 ByteArrayResource

这是给定字节 array 的Resource implementation。它为给定的字节 array 创建ByteArrayInputStream

它对于从任何给定的字节 array 中加载内容非常有用,而不必求助于 single-use InputStreamResource

8.4 ResourceLoader

ResourceLoader接口应由 objects 实现,它可以 return(i.e.load)Resource实例。

public interface ResourceLoader {

    Resource getResource(String location);

}

所有 application 上下文都实现了ResourceLoader接口,因此所有 application 上下文都可用于获取Resource实例。

当您在特定的 application context 上调用getResource(),并且指定的位置路径没有特定的前缀时,您将返回适合该特定 application context 的Resource类型。对于 example,假设对ClassPathXmlApplicationContext实例执行了以下 code 代码段:

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

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

因此,您可以以适合特定 application context 的方式加载资源。

另一方面,通过指定特殊的classpath:前缀,您也可以强制使用ClassPathResource,而不管 application context 类型如何:

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");

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

表格 1_.资源 strings

字首说明
classpath:classpath:com/myapp/config.xml从 classpath 加载。
文件:file:///data/config.xml从文件系统加载为URL[1]
HTTP:http://myserver/logo.png加载为URL
(没有)/data/config.xml取决于潜在的ApplicationContext

[1]但也见第 8.7.3 节,“FileSystemResource 警告”

8.5 ResourceLoaderAware 接口

ResourceLoaderAware接口是一个特殊的标记接口,用于标识期望提供ResourceLoader reference 的 objects。

public interface ResourceLoaderAware {

    void setResourceLoader(ResourceLoader resourceLoader);
}

当 class 实现ResourceLoaderAware并部署到 application context(作为 Spring-managed bean)时,application context 将其识别为ResourceLoaderAware。然后 application context 将调用setResourceLoader(ResourceLoader),将自身作为参数提供(请记住,Spring 中的所有 application 上下文都会实现ResourceLoader接口)。

当然,由于ApplicationContextResourceLoader,bean 也可以实现ApplicationContextAware接口并直接使用提供的 application context 来加载资源,但一般来说,最好使用专门的ResourceLoader接口,如果这就是所需要的。 code 只会耦合到资源 loading 接口,它可以被认为是一个实用程序接口,而不是整个 Spring ApplicationContext接口。

从 Spring 2.5 开始,您可以依靠自动装配ResourceLoader作为实现ResourceLoaderAware接口的替代方法。 “传统”constructorbyType自动装配模式(如第 7.4.5 节,“自动装配协作者”中所述)现在能够分别为构造函数参数或 setter 方法参数提供类型ResourceLoader的依赖性。为了获得更大的灵活性(包括自动装配字段和多参数方法的能力),请考虑使用新的 annotation-based 自动装配 features。在这种情况下,ResourceLoader将被自动装入一个字段,构造函数参数或方法参数,期望ResourceLoader类型为 long,因为有问题的字段,构造函数或方法带有@Autowired annotation。有关更多信息,请参阅第 7.9.2 节,“@Autowired”

8.6 作为依赖项的资源

如果 bean 本身将通过某种动态 process 确定并提供资源路径,那么 bean 使用ResourceLoader接口加载资源可能是有意义的。考虑将某个模板的 loading 作为 example,其中所需的特定资源取决于用户的角色。如果资源是静态的,那么完全消除ResourceLoader接口的使用是有意义的,只需让 bean 公开它需要的Resource properties,并期望它们被注入其中。

那些 inject 这些 properties 使得它变得微不足道的是,所有 application 上下文都注册并使用一个特殊的 JavaBeans PropertyEditor,它可以将String _path 转换为Resource objects。因此,如果myBean具有Resource类型的模板 property,则可以使用该资源的简单 string 进行配置,如下所示:

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

请注意,资源路径没有前缀,因为 application context 本身将用作ResourceLoader,资源本身将通过ClassPathResourceFileSystemResourceServletContextResource(根据需要)加载,具体取决于 context 的确切类型。

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

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

8.7 Application contexts 和 Resource paths

8.7.1 构造 application 上下文

application context 构造函数(对于特定的 application context 类型)通常使用 string 的 string 或 array 作为 resource(s 的位置 path(s),例如构成 context 定义的 XML files。

当这样的位置路径没有前缀时,从该路径构建并用于加载 bean 定义的特定Resource类型取决于特定的 application context 并且是否合适。例如,如果您创建ClassPathXmlApplicationContext,如下所示:

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

bean 定义将从 classpath 加载,因为将使用ClassPathResource。但是如果你创建一个FileSystemXmlApplicationContext如下:

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

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

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

ApplicationContext ctx =
    new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");
  • 实际上将从 classpath 加载其 bean 定义。但是,它仍然是FileSystemXmlApplicationContext。如果它随后用作ResourceLoader,任何未加前缀的 paths 仍将被视为 filesystem paths。

构造 ClassPathXmlApplicationContext 实例 - 快捷方式

ClassPathXmlApplicationContext公开了许多构造函数以实现方便的实例化。基本的 idea 是一个只提供 string array 只包含 XML files 本身的文件名(没有前导路径信息),还有一个Class; ClassPathXmlApplicationContext将从提供的 class 派生路径信息。

一个 example 有望清楚地表明这一点。考虑一个如下所示的目录布局:

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

'services.xml''daos.xml'中定义的 beans 组成的ClassPathXmlApplicationContext实例可以像这样实例化...

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

有关各种构造函数的详细信息,请参阅ClassPathXmlApplicationContext javadocs。

8.7.2 application context 构造函数资源 paths 中的通配符

application context 构造函数值中的资源 paths 可以是一个简单路径(如上所示),它具有到目标资源的 one-to-one 映射,或者可以包含特殊的“classpath *:”前缀 and/or internal Ant-style 正则表达式(使用 Spring 的PathMatcher匹配)效用)。后者都是有效的通配符

这种机制的一个用途是在进行 component-style application 程序集时。所有组件都可以将 context 定义片段“发布”到 well-known 位置路径,并且当使用前缀为classpath*:的相同路径创建最终 application context 时,将自动拾取所有 component 片段。

请注意,此通配符特定于 application context 构造函数中使用资源_path(或直接使用PathMatcher实用程序 class 层次结构时),并在构造 time 时解析。它与Resource类型本身无关。使用classpath*:前缀来构造实际的Resource是不可能的,因为资源仅指向 time 时的一个资源。

Ant-style 模式

当路径位置包含 Ant-style pattern 时,对于 example:

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

解析器遵循更复杂但定义的过程来尝试解析通配符。它为直到最后 non-wildcard 段的路径生成一个 Resource,并从中获取一个 URL。如果此 URL 不是jar: URL 或 container-specific 变体(WebLogic 中的 e.g. zip:,WebSphere 中的wsjar,etc.),则从中获取java.io.File并通过遍历文件系统来解析通配符。对于 jar URL,解析器从中获取java.net.JarURLConnection或手动解析 jar URL,然后遍历 jar 文件的内容以解析通配符。

对可携带性的影响

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

如果指定的路径是 classpath 位置,则解析程序必须通过Classloader.getResource()调用获取最后的 non-wildcard 路径段 URL。由于这只是路径的一个节点(不是最后的文件),因此实际上未定义(在ClassLoader javadocs 中)在这种情况下返回的 URL 究竟是什么类型。实际上,它总是java.io.File表示目录,classpath 资源解析为文件系统位置,或某种 jar URL,classpath 资源解析为 jar 位置。尽管如此,这项行动仍存在可移植性问题。

如果为最后一个 non-wildcard 段获取了 jar URL,则解析器必须能够从中获取java.net.JarURLConnection,或者手动解析 jar URL,以便能够遍历 jar 的内容,并解析通配符。这适用于大多数环境,但在其他环境中会失败,强烈建议在依赖它之前,在特定环境中对来自 jars 的资源的通配符解析进行全面测试。

classpath *:前缀

构造 XML-based application context 时,位置 string 可以使用特殊的classpath*:前缀:

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

此特殊前缀指定必须获取 match 给定 name 的所有 classpath 资源(内部,这通常通过ClassLoader.getResources(…)调用发生),然后合并以形成最终的 application context 定义。

通配符 classpath 依赖于底层类加载器的getResources()方法。由于现在大多数 application 服务器都提供了自己的类加载器 implementation,因此行为可能会有所不同,特别是在处理 jar files 时。检查classpath*是否有效的简单测试是使用类加载器从 class 路径上的 jar 中加载文件:getClass().getClassLoader().getResources("<someFileInsideTheJar>")。尝试使用具有相同 name 但放在两个不同位置的 files 进行此测试。如果返回了不适当的结果,请检查 application 服务器文档以获取可能影响类加载器行为的设置。

对于 example classpath*:META-INF/*-beans.xmlclasspath*:前缀也可以与位置路径的 rest 中的PathMatcher pattern 组合使用。在这种情况下,解析策略非常简单:在最后一个 non-wildcard 路径段上使用ClassLoader.getResources()调用来获取 class 加载器层次结构中的所有匹配资源,然后关闭每个资源,将上述相同的 PathMatcher 解析策略用于通配符子路径。

与通配符有关的其他说明

请注意,与 Ant-style 模式结合使用只能在 pattern 启动之前与至少一个根目录可靠地工作,除非实际目标 files 位于文件系统中。这意味着像classpath*:*.xml这样的 pattern 可能无法从 jar files 的根目录中检索 files,而只能从扩展目录的根目录中检索 files。

Spring 检索 classpath 条目的能力来自 JDK 的ClassLoader.getResources()方法,该方法仅返回 passed-in empty string 的文件系统位置(指示搜索的潜在根)。 Spring 也会评估URLClassLoader运行时 configuration 和 jar files 中的“java.class.path”清单,但这不能保证导致可移植行为。

扫描 classpath 包需要在 classpath 中存在相应的目录条目。使用 Ant build JAR 时,请确保不要激活 JAR 任务的 files-only 开关。此外,classpath 目录可能不会在某些环境中基于安全性 policies 公开,e.g. JDK 1.7.0_45 及更高版本上的独立应用程序(在清单中需要'Trusted-Library'设置;请参阅http://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)。

如果要搜索的根包在多个 class 路径位置中可用,则不保证具有classpath:资源的 Ant-style 模式可以找到匹配的资源。这是因为资源如

com/mycompany/package1/service-context.xml

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

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

用于尝试解决它,解析器将解决由getResource("com/mycompany");返回的(第一个)URL。如果此基本包节点存在于多个类加载器位置中,则实际的最终资源可能不在下面。因此,最好在这种情况下使用“classpath*:”和相同的 Ant-style pattern,它将搜索包含根包的所有 class 路径位置。

8.7.3 FileSystemResource 警告

没有附加到FileSystemApplicationContextFileSystemResource(即,FileSystemApplicationContext不是实际的ResourceLoader)将按照您的预期处理绝对路径与相对路径。相对 paths 相对于当前工作目录,而绝对 paths 相对于文件系统的根目录。

但是,出于向后兼容性(历史)的原因,当FileSystemApplicationContextResourceLoader时,这会发生变化。 FileSystemApplicationContext只是强制所有附加的FileSystemResource实例将所有位置 paths 视为相对的,无论它们是否以前导斜杠开头。实际上,这意味着以下内容是等效的:

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

如下所示:(尽管它们有所不同,因为一个案例是相对的而另一个案例是 absolute.)

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

实际上,如果需要 true 绝对文件系统 paths,最好放弃使用带有FileSystemResource/FileSystemXmlApplicationContext的绝对_path,并使用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");