8. 资源

8.1 简介

遗憾的是,Java的标准 java.net.URL 类和各种URL前缀的标准处理程序不足以完全访问低级资源。例如,没有标准化的 URL 实现可用于访问需要从类路径或相对于 ServletContext 获取的资源。虽然可以为专用的 URL 前缀注册新的处理程序(类似于现有的前缀如 http: 的处理程序),但这通常非常复杂,并且 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 界面中一些最重要的方法是:

  • getInputStream() :找到并打开资源,返回 InputStream 以从资源中读取。预计每次调用都会返回一个新的 InputStream 。呼叫者有责任关闭流。

  • exists() :返回 boolean ,指示此资源是否实际存在于物理形式中。

  • isOpen() :返回 boolean ,指示此资源是否表示具有打开流的句柄。如果 true ,则 InputStream 无法多次读取,并且必须只读取一次然后关闭以避免资源泄漏。对于所有常规资源实现,将为 false ,但 InputStreamResource 除外。

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

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

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

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

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

8.3 内置资源实现

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

8.3.1 UrlResource

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

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

8.3.2 ClassPathResource

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

如果类路径资源驻留在文件系统中,则此 Resource 实现支持解析为 java.io.File ,但不支持驻留在jar中且尚未展开的类路径资源(由servlet引擎,或者无论环境如何)到文件系统。为了解决这个问题,各种 Resource 实现始终支持作为 java.net.URL 的解决方案。

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

8.3.3 FileSystemResource

这是 java.io.File 句柄的 Resource 实现。它显然支持作为 File 的分辨率和 URL

8.3.4 ServletContextResource

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

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

8.3.5 InputStreamResource

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

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

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 的策略:

Table 8.1. Resource strings

前缀示例说明
classpath:classpath:com/myapp/config.xml从类路径加载。
file:file:///data/config.xml从文件系统加载为 URL[1]
http:http://myserver/logo.png加载为 URL
(无)/data/config.xml取决于基础 ApplicationContext
[1] 但也见 Section 8.7.3, “FileSystemResource caveats”

8.5 ResourceLoaderAware接口

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

public interface ResourceLoaderAware {

    void setResourceLoader(ResourceLoader resourceLoader);
}

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

当然,由于 ApplicationContext 是一个 ResourceLoader ,bean也可以实现 ApplicationContextAware 接口并直接使用提供的应用程序上下文来加载资源,但一般情况下,最好使用专用的 ResourceLoader 接口,如果这就是所需要的。代码只会耦合到资源加载接口,可以将其视为实用程序接口,而不是整个Spring ApplicationContext 接口。

从Spring 2.5开始,您可以依靠自动装配 ResourceLoader 作为实现 ResourceLoaderAware 接口的替代方法。 "traditional" constructorbyType 自动装配模式(如 Section 7.4.5, “Autowiring collaborators” 中所述)现在能够分别为构造函数参数或setter方法参数提供类型 ResourceLoader 的依赖项。为了获得更大的灵活性(包括自动装配字段和多参数方法的能力),请考虑使用新的基于注释的自动装配功能。在这种情况下,只要有问题的字段,构造函数或方法带有 @Autowired 注释, ResourceLoader 将自动装入一个期望 ResourceLoader 类型的字段,构造函数参数或方法参数。有关更多信息,请参阅 Section 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 。但是如果您创建 FileSystemXmlApplicationContext 如下:

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

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

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

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

构造ClassPathXmlApplicationContext实例 - 快捷方式

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

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

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 应用程序上下文构造函数资源路径中的通配符

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

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

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

Ant 风格的图案

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

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

解析器遵循更复杂但定义的过程来尝试解析通配符。它为直到最后一个非通配符段的路径生成一个Resource,并从中获取一个URL。如果此URL不是 jar: URL或特定于容器的变体(例如,WebLogic中的 zip: ,WebSphere中的 wsjar 等),则从中获取 java.io.File 并用于通过遍历文件系统来解析通配符。对于jar URL,解析器从其获取 java.net.JarURLConnection 或手动解析jar 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");

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

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

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

与通配符有关的其他说明

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

Spring检索类路径条目的能力来自JDK的 ClassLoader.getResources() 方法,该方法仅返回传入的空字符串的文件系统位置(指示搜索的潜在根)。 Spring也会评估 URLClassLoader 运行时配置和jar文件中的 "java.class.path" 清单,但这不能保证导致可移植行为。

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

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

实际上,如果需要真正的绝对文件系统路径,最好放弃使用 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");
Updated at: 5 months ago
7.16.2. 胶水代码和邪恶的单身人士Table of content9. 验证,数据绑定和类型转换
Comment
You are not logged in.

There are no comments.