本文内容

  1. Resource接口的定义
  2. Resource接口的内置实现
  3. ResourceLoader接口
  4. ResourceLoaderAware 接口

Resource接口的定义

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

针对上述这种情况,Spring 提供了更强大的接口Resource用于对低级资源的抽象访问,其定义和主要接口方法说明如下。

  1. package org.springframework.core.io;
  2. public interface Resource extends InputStreamSource {
  3. // 特定资源是否存在
  4. boolean exists();
  5. // 内容非空可通过#getInputStream()读取
  6. default boolean isReadable() {
  7. return exists();
  8. }
  9. // 资源对应的InputStream是否已经打开,不能多次读取,应读取后关闭避免资源泄漏
  10. default boolean isOpen() {
  11. return false;
  12. }
  13. // 是否是 File 类型,配合 #getFile()
  14. default boolean isFile() {
  15. return false;
  16. }
  17. // 获取 URL
  18. URL getURL() throws IOException;
  19. // 获取URI
  20. URI getURI() throws IOException;
  21. // 获取 File
  22. File getFile() throws IOException;
  23. // 资源内容长度
  24. long contentLength() throws IOException;
  25. // 上次修改的时间戳
  26. long lastModified() throws IOException;
  27. // 给定路径创建资源
  28. Resource createRelative(String relativePath) throws IOException;
  29. // 获取文件名非全路径
  30. @Nullable
  31. String getFilename();
  32. // 获取资源描述
  33. String getDescription();
  34. }

Resource接口继承了InputStreamSource,其定义如下。

  1. package org.springframework.core.io;
  2. public interface InputStreamSource {
  3. // 获取资源对应的 InputStream
  4. InputStream getInputStream() throws IOException;
  5. }

Resource接口并不是用于完全取代java.net.URL,而是尽可能地通过其实现类来包装URL进行处理,如UrlResource 包装一个 URL 并使用包装的 URL 来完成它的的功能。具体看下一节的内置实现。

Resource接口的内置实现

UrlResource

UrlResource 包装了 java.net.URL,可用于访问通常可通过 URL 访问的任何对象,例如文件、HTTP 目标、FTP 目标等。这些资源通过标准前缀来区分,file:用于访问文件系统路径,http:用于通过 HTTP 协议访问资源,ftp:用于通过 FTP 访问资源等。

ClassPathResource

ClassPathResource表示应从类路径获取的资源。它使用线程上下文类加载器、给定的类加载器或给定的类来加载资源。可通过特定前缀classpath:指定为该资源。

如果类路径资源驻留在文件系统中,则此 Resource 实现支持解析为 java.io.File,但不支持在 jar 包中且尚未解压的类路径资源。

FileSystemResource

这是 java.io.Filejava.nio.file.Path 句柄的资源实现。它支持作为FileURL 的解析。

ServletContextResource

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

它始终支持流访问和 URL 访问,但仅在扩展 Web 应用程序存档并且资源物理位于文件系统上时才允许 java.io.File 访问。它是否被解压并在文件系统上或直接从 JAR 或其他地方(如数据库)访问实际上取决于 Servlet 容器。

ByteArrayResource

给定字节数组的资源实现。它为给定的字节数组创建一个 ByteArrayInputStream。对于从任何给定的字节数组加载内容很有用,而不必求助于单一使用的 InputStreamResource

InputStreamResource

InputStreamResource 是给定 InputStream 的资源实现。首选 ByteArrayResource 或任何基于文件的 Resource 实现,仅当没有特定的 Resource 实现适用时才应使用它。

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

ResourceLoader接口

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

  1. package org.springframework.core.io;
  2. public interface ResourceLoader {
  3. /** Pseudo URL prefix for loading from the class path: "classpath:". */
  4. String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
  5. // 返回指定资源位置的资源句柄
  6. Resource getResource(String location);
  7. // 获取 ClassLoader
  8. // 需要直接访问 ClassLoader 的客户端可以通过 ResourceLoader 统一的方式进行访问,而不是依赖于线程上 下文 ClassLoader。
  9. @Nullable
  10. ClassLoader getClassLoader();
  11. }

源码中对于的说明:

  • 句柄应始终是可重用的资源描述符,允许多个 Resource.getInputStream() 调用。
  • 必须支持完全限定的 URL,例如“file:C:/test.dat”。
  • 必须支持类路径伪 URL,例如“classpath:test.dat”。
  • 应该支持相对文件路径,例如“WEB-INF/test.dat”。 (这将是特定于实现的,通常由 ApplicationContext 实现提供。)
  • 资源句柄并不意味着现有资源;需要调用 Resource.exists() 来检查是否存在。

所有 org.springframework.context.ApplicationContext都必须实现该ResourceLoader接口。调用getResource()接口的返回值类型取决于当前context的类型,当前context会自动转换。如下案例。

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

ctxClassPathXmlApplicationContext返回ClassPathResource

ctxFileSystemXmlApplicationContext返回FileSystemResource

ctx是WebApplicationContext返回 ServletContextResource

如果不想依赖context类型决定返回资源的类型,可以指定前缀的方式,强制返回特定类型的资源。如下案例。

  1. Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");
  2. // 返回 ClassPathResource
  1. Resource template = ctx.getResource("file:///some/resource/path/myTemplate.txt");
  2. Resource template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt");
  3. // 返回 UrlResource

指定前缀对于资源加载方式和返回的影响对应关系如下表。

前缀 例子 如何解析
classpath: classpath:com/myapp/config.xml 从类路径加载
file: `file:///data/config.xml 从文件系统作为 URL 加载
http: https://myserver/logo.png 从文件系统作为 URL 加载
(none) /data/config.xml 取决于底层的 ApplicationContext

ResourceLoaderAware 接口

ResourceLoaderAware 接口是一个特殊的回调接口,它标识期望提供 ResourceLoader 引用的组件。其定义如下。

  1. package org.springframework.context;
  2. public interface ResourceLoaderAware extends Aware {
  3. // 可自定义保存一个ResourceLoader的引用来进行资源加载
  4. void setResourceLoader(ResourceLoader resourceLoader);
  5. }

由于 org.springframework.context.ApplicationContext实现了 ResourceLoader,因此 bean 还可以实现 ApplicationContextAware 接口并直接使用提供的应用程序上下文来加载资源。从面向接口编程和解耦的角度来说,需要ResourceLoader的来进行资源加载,更推荐实现``ResourceLoaderAware 接口。

深一层思考,如果容器中的一个bean需要一个ResourceLoader依赖用于加载资源,除了实现ResourceLoaderAware,是否还有其它方式呢?

ResourceLoader实例会自动注入到IoC容器,我们可通过构造函数、setter方法、@Autowire注解等方式,直接注入该依赖,具体看一看之前Ioc相关的文章。

配置文件当做Resource 注入到bean依赖中

如果某个bean依赖于特定的Resource,那么我们如何快速优雅地注入该依赖呢?直接上代码。

  1. public class MyConfig {
  2. /**
  3. * 通过配置文件注入的资源
  4. */
  5. private Resource template;
  6. public Resource getTemplate() {
  7. return template;
  8. }
  9. public void setTemplate(Resource template) {
  10. this.template = templa
  11. }
  12. }

MyConfig依赖一个类文件路径下的一个配置文件。

对应的xml文件spring.xml和配置文db.properties件如下:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  5. <bean id="myConfig" class="com.crab.spring.ioc.demo02.MyConfig">
  6. <property name="template" value="db.properties"></property>
  7. </bean>
  8. </beans>
  1. url=jdbc:mysql://localhost/test
  2. username=root
  3. password=456
  4. max=1024

运行主程序如下:

  1. package com.crab.spring.ioc.demo02;
  2. import org.springframework.context.support.ClassPathXmlApplicationContext;
  3. import org.springframework.core.io.ClassPathResource;
  4. import org.springframework.core.io.Resource;
  5. public class ResourceTest {
  6. public static void main(String[] args) {
  7. ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring" +
  8. ".xml");
  9. MyConfig myConfig = context.getBean(MyConfig.class);
  10. Resource template = myConfig.getTemplate();
  11. System.out.println(template);
  12. System.out.println(template instanceof ClassPathResource);
  13. }
  14. }

运行结果:

  1. class path resource [db.properties]
  2. true
  3. class path resource [db.properties]
  • 成功注入了Resourece资源到MyConfig的bean中
  • 注入Resource实际是我们通过ClassPathXmlApplicationContext加载的ClassPathResource

扩展:结合上一篇的资源特定前缀和ApplicationContext的关系,忘了请翻看下,我们也可以指定前缀来加载特定的资源。较为简单就不演示了。

  1. <bean id="myConfig" class="com.crab.spring.ioc.demo02.MyConfig">
  2. <property name="template" value="classpath:db.properties"></property>
  3. </bean>file:///some/resource/path
  1. <bean id="myConfig" class="com.crab.spring.ioc.demo02.MyConfig">
  2. <property name="template" value="file:///some/resource/path"></property>
  3. </bean>
  4. // 注入的是FileSystemResource

使用资源路径配置应用上下文

回顾一下,ResourceLoader章节,我们分析了指定资源路径前缀对于资源加载方式的影响,对应关系如下表。下面将分带前缀和不带前缀的方式来配置应用上下文。

前缀 例子 如何解析
classpath: classpath:com/myapp/config.xml 从类路径加载
file: `file:///data/config.xml 从文件系统作为 URL 加载
http: https://myserver/logo.png 从文件系统作为 URL 加载
(none) /data/config.xml 取决于底层的 ApplicationContext
资源路径不带前缀
  1. ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");

上面这个案例资源路径不带前缀,ClassPathXmlApplicationContext将使用ClassPathResource加载在类路径下的资源文件。再来一个案例。

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

FileSystemXmlApplicationContext将使用URLResouce来加载文件系统中的配置文件,这里案例是相对路径。

资源路径带前缀

当指定了资源前缀,则使用指定前缀对应的Resource来加载资源。如下案例。

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

对比上一个案例,此处将使用ClassPathResource加载在类路径下的资源文件。

资源路径带前缀和通配符

指定单一的资源文件通过前面2种方式,若需要指定多个资源则可以考虑使用通配符,支持Ant-styleclasspath*

  1. Ant-style通配符

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

    当使用Ant-style通配符,解析器遵循更复杂的过程来尝试解析通配符。它为直到最后一个非通配符段的路径生成一个资源,并从中获取一个 URL。它为直到最后一个非通配符段的路径生成一个资源,并从中获取一个 URL

    最后一个非通配符段的路径分2种情况处理:

    • 普通的文件路径: 生成java.io.File,如classpath:com/mycompany/**/applicationContext.xml取到classpath:com/mycompany,然后进行遍历。

    • 特殊的文件jar: 路径:如classpath:com/mycompany/**/applicationContext.xml,取到classpath:com/mycompany ,将生成 java.net.JarURLConnection,或是手动解析 jar URL然后遍历 jar 文件的内容来解析通配符。

      使用 jar URL强烈建议测试下是否能正常通过通配符访问到资源

  2. classpath*前缀

    如下案例

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

    遍历类路径下所有匹配到conf/appContext.xml的资源都会加载。

  3. 两种方式可以组合使用

    classpath*:META-INF/*-beans.xml

    解决策略相当简单:在最后一个非通配符路径段上使用 ClassLoader.getResources() 调用来获取类加载器层次结构中的所有匹配资源,然后对于每个资源,PathMatcher 解析策略用于通配符子路径。感兴趣的可以了解下PathMatcher 类。

    组合使用容易掉坑的2个地方,请注意:

    • classpath*:*.xml

      可能不会从 类路径下的jar 文件的根目录中检索文件,而只能从扩展目录的根目录中检索文件。

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

      如果要搜索的根包在多个类路径位置中可用,则不保证资源能够找到匹配的资源。 使用ClassLoader#getResource(com/mycompany/)如果多个路径有,则可能只返回第一个,其它的漏掉了。保险的做法是classpath*:com/mycompany/**/service-context.xml

彩蛋

出于向后兼容的历史原因, FileSystemXmlApplicationContext关联的FileSystemResource会将所有的资源的非前缀路径统一当做相对路径。上案例

  1. // 2种写法是一样的
  2. ApplicationContext ctx =new FileSystemXmlApplicationContext("conf/context.xml");
  3. ApplicationContext ctx =new FileSystemXmlApplicationContext("/conf/context.xml");
  4. // 2种写法是一样的
  5. ctx.getResource("some/resource/path/myTemplate.txt");
  6. ctx.getResource("/some/resource/path/myTemplate.txt");
  7. // 可以强制不按相对路径处理不,可以的!按URLResource处理
  8. ctx = new FileSystemXmlApplicationContext("file:///conf/context.xml");

总结

本文主要详细说明了

  1. Resource接口的定义和方法说明
  2. Resource接口的内置6种实现
  3. ResourceLoader接口和实现
  4. ResourceLoaderAware 接口使用
  5. 如何使用Resource配置ApplicationContext

这一篇下来,基本Spring关于资源处理和相关接口总体是比较清晰了。

知识分享,转载请注明出处。学无先后,达者为先!

Spring系列18:Resource接口及内置实现的更多相关文章

  1. 流程自动化RPA,Power Automate Desktop系列 - 不讲武德的Windows10内置应用

    简介 Power Automate Desktop 扩展Power Automate中的现有机器人流程自动化(RPA)功能,并使您能够自动化所有重复的桌面流程. 使用预生成的拖放操作或记录您自己的桌面 ...

  2. Spring Cloud Gateway实战之五:内置filter

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  3. 【Android】18.1 利用安卓内置的定位服务实现位置跟踪

    分类:C#.Android.VS2015: 创建日期:2016-03-04 一.安卓内置的定位服务简介 通常将各种不同的定位技术称为位置服务或定位服务.这种服务是通过电信运营商的无线电通信网络(如GS ...

  4. Spring Boot 添加jersey-mvc-freemarker依赖后内置tomcat启动不了解决方案

    我在我的Spring Boot 项目的pom.xml中添加了jersey-mvc-freemarker依赖后,内置tomcat启动不了. 报错信息如下: org.springframework.con ...

  5. .net core系列之《.net core内置IOC容器ServiceCollection》

    一.IOC介绍 IOC:全名(Inversion of Control)-控制反转 IOC意味着我们将对象的创建控制权交给了外部容器,我们不管它是如何创建的,我们只需要知道,当我们想要某个实例时,我们 ...

  6. Spring Boot2.0之 原理—创建内置Tomcat容器

    前面所述的https://www.cnblogs.com/toov5/p/9823728.html 中的第一条先不赘述了,就是玩了maven 重点介绍后两条 首先内置Tomcat: SpringBoo ...

  7. Spring Cloud Gateway实战之四:内置predicate小结

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  8. python学习 day12 (3月18日)----(装饰器内置函数)

    读时间函数: # import time # def func(): # start_time = time.time() # 代码运行之前的时间 # print('这是一个func函数') # ti ...

  9. $python正则表达式系列(3)——正则内置属性

    本文主要总结一下python正则的一些内置属性的用法. 1. 编译标志:flags 首先来看一下re.findall函数的函数原型: import re print('[Output]') print ...

随机推荐

  1. Go语言系列之标准库strconv

    Go语言中strconv包实现了基本数据类型和其字符串表示的相互转换. strconv包 strconv包实现了基本数据类型与其字符串表示的转换,主要有以下常用函数: Atoi().Itia().pa ...

  2. Mysql设计遵循规则

    为什么要优化系统的吞吐量瓶颈往往出现在数据库的访问速度上随着应用程序的运行,数据库的中的数据会越来越多,处理时间会相应变慢数据是存放在磁盘上的,读写速度无法和内存相比 如何优化设计数据库时:数据库表. ...

  3. 自旋锁-JUC系列

    公众号原文:自旋锁-JUC系列 前言 2022!这个年份现在看起来都觉得有那么些恍惚的未来感,然而现在已在脚下. 无边落木萧萧下, 不尽长江滚滚来! 人生如白驹过隙! 本来计划最近把AQS源码分析做了 ...

  4. LaTex 中圆圈序号及一些特殊字符的输入

    众所周知,LATEX 提供了 \textcircled 命令用以给字符加圈,但效果却不怎么好: 实际上,加圈并不是一个平凡的变换,它会涉及到圈内字符形状的微调,而这是几乎无法在 TEX 宏层面解决的. ...

  5. JSP页面使用EL表达式不显示实际数据

    今天在学习有关jsp的相关知识内容时,遇到了el表达式只是显示括号里面的内容 代码如下: <%@ page contentType="text/html;charset=UTF-8&q ...

  6. Cesium中文网的朋友们

    目前已开通知识星球-Cesium中文网的朋友们 注意:仔细思量好再进来,一旦付费,概不退费.下述内容均尽力而为. 1. 每月一次技术交流(Cesium为主),提供源码. 2. 每人5次/周免费提问(C ...

  7. 初识WorldWind——WorldWind编译生成,解决乱码等问题

    本文中World Wind的GitHub源码下载地址:https://github.com/hujiulin/WorldWind 美国国家航空航天局(National Aeronautics and ...

  8. Qt之QFontDialog

    widget.h: #ifndef WIDGET_H #define WIDGET_H #include <QWidget> class Widget : public QWidget { ...

  9. gin框架的热加载方法

    gin是用于实时重新加载Go Web应用程序的简单命令行实用程序.只需gin在您的应用程序目录中运行,您的网络应用程序将 gin作为代理提供.gin检测到更改后,将自动重新编译您的代码.您的应用在下次 ...

  10. String类(获取,转换,判断,比较)

    1 package cn.itcast.p1.string.demo; 2 3 import java.util.Iterator; 4 5 import com.sun.org.apache.xpa ...