9

Spring 5 中文解析核心篇:IoC 容器之 Resources

 3 years ago
source link: https://mp.weixin.qq.com/s?__biz=MzA3NDgzODYzNg%3D%3D&%3Bmid=2247494467&%3Bidx=1&%3Bsn=88af80780674f168f8c1a9a997969575&%3Bchksm=9f7b0c40a80c85563fe4116c036bba3ba8603553120b47c1d8767b94627e9dcdbdbed695af65&%3Btoken=1182627745&%3Blang=zh_CN
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

精品推荐:

这个章节涵盖了Spring怎样处理和在Spring中使用资源文件。包括下面主题:

  • 介绍

  • Resource接口

  • 内建Resource实现

  • ResourceLoader

  • ResourceLoaderAware接口

  • Resource作为依赖

  • 应用上下文和Resource路径

2.1 介绍

Java的标准 java.net.URL 类和标准处理URL前缀变体,不幸地,对于所有访问低级资源的能力还不够。例如,这里没有需要从类路径或相关联的 ServletContext 获取资源使用的标准URL实现。当然也可以注册新的处理器为特定URL前缀(类似已经存在的前置处理器,例如: http: ),通常这是十分的复杂,并且URL接口仍然缺乏一些功能描述,例如一种检查所指向资源是否存在的方法。

2.2 Resource接口

Spring的 Resource 接口意思是为抽象获取低级别资源提供更多的能力。下面清单显示了 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();
}

Resource 接口定义显示这样,它拓展了 InputStreamSource 接口。下面清单显示 InputStreamSource 接口的定义:

public interface InputStreamSource {
    InputStream getInputStream() throws IOException;
}

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

  • getInputStream() :定位和打开资源并且返回从资源中读取的 InputStream 。每次调用都返回一个新的 InputStream 。我们有责任去关闭流。

  • exists() :任何一个 boolean 指示是否这个资源在物理路径存在。

  • isOpen() :然后一个 boolean 指示此资源是否代表打开流的句柄。如果为 true InputStream 不能被多次读取且只能读取一次,而且需要关闭资源避免被泄露。对于所有常规资源实现,返回 false ,但 InputStreamResource 除外。

  • getDescription() :返回这个资源的描述,以在使用该资源时用于错误输出。这通常是标准文件名或资源的实际URL。

其他方法允许你获取一个真实URL或FIle对象描述资源(如果底层实现是兼容和支持该功能)。

当需要 资源 时,Spring自身广泛地使用 Resource 抽象,在许多方法签名上参数类型。一些Spring API中的其他方法(例如,各种 ApplicationContext 实现的构造函数)采用String形式,该字符串以未经修饰或简单的形式用于创建适合该上下文实现的 Resource ,或者通过String路径上的特殊前缀,让调用者指定必须创建并使用特定的资源实现。

虽然 Resource 接口在Spring中被大量使用,但是在你自己的代码中作为一个通用的实用程序类来使用它实际上是非常有用的,以便访问资源,即使你的代码不知道或不关心Spring的任何其他部分。虽然这个耦合Spring到你的代码,它仅仅耦合非常小的工具类集合,可以用作URL的更强大替代,并且可以认为与你将用于此目的的任何其他库等效。

Resource 抽象不能替换功能。它尽可能地包装它。例如, UrlResource 包装URL和使用包装的URL去工作。

2.3 内建的Resource实现

Spring包含下面的 Resource 实现:

  • UrlResource

  • ClassPathResource

  • FileSystemResource

  • ServletContextResource

  • InputStreamResource

  • ByteArrayResource

2.3.1   UrlResource

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

UrlResource 是由Java代码通过显式使用 UrlResource 构造函数创建的,但通常在调用带有String参数表示路径的API方法时隐式创建。对于后一种情况,JavaBean   PropertyEditor 最终决定哪一种 Resource 类型被创建。如果字符串包含我们所知的前缀(例如   classpath: ),它创建一个适当的指定前缀的 Resource 。然而,如果不能识别前缀,假设字符串是标准的URL字符串并创建一个 UrlResource

2.3.2   ClassPathResource

这个类代表一个能够从类路径获取的资源。它使用线程上下文类加载器、给定的类加载器或给定的类来加载资源。

这个资源实现支持将解析作为 java.io.File 。如果类路径资源驻留在文件系统中,而不是类路径资源驻留在jar中,并且没有(由servlet引擎或其他环境)扩展到文件系统。为了解决这个问题,各种 Resource 实现始终支持将解析作为 java.net.URL 进行。

当你采用一个String参数描述路径调用API方法时, ClassPathResource 是通过显示使用 ClassPathResource 构造函数通过Java代码创建,但是通常隐式地被创建。对于后一种情况,javabean   PropertyEditor 识别在字符串路径中指定前缀 classpath: 并且在这个场景中创建一个 ClassPathResource

2.3.3   FileSystemResource

这是 java.io.File java.nio.file.Path 句柄的 Resource 实现。它支持解析作为File和URL。

2.3.4   ServletContextResource

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

它始终支持流访问和URL访问,但仅在扩展Web应用程序实现被扩展和资源在实际文件系统上时才允许 java.io.File 访问。它是在文件系统上扩展还是直接扩展,或者直接从JAR或其他类似数据库(可以想到的)中访问,实际上取决于Servlet容器。

2.3.5   InputStreamResource

InputStreamResource 是一个给定 InputStream Resource 实现。仅当没有特定的资源实现时才应使用它。特别是,在可能的情况下,最好选择 ByteArrayResource 或任何基于文件的 Resource 实现。

对比其他的 Resource 实现,这是一个对于已经打开的资源的描述。因此, isOpen() 方法返回 true 。如果你需要将资源描述符保留在某个地方或需要多次读取流,请不要使用它。

2.3.6   ByteArrayResource

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

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

参考代码: com.liyong.ioccontainer.starter.ResourceIocContainer

2.4   ResourceLoader

ResourceLoader 接口是由可以返回(即加载)资源实例的对象实现的。下面清单显示 ResourceLoader 定义:

public interface ResourceLoader {
    Resource getResource(String location);
}

所有的应用上下文实现 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");

类似地,你可以通过指定标准的 java.net.URL 前缀强制使用 UrlResource 。下面两个例子使用 file http 前缀:

Resource template = ctx.getResource("file:///some/resource/path/myTemplate.txt");
Resource template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt");

下面表格总结对于转换String对象到 Resource 对象的策略 :

Prefix Example Explanation classpath: classpath:com/myapp/config.xml 类路径加载 file: file:///data/config.xml 作为URL资源从文件系统加载   FileSystemResource Caveats . http: https://myserver/logo.png 作为URL加载 (none) /data/config.xml 依赖底层 ApplicationContext

2.5   ResourceLoaderAware 接口

ResourceLoaderAware 接口是一个特殊的回调接口,它表示希望使用 ResourceLoader 引用提供的组件。下面清单显示 ResourceLoaderAware 接口定义:

public interface ResourceLoaderAware {
    void setResourceLoader(ResourceLoader resourceLoader);
}

当一个类实现 ResourceLoaderAware 并且被部署到应用上下文中(作为Spring管理的bean),它应用上下文作为 ResourceLoaderAware 被识别。应用上下文调用 setResourceLoader(ResourceLoader) ,应用自身作为参数(记住,Spring中的所有应用程序上下文都实现 ResourceLoader 接口)。

因为 ApplicationContext ResourceLoader ,bean也可以实现 ApplicationContextAware 接口并且使用使用应用上下文直接地加载资源。但是,通常,如果需要的话,最好使用专门的 ResourceLoader 接口。该代码将仅耦合到资源加载接口(可以视为实用程序接口),而不耦合到整个Spring   ApplicationContext 接口。

在应用中的组件,你也可以依赖注入 ResourceLoader 作为实现 ResourceLoaderAware 接口替代方案。“传统”构造函数和 byType 自动装配模式(如“ 自动装配协作器 ”中所述)能够分别为构造函数参数或setter方法参数提供 ResourceLoader 。为了更大的灵活性(包含自动装配字段和多参数方法),考虑使用基于注解的特性。在这种情况下, ResourceLoader 自动装配到字段、构造函数或方法参数、字段、构造函数或方法携带有 @Autowired 注解,就会期望 ResourceLoader 类型。更多消息,查看 @Autowired 使用。

2.6 依赖Resources  

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

然后注入这些属性的麻烦之处在于,所有应用程序上下文都注册并使用了特殊的JavaBeans   PropertyEditor ,它可以将String路径转换为 Resource 对象。如果 myBean 有一个 Resource 类型模版属性,它可以为资源配置一个简单的字符串,类似下面例子显示:

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

注意,资源路径没有前缀。因此,由于应用程序上下文本身将被用作ResourceLoader,所以资源本身是通过ClassPathResource、FileSystemResource或ServletContextResource加载的,这取决于上下文的确切类型。

如果你需要强制指定使用的Resource类型,你可以使用前缀。下面两个例子显示怎样去强制使用ClassPathResource和UrlResource(后面这个例子使用文件系统获取):

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

参考代码: com.liyong.ioccontainer.starter.Resource2IocContainer

2.7 应用上下文和资源路径

这个章节涵盖怎样创建应用上下文与资源,包括与XML一起使用的快捷方式,如何使用通配符以及其他详细信息

2.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前缀会覆盖为加载定义而创建的默认资源类型。

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

使用 FileSystemXmlApplicationContext 从类路径加载bean定义。然而,它仍然是一个 FileSystemXmlApplicationContext 。如果随后将其用作 ResourceLoader ,则任何无前缀的路径仍将视为文件系统路径。

构造 ClassPathXmlApplicationContext 实例-快捷方式

ClassPathXmlApplicationContext 暴露许多构造方法去实例化。基本思想是,你只提供一个字符串数组,该字符串数组仅包含XML文件本自身的文件名(不包含前导路径信息),并且还提供一个Class。然后, ClassPathXmlApplicationContext 从提供的类中获取路径信息。

考虑下面路径布局:

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

以下示例显示如何实例化由在名为 service.xml daos.xml (位于类路径中)的文件中定义的bean组成的 ClassPathXmlApplicationContext 实例:

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

查看 ClassPathXmlApplicationContext 详细javadock的各种构造。

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

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

这种机制使用之一是当你需要组件风格封装应用时。所有组件都可以将上下文定义片段“发布”到一个已知的位置路径,并且,当使用以 classpath*: 作为前缀的相同路径创建最终的应用程序上下文时,所有组件片段都会被自动获取。

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

Ant风格模式

路径位置可以包含Ant风格模式,类似下面例子显示:

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

当路径位置包含一个Ant风格模式时,解析器允许更复杂程序尝试去解析通配符。它为最后一个非通配符段的路径生成一个资源,并从中获得一个URL。如果这个URL不是 jar: URL或包含特定变体(例如,在WebLogic中的 zip: 、在WebSphere中的wsjar等等),从中获得一个 java.io.File ,并通过遍历文件系统来解析通配符。对于jar URL,解析器可以从中获取 java.net.Jar   URLConnection ,也可以手动解析jar URL,然后遍历jar文件的内容以解析通配符。

影响可移植性

如果指定的路径已经是一个文件URL(由于基本ResourceLoader是一个文件系统,所以它是隐式的,或者是显式的),则保证通配符可以完全可移植的方式工作。

如果指定的路径是类路径位置,则解析器必须通过调用 Classloader.getResource() 获得最后的非通配符路径段URL。由于这只是路径的一个节点(而不是末尾的文件),因此实际上(在 ClassLoader   javadoc中)未定义确切返回的是哪种URL。。在实践中,它总是一个 java.io.File 描述目录(类路径资源解析为文件系统的位置)或一个一些种类(类路径资源解析为jar位置)jar URL。尽管如此,此操作仍存在可移植性问题。

如果为最后一个非通配符段获取了jar URL,则解析器必须能够从中获取 java.net.Jar   URLConnection 或手动解析jar URL,以便能够遍历jar的内容并解析通配符。这在大多数环境中确实有效,但在其他环境中则无效,因此我们强烈建议在依赖特定环境之前,对来自jars的资源的通配符解析进行彻底测试。

classpath*: 前缀

当构造基于XML应用上下文时,位置字符串可能使用指定的 classpath*: 前缀,类似下面例子显示:

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

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

通配符类路径依赖类加载器底层的 getResources() 方法。由于当今大多数应用程序服务器都提供自己的类加载器实现,因此行为可能有所不同,尤其是在处理jar文件时。检查 classpath * 是否有效的一个简单测试是使用 classloader classpath 的jar中加载文件:

getClass().getClassLoader().getResources("<someFileInsideTheJar>") 。尝试对具有相同名称但位于两个不同位置的文件进行此测试。如果返回了不合适的结果,请检查应用程序服务器文档中可能会影响类加载器行为的设置。

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

通配符相关的其他注意

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

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

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

在JDK9的模块路径上,Spring的类路径扫描通常可以正常进行。强烈建议在此处将资源放入专用目录,以避免在搜索jar文件根目录级别时出现上述可移植性问题。

具有 classpath: 的Ant样式模式:如果要搜索的根包在多个类路径位置可用,不能保证找到匹配的资源。考虑下面资源为主例子:

com/mycompany/package1/service-context.xml

现在考虑一个ant样式的路径,有人可能会使用它来尝试查找该文件Ant格式路径

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

这些资源可能只在一个位置,但是当使用诸如前面示例的路径尝试对其进行解析时,解析器将处理 getResource( "com/mycompany”) 返回的(第一个)URL。如果这个基础包节点在多个类加载器路径中存在,实际的最终资源可能不存在。因此,在这种情况下,你应该首选使用具有相同Ant样式模式的 classpath *: ,该模式将搜索包含根包的所有类路径位置。

2.7.3   FileSystemResource 注意事项

未附加到 FileSystemApplicationContext FileSystemResource (即,当 FileSystemApplicationContext 不是实际的 ResourceLoader 时)将按你期望的方式处理绝对路径和相对路径。相对路径是相对于当前工作目录的,而绝对路径是相对于文件系统的根的。

但是,出于向后兼容性(历史)的原因,当 FileSystemApplicationContext ResourceLoader 时,情况会发生变化。 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: 前缀的 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");

精品推荐: 

作者

个人从事金融行业,就职过易极付、思建科技、某网约车平台等重庆一流技术团队,目前就职于某银行负责统一支付系统建设。自身对金融行业有强烈的爱好。同时也实践大数据、数据存储、自动化集成和部署、分布式微服务、响应式编程、人工智能等领域。同时也热衷于技术分享创立公众号和博客站点对知识体系进行分享。

博客地址: http://youngitman.tech

CSDN: https://blog.csdn.net/liyong1028826685

微信公众号:

fuaAjqy.jpg!mobile

如果觉得文章对您有所帮助请点击文中广告支持作者哟


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK