Spring核心解析—Resource与ResourceLoader接口

news2025/2/21 21:38:47

Resource你不得不知的事情

      • 前言
      • Resource内容
        • 继承结构
        • DOC解释
        • 提供的功能
        • 重要的内置Resource实现
          • UrlResource
          • ClassPathResource
          • FileSystemResource
          • ServletContextResource
          • InputStreamResource
          • ByteArrayResource
      • ResourceLoader内容
        • 结构体系
        • 源码分析
      • ResourcePatternResolver内容
        • 容器与Resolver关系

前言

我们为什么要学习Spring下面的Resource模块?
首先我们要了解什么是资源,我们把资源来自于几个方面:比如spring中xml配置文件,url网络上不同渠道的资源,自建的properties文件等,程序对于这些资源的访问,被称之为资源访问。
我们在学习spring之前,接触过一些资源访问API,比如InputStream等文件的IO,java.net.URL。
首先,它们都不是专门为Web服务设计的,对于Spring服务,这些工具比较底层,直接使用这些方法,首先要编写更多额外的代码,比如文件是否存在的判断,路径转换。
其次,他们对于访问底层资源的能力不够,没有一种URL的实现可以访问需要从类路径的资源或者相对于ServletContext获得的资源,虽然可以为专门的URL前缀注册新的处理程序(类似于现有的用于前缀的处理程序:如http:),但是比较复杂。而且URL缺少需要的一些功能,比如检查被指向的资源是否存在的方法(上面提到过,原理在于,URL表示一个统一的资源定位符,一个指向互联网上“资源”的指针,较于Resource更侧重于web资源,而Resource是实现资源的抽象,可以检查资源是否存在)。
Spring整合了获取资源的工具,统一读取例如本地文件,classpath项目路径下的文件,url互联网上的文件等不同渠道的资源,也封装了打开流,关闭流,报错处理等大量重复模板的代码,专程设计Resource接口类。

Resource内容

继承结构

在这里插入图片描述
代码如下:

public interface Resource extends InputStreamSource

InputStreamSource接口:spring核心工具包输入流接口,该接口只有一个抽象方法,每次调用这个方法都会得到一个新的流。

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

DOC解释

 * Interface for a resource descriptor that abstracts from the actual
 * type of underlying resource, such as a file or class path resource.
 *从实际类型的底层资源(例如文件或类路径资源)中抽象出来的资源描述符的接口

提供的功能

boolean exists();//返回Resource所指向的底层资源是否存在

default boolean isReadable() {  //返回当前Resource代表的底层资源是否可读
		return exists();
	}
default boolean isOpen() {
		//返回Resource资源文件是否已经打开,如果是,则只能被读取一次然后关闭以避免内存泄漏
		//常见的Resource实现一般都返回false
		return false;
	}
//如果当前Resource代表的底层资源能由java.util.URL代表,则返回URL,否则抛出io异常
	URL getURL() throws IOException;
//如果当前Resource代表的底层资源能由java.util.URI代表,则返回该URI,否则抛出io异常
	URI getURI() throws IOException;
//	如果当前Resource代表的底层资源能由java.io.File代表,则返回FIle,否则抛出io异常
	File getFile() throws IOException;
	
//返回当前Resource代表的底层资源的长度,一般是值代表的文件资源的长度。
	long contentLength() throws IOException;
	
//返回当前Resource代表的底层资源的最后修改时间。
	long lastModified() throws IOException;
	@Nullable
	//返回当前Resource代表的底层文件资源的文件路径,比如File资源“file://E:/hao/wang.txt”将返回“E:/hao/wang.txt”
	//如果是URL资源,无论地址是多少,都返回"",因为只会返回文件路径。
	String getFilename();
//返回当前Resource代表的底层资源的描述符,通常是资源的全路径(实际的文件名或实际的URL地址)
	String getDescription();

重要的内置Resource实现

先看一下Resource接口的子类都有什么:
在这里插入图片描述
我们简单看一看Spring内提供的比较重要常用的Resource实现:

  • UrlResource
  • ClassPathResource
  • FileSystemResource
  • ServletContextResource
  • InputStreamResource
  • ByteArrayResource
  • 等等。。
UrlResource

代表URL资源,用于简化URL资源访问,对java.net.URL进行了包装。该方法接受一个表示路径的字符串参数,它把不同来源的资源抽象成URL,通过注册不同的handler来处理不同来源的资源的读取逻辑。一般不同类型使用不同的前缀。
isOpen 永远返回false,表示可多次读取资源。
UrlResource应该提供标准的协议前缀,一般支持如下资源访问:

  • http:通过标准的http协议访问web资源
  • ftp:通过ftp协议访问资源
  • file:通过file协议访问本地文件系统资源
    缺点:
    UrlResource无法解决相对于classPath路径或servletContext的处理方法,因此需要其他的Resource实现类。
ClassPathResource

代表classPath路径的资源,使用ClassLoader加载资源。
其主要优势就是方便访问类加载路径下的资源,尤其是Web应用,因为它可以自动搜索位于WEB-INF/classes下的资源文件。
classPath资源位于类路径中的文件系统中或jar包里,且isOpen永远返回false,表示可多次读取资源。
ClassPathResource加载资源替代了Class类和ClassLoader类的getResource(String name)和getResourceAsStream(String name)两个加载类路径资源方法,提供一致的访问方式。
ClassPathResource提供了三个构造器:

  • public ClassPathResource(String path):使用默认的ClassLoader加载Path类路径资源;
  • public ClassPathResource(String path,ClassLoader classLoader):使用指定的ClassLoader加载“Path”类路径资源;
  • public ClassPathResource(String path,Class<?> clazz):使用指定的类加载"path"类路径资源,将加载相对于当前类的路径的资源。
FileSystemResource

代表了java.io.File资源,对于getInputStream操作将返回底层文件的字节流,isOpen将永远返回false,从而表示可以多次读取底层文件的字节流。

ServletContextResource

访问Web Context下相对路径下的资源,入参的资源位置是相对于Web应用根路径的位置(工程文件夹下,WEB-INF所在的那级文件夹)。用于简化servlet容器的ServletContext接口的getResource操作和getResourceAsStream操作。
使用ServletContextResource无需关心资源是否被解压缩出来,或者直接存放在JAR文件中,都可以通过Servlet容器访问。
接受的参数为ServletContext和字符串类型

InputStreamResource

代表java.io.InputStream字节流,对于getInputStream操作将直接返回该字节流,因此只能读取一次该字节流,即isOpen永远返回true(其他Resource大都为false可以多次读取)。
只有当没有合适Resource实现时,才考虑使用InputStreamResource。一般考虑使用ByteArrayResource。参数需要InputStream类型。
如果需要将资源描述符保存在某个地方或者需要多次读取流,不要使用。

ByteArrayResource

可以多次读取数组资源,即isOpen永远返回false
参数需要byte[]字节数组类型,用途比较广泛,可以把网络或者本地资源转换为byte[]类型,然后用ByteArrayResource转化为资源,而且不必依赖一次性使用的InputStreamResource。

ResourceLoader内容

前面我们了解到了,Spring整合获取资源的工具,就是使用Resource接口。此接口是Spring为了统一读取本地文件,类路径下面文件(ClassPath),网络等不同类型渠道的资源,封装隐藏如打开流,关闭流等大量重复模板代码,专程提供的接口类。
那么,Spring框架为了更方便的获取资源,全力弱化使用者对各个Resource接口实现类的学习与使用成本,设计目的为了快速返回(加载)Resource实例对象,定义了另一个接口,ResourceLoader接口。

结构体系

在这里插入图片描述
我们可以看见比较熟悉的内容:
1.ApplicationContext–上下文容器,这里说明高级容器(应用上下文容器)也是实现了ResourceLoader接口的,其本身就是一个ResourceLoader,所以说高级容器都可以根据资源地址类型快速获取对应的Resource实例。
2.资源获取策略的顶级接口–ResourceLoader接口
3.ResourcePatternResolver–扩展ResourceLoader,可以批量加载Resource对象。

源码分析

public interface ResourceLoader {

    //支持ClassPath:类型的路径匹配
	String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
	
    //根据路径信息返回一个对应的Resource资源实例
	Resource getResource(String location);
	
    //获取当前使用的ClassLoader,对外暴露,让ResourceLoader可以使用自定义的ClassLoader
	@Nullable
	ClassLoader getClassLoader();

}

我们可以看到,ResourceLoader接口有默认实现类——DefaultResourceLoader类
DefaultResourceLoader类实现了ResourceLoader接口加载资源的方法,同时也扩展了一些通用的基本操作方法,其中最重要的就是实现了getResource(String location)方法,我们看一下里面重要的实现方法的源码,其他不是重点,有兴趣额外了解吧:

@Override
	public Resource getResource(String location) {
		Assert.notNull(location, "Location must not be null");
          //ProtocolResolver:用户自定义协议资源解决策略,看看用户是否有提前自定义
		for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
			Resource resource = protocolResolver.resolve(location, this);
			if (resource != null) {
				return resource;
			}
		}
        //如果用户没有自定义策略,就用以下自定义资源解析策略
        //如果是“/”打头,就构造并返回ClassPathResource
		if (location.startsWith("/")) {
			return getResourceByPath(location);
		}    
		//如果以“ClassPath:”打头也会构造构造并返回ClassPathResource在构造资源时还会获取类加载器
		else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
			return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
		}
		//如果上述方式无法构造,则构造URL地址,并尝试通过URL进行资源定位,若没有就抛出异常
		//然后判断是否为FileURL,如果是就返回FileUrlResource,否则就构造UrlResource
		//若是加载的过程中抛错,委派getResourceByPath来实现资源定位和加载
		else {
			try {
				// Try to parse the location as a URL...
				URL url = new URL(location);
				return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
			}
			catch (MalformedURLException ex) {
				// No URL -> resolve as resource path.
				return getResourceByPath(location);
			}
		}
	}

我们来看一下这个return的方法

	protected Resource getResourceByPath(String path) {
		return new ClassPathContextResource(path, getClassLoader());
	}

	protected static class ClassPathContextResource extends ClassPathResource implements ContextResource {
		public ClassPathContextResource(String path, @Nullable ClassLoader classLoader) {
			super(path, classLoader);
		}
		//其他代码省略
		}

	
我们上面已经了解过了ClassPathResource(path,classLoader)使用指定的ClassLoader加载“Path”类路径资源;相信看到这里你已经明白了。

ResourcePatternResolver内容

对于ResourcePatternResolver接口来说,除了继承自Resource方法外,ResourcePatternResolver额外扩充了一个方法,就来批量加载Resource资源对象,源码如下:

public interface ResourcePatternResolver extends ResourceLoader {

	//支持classpath*:形式路径匹配,即Ant风格
	String CLASSPATH_ALL_URL_PREFIX = "classpath*:";

   //批量加载Resource资源类型的实现
	Resource[] getResources(String locationPattern) throws IOException;

}

ResourcePatternResolver接口的默认实现类——PathMatchingResourcePatternResolver类
这里全部列出没什么必要,因为PathMatchingResourcePatternResolver实例化了一个ResourceLoader,继承的ResourceLoader中方法都委托给了内部的ResourceLoader对象去处理。
对于PathMatchingResourcePatternResolver只负责实现处理ResourcePatternResolver中的方法,这里主要是实现getResources(String locationPattern)方法。
源码如下:

public class PathMatchingResourcePatternResolver implements ResourcePatternResolver {

private final ResourceLoader resourceLoader;

private PathMatcher pathMatcher = new AntPathMatcher();

public Resource[] getResources(String locationPattern) throws IOException //有点复杂,知道有这个就行,感兴趣自己查一下。

}

我们在上面可以看到,PathMatchingResourcePatternResolver引入了一个新的组件PathMatcher,
这个PathMatcher负责对基于字符串的路径和指定的模式符号进行匹配。
对于这个接口PathMatcher而言,它被翻译为路径匹配模板解析器,先用模板解析器对路径进行解析,分解成多个资源配置文件,将资源信息提供给资源加载器,后者根据不同策略将配置文件形成不同类型的资源

容器与Resolver关系

我们从Application源码中可以看到,接口继承了ResourcePatternResolver接口,也就是说,ApplicationContext本身也是一个模板解释器和资源加载器,是模板解释器的具体实现,是支持Ant风格路径匹配和批量加载资源的一个资源加载器。
在这里插入图片描述
我们来好好分析一波里面的继承关系:
ApplicationContext的抽象实现类AbstractApplicationContext在实现了ConfigurableApplicationContext的基础上,同时继承了ResourceLoader的实现类DefaultResourceLoader,如下图:
在这里插入图片描述
在这里插入图片描述
我们看上图可以发现,实现ConfigurableApplicationContext也意味着实现了ResourcePatternResolver,但是为什么要继承一个DefaultResourceLoader?显然spring设计人员不想将资源解释的逻辑直接暴露在容器中,这里将解析逻辑与容器的实现进行了解耦。
在AbstractApplicationContext的构造函数源码中可以看到,在这里将ResourcePatternResolver接口的实例——PathMatchingResourcePatternResolver实例进行了初始化,并且传入的resourceLoader实例,就是容器本身(容器继承了DefaultResourceLoader),也就是容器进行了献祭,来实现资源解释器
我们看一下源码里面内容:

public abstract class AbstractApplicationContext extends DefaultResourceLoader
		implements ConfigurableApplicationContext {
	/** ResourcePatternResolver used by this context. */
	private ResourcePatternResolver resourcePatternResolver;
	
	/**
	 * Create a new AbstractApplicationContext with no parent.
	 */
	public AbstractApplicationContext() {
		this.resourcePatternResolver = getResourcePatternResolver();
	}
	
	protected ResourcePatternResolver getResourcePatternResolver() {
		return new PathMatchingResourcePatternResolver(this);//这里传入了容器本身
	}
//省略其他代码
}

最后一张图总结一下:
在这里插入图片描述
对于AbstractApplicationContext我们需要注意几点:
1.AbstractApplicationContext因为继承了DefaultResourceLoader,所以AbstractApplicationContext本身就是resourceLoader。
2.AbstractApplicationContext实现了ResourcePatternResolver接口:实现逻辑使用PathMatchingResourcePatternResolver进行委派,PathMatchingResourcePatternResolver实例化时需要传入resourceLoader实例,就将容器本身传进去了
3.所以容器本身即是resourceLoader实例,也是resourcePatternResolver实例。

后面有精力联动一下Resource里面涉及到的装饰模式还有在项目中的用法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/14072.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

gdb调试插件的安装——gef/gdbinit/peda(记录向)

源地址&#xff1a;https://github.com/hugsy/ 根据上面的要求&#xff1a; gdb必须得8.0以上&#xff0c;python得3.6以上&#xff0c;但是一般裸机的gdb都是7.2&#xff0c;python是2.7。我们需要下载更高版本的gdb和python 先将系统自带的gdb删除&#xff1a; sudo yum r…

HMS Core手语服务荣获2022中国互联网大会“特别推荐案例”:助力建设数字社会

11月15日&#xff0c;HMS Core手语服务在2022&#xff08;第二十一届&#xff09;中国互联网大会 “互联网助力经济社会数字化转型”案例评选活动中&#xff0c;荣获“特别推荐案例”。 经过一年多的技术迭代和经验积累&#xff0c;HMS Core手语服务已与多个行业的开发者合作&a…

【算法系列】非线性最小二乘求解-直接求解法

系列文章目录 【算法系列】卡尔曼滤波算法 【算法系列】非线性最小二乘求解-直接求解法 文章目录 系列文章 文章目录 前言 算法推导 总结 前言 SLAM问题常规的解决思路有两种&#xff0c;一种是基于滤波器的状态估计&#xff0c;围绕着卡尔曼滤波展开&#xff1b;另一种则是基…

深度学习入门(三十九)计算性能——分布式训练、参数服务器(TBC)

深度学习入门&#xff08;三十九&#xff09;计算性能——分布式训练、参数服务器前言计算性能——分布式训练、参数服务器课件&#xff08;分布式训练&#xff09;分布式计算GPU机器架构计算一个小批量同步SGD性能性能的权衡实践的建议总结教材&#xff08;参数服务器&#xf…

Lazada如何申请入驻Lazmall品牌商城,会给商家带来哪些权益

Lazmall入驻标准&权益 &#xff08;1&#xff09;Lazmall申请要求:①每月至少售出1单②Seller Rating 270%③Cancellation Rate (取消率) < 2%④Ship on Time及时发货率2 90%⑤Return Rate退货率≤1%⑥店铺至少有10件在线产品⑦店铺有装修⑧店内没有OEM/NO BRAND产品 …

微服务介绍与 SpringCloud Eureka

哈喽~大家好&#xff0c;前端时间我们完结了JavaEE框架篇&#xff0c;从现在开始呢&#xff0c;开始了微服务章节了&#xff0c;这篇就来看看微服务介绍与 SpringCloud Eureka。 &#x1f947;个人主页&#xff1a;个人主页​​​​​ &#x1f948; 系列专栏&…

linux级联中断控制器的处理流程

本篇文章是通过学习韦东山老师的中断子系统相关章节而成。 这里写自定义目录标题1. 下级中断控制器的类别1.1 链式中断控制器(chained)1.2 层级中断控制器(hierarchy)2. 链式(chained)中断控制器的处理流程3. 层级(hierarchy)中断控制器的处理流程4. 处理流程对比4.1 chain的中…

linux和windwos下的磁盘扇区数据查看

目录 一、linux 二、windows 一、linux linux下一切皆文件&#xff0c;磁盘也是一种设备文件。所以我们可以直接编写 C 程序读取磁盘文件数据&#xff0c;依据扇区进行读取。 #define _GNU_SOURCE #include <stdio.h> #include <errno.h> #include <stdlib.h…

二叉树习题-小记

文章目录二叉树相同的树572. 另一棵树的子树110. 平衡二叉树101. 对称二叉树236. 二叉树的最近公共祖先JZ36 二叉搜索树与双向链表105. 从前序与中序遍历序列构造二叉树606 根据二叉树创建字符串二叉树 相同的树 相同的树 给你两棵二叉树的根节点 p 和 q &#xff0c;编写一个…

秒杀同龄人的顶级复盘能力,坚持1年,领先5年!

什么是复盘 复盘一词起源于围棋术语&#xff0c;本意是对弈者下完一盘棋之后&#xff0c;重新把过程摆一遍&#xff0c;看哪些地方下得好&#xff0c;哪些不好&#xff0c;总结经验。 在头脑中把过去做的事情“过”一遍&#xff0c;通过对过去的思维和行为进行回顾、反思和探…

使用STM32CubeMX实现LED闪烁

需提前学习&#xff1a;STM32CubeMX新建工程并点亮一个LED&#xff1b; 目录 打开GPIO相关文件 我们需要看的部分 HAL_GPIO_ReadPin&#xff08;&#xff09; HAL_GPIO_WritePin&#xff08;&#xff09; 函数声明 GPIOx GPIO_Pin PinState HAL_GPIO_TogglePin&…

538页21万字数字政府大数据云平台项目建设方案

2 项目建设需求 2.1 项目建设目标 2.2 项目建设内容 2.3 项目建设要求 2.3.1 基础设施服务建设 2.3.2 信息安全服务建设 2.3.3 运行保障服务建设 2.3.4 业务应用支撑建设 3 平台建设方案 3.1 总体建设原则 3.2 总体方案设计 3.2.1 方案设计思路 3.2.2 总体架构设计 …

Spark RDD编程模型及算子介绍(二)

文章目录常见的Action算子常见分区操作算子常见的Action算子 countByKey算子&#xff1a;统计Key出现的次数&#xff0c;部分代码如下&#xff1a; rdd_file sc.textFile("../Data/input/words.txt") rdd_map rdd_file.flatMap(lambda line: line.split(" &…

Mybatis拦截器源码详解

Mybatis拦截器源码详解Mybatis相关全览一、简介执行与添加顺序拦截器生效入口二、使用例子三、原理加载入口生成代理遍历拦截器匹配&生成代理四、实践例子本文用的是3.5.10版本 源码地址&#xff1a;https://github.com/mybatis/mybatis-3/releases 文档地址&#xff1a;ht…

【云原生监控系列第三篇】Prometheus普罗米修斯进阶——PromQL的指标类型

目录一、PromQL 的指标类型1.1 Counter1.2 Gauge1.3 Histogram1.4 Summary1.5 Histogram 与 Summary 的异同二、Prometheus 的聚合函数三、PromQL 的聚合表达式一、PromQL 的指标类型 PromQL 有四个指标类型&#xff1a; Counter &#xff1a;计数器&#xff0c;用于保存单调递…

医疗产品设计的重要性,你了解多少?

医疗产品设计直接关系患者生活的方式与治疗&#xff0c;一个好的医疗产品设计不但要逐渐细化、便于实际操作&#xff0c;而且还要有利于医师操纵&#xff0c;让患者觉得舒服。这是一种具备重大意义的产品。让我们一起来看看有关医疗产品设计的具体内容! 什么叫医疗产品设计? 医…

【Java技术专题】「原理分析系列」Lambda表达式实现原理分析

Lamdba表达式起源 java8引入了lambda表达式是我们java编程方式变革的一个伟大的创举&#xff0c;由了它我们可以采用闭包的形式区开发任何想开发的方法&#xff0c;让java程序与C或者C更加有了贴合的感觉&#xff0c;虽然编程方式和我们目前的命令式编程方式有很大的不同&#…

仿真必修课:计算电磁学入门(附件参考文献与笔记)

转载自电磁CAEer &#xff0c;作者&#xff1a;刘兵 “作为一个电磁设计师&#xff0c;有必要了解计算电磁学吗&#xff1f;” 答案是肯定的。电磁计算从业人员按照分工大致可以分为两类&#xff1a;一类从事CEM&#xff08;计算电磁学&#xff09;&#xff0c;一类从事CAE&a…

ROS 开源项目 TurtleBot3 安装与使用

功能介绍 启动slam完成地图的搭建与保存启动navigation并读取保存的地图&#xff0c;完成自动导航。 注&#xff1a;人工咨询 如果按照下面方案也无法成功解决&#xff0c;可以进入我淘宝咨询&#xff0c;可进行远程辅助解决。 1、安装部分 1.1 创建工作空间lee_ws mkdir…

java项目-第140期ssm高校二手交易平台-ssm毕业设计_计算机毕业设计

java项目-第140期ssm高校二手交易平台-ssm毕业设计_计算机毕业设计 【源码请到资源专栏下载】 今天分享的项目是《ssm高校二手交易平台》 该项目分为2个角色&#xff0c;管理员和用户。 用户在前台浏览商品&#xff0c;并且可以进行购买。用户购买后可以在后台查看自己的订单等…