三.重新回炉Spring Framework:Resource资源加载策略

1. Spring Framework中的资源Resource

1.1 Resource的源码

在org.springframework.core.io包中的Resource接口,作为所有资源的统一抽象,它继承 org.springframework.core.io.InputStreamSource接口,在Resource 定义了一些通用的方法,由子类 AbstractResource 提供统一的默认实现。Resource 的代码如下:

public interface Resource extends InputStreamSource {

	/**
	 * 资源是否存在
	 */
	boolean exists();

	/**
	 * 资源是否可读
	 */
	default boolean isReadable() {
		return exists();
	}

	/**
	 * 指示此资源是否表示具有开放流的句柄。如果为true,则不能多次读取InputStream,
	 * 必须读取并关闭InputStream以避免资源泄漏。对于典型的资源描述符,将为false。
	 */
	default boolean isOpen() {
		return false;
	}

	/**
	 * 是否 File
	 */
	default boolean isFile() {
		return false;
	}

	/**
	 * 返回资源的 URL
	 */
	URL getURL() throws IOException;

	/**
	 * 返回资源的 URI
	 */
	URI getURI() throws IOException;

	/**
	 * 返回资源的 File
	 */
	File getFile() throws IOException;
	
	default ReadableByteChannel readableChannel() throws IOException {
		return Channels.newChannel(getInputStream());
	}

	/**
	 * 资源内容的长度
	 */
	long contentLength() throws IOException;

	/**
	 * 资源的最后修改时间
	 */
	long lastModified() throws IOException;

	/**
	 * 根据相对路径创建资源
	 */
	Resource createRelative(String relativePath) throws IOException;

	/**
	 * 确定此资源的文件名,即通常路径的最后一部分:例如“myfile.txt”。如果此类型的资源没有文件名,则返回null
	 */
	@Nullable
	String getFilename();

	/**
	 *  资源的描述
	 */
	String getDescription();

1.2 Resource相关的类图

在这里插入图片描述
从上面类图可以看到,Resource 根据资源的不同类型提供不同的具体实现,如下:

  • FileSystemResource :对 java.io.File 类型资源的封装,只要是跟 File 打交道的,基本上与 FileSystemResource 也可以打交道;
  • ByteArrayResource :对字节数组提供的数据的封装。如果通过 InputStream 形式访问该类型的资源,该实现会根据字节数组的数据构造一个相应的 ByteArrayInputStream;
  • UrlResource :对 java.net.URL类型资源的封装。内部委派 URL 进行具体的资源操作;
  • ClassPathResource :class path 类型资源的实现。使用给定的 ClassLoader 或者给定的 Class 来加载资源。
  • InputStreamResource : 将给定的 InputStream 作为一种资源的 Resource 的实现类。

1.3 AbstractResource的源码

AbstractResource是个抽象类,为 Resource 接口的默认抽象实现。它实现了 Resource 接口的大部分的公共实现,是Resource接口最重要的实现,源码如下:

public abstract class AbstractResource implements Resource {

	/**
	 * 此实现检查文件是否可以打开,若判断过程产生错误或者异常,就关闭对应的流
	 */
	@Override
	public boolean exists() {
		// 基于 File 文件系统进行判断
		if (isFile()) {
			try {
				return getFile().exists();
			}
			catch (IOException ex) {
				Log logger = LogFactory.getLog(getClass());
				if (logger.isDebugEnabled()) {
					logger.debug("Could not retrieve File for existence check of " + getDescription(), ex);
				}
			}
		}
		//若判断过程产生错误或者异常,就关闭对应的流
		try {
			getInputStream().close();
			return true;
		}
		catch (Throwable ex) {
			Log logger = LogFactory.getLog(getClass());
			if (logger.isDebugEnabled()) {
				logger.debug("Could not retrieve InputStream for existence check of " + getDescription(), ex);
			}
			return false;
		}
	}

	/**
	 * 同exists()方法一致
	 */
	@Override
	public boolean isReadable() {
		return exists();
	}

	/**
	 *  直接返回 false,表明没有打开
	 */
	@Override
	public boolean isOpen() {
		return false;
	}

	/**
	 * 直接返回false,表明不是 File
	 */
	@Override
	public boolean isFile() {
		return false;
	}

	/**
	 * 获取URL,直接抛出异常
	 */
	@Override
	public URL getURL() throws IOException {
		throw new FileNotFoundException(getDescription() + " cannot be resolved to URL");
	}

	/**
	 * 基于 getURL() 返回的 URL 构建 URI
	 */
	@Override
	@SuppressWarnings("deprecation")
	public URI getURI() throws IOException {
		URL url = getURL();
		try {
			return ResourceUtils.toURI(url);
		}
		catch (URISyntaxException ex) {
			throw new org.springframework.core.NestedIOException("Invalid URI [" + url + "]", ex);
		}
	}

	/**
	 * 获取File,直接抛出异常
	 */
	@Override
	public File getFile() throws IOException {
		throw new FileNotFoundException(getDescription() + " cannot be resolved to absolute file path");
	}

	/**
	 * 根据 getInputStream() 的返回结果构建 ReadableByteChannel
	 */
	@Override
	public ReadableByteChannel readableChannel() throws IOException {
		return Channels.newChannel(getInputStream());
	}

	/**
	 * 获取资源的长度,实际就是资源内容的字节长度,通过全部读取一遍来判断
	 */
	@Override
	public long contentLength() throws IOException {
		InputStream is = getInputStream();
		try {
			long size = 0;
			byte[] buf = new byte[256];
			int read;
			while ((read = is.read(buf)) != -1) {
				size += read;
			}
			return size;
		}
		finally {
			try {
				is.close();
			}
			catch (IOException ex) {
				Log logger = LogFactory.getLog(getClass());
				if (logger.isDebugEnabled()) {
					logger.debug("Could not close content-length InputStream for " + getDescription(), ex);
				}
			}
		}
	}

	/**
	 * 返回资源最后的修改时间
	 */
	@Override
	public long lastModified() throws IOException {
		File fileToCheck = getFileForLastModifiedCheck();
		long lastModified = fileToCheck.lastModified();
		if (lastModified == 0L && !fileToCheck.exists()) {
			throw new FileNotFoundException(getDescription() +
					" cannot be resolved in the file system for checking its last-modified timestamp");
		}
		return lastModified;
	}

	protected File getFileForLastModifiedCheck() throws IOException {
		return getFile();
	}

	/**
	 * 根据相对路径创建资源,直接抛出异常
	 */
	@Override
	public Resource createRelative(String relativePath) throws IOException {
		throw new FileNotFoundException("Cannot create a relative resource for " + getDescription());
	}

	/**
	 * 获取文件名称,直接返回null
	 */
	@Override
	@Nullable
	public String getFilename() {
		return null;
	}


	@Override
	public boolean equals(@Nullable Object other) {
		return (this == other || (other instanceof Resource &&
				((Resource) other).getDescription().equals(getDescription())));
	}

	@Override
	public int hashCode() {
		return getDescription().hashCode();
	}

	/**
	 * 返回资源的描述
	 */
	@Override
	public String toString() {
		return getDescription();
	}

}

通过源码我们可以看到,AbstractResource实现了较大部分的Resource接口的内容,并且将一些非通用的实现交给子类去实现。所以如果我们想要实现自定义的 Resource ,记住不要实现 Resource 接口,而应该继承 AbstractResource 抽象类,然后根据当前的具体资源特性覆盖相应的方法即可。

1.4 AbstractResource的其他子类

从类图上我们可以看到,FileSystemResource、InputStreamResource、ByteArrayResource、ClassPathResource、UrlResource这些都是AbstractResource的子类。根据名称,我们基本就能猜出各个子类所代表的资源类型,相关的源码这里不再一一进行分析了。

2. Spring Framework中的资源加载器ResourceLoader

Spring Framework将资源的定义和资源的加载区分开了,Resource 定义了统一的资源,而资源的加载则由 ResourceLoader 来统一定义。ResourceLoader 主要用于根据给定的资源文件地址,返回对应的 Resource 。

2.1 ResourceLoader的源码

ResourceLoader接口在org.springframework.core.io包下,源码如下:

public interface ResourceLoader {

	/** CLASSPATH URL 前缀。默认为:"classpath:"*/
	String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
	/**
	 * 根据资源路径返回资源对象,它不确保该 Resource 一定存在,需要调用 Resource#exist() 方法来判断。
	 * 该方法支持以下模式的资源加载:
	 * URL位置资源,如 "file:C:/test.dat" 。
	 * ClassPath位置资源,如 "classpath:test.dat 。
	 * 相对路径资源,如 "WEB-INF/test.dat"
	 * 返回的Resource 实例,根据实现不同而不同。
	 * 主要实现是在子类 DefaultResourceLoader 中实现,具体过程我们在分析 DefaultResourceLoader 时做详细说明。
	 */
	Resource getResource(String location);
	/**
	*返回 ClassLoader 实例,对于想要获取 ResourceLoader 使用的 ClassLoader 用户来说,
	*可以直接调用该方法来获取。在分析 Resource 时,提到了一个类 ClassPathResource ,
	*这个类是可以根据指定的 ClassLoader 来加载资源的。
	*/
	@Nullable
	ClassLoader getClassLoader();

}

2.2 ResourceLoader的类图

在这里插入图片描述

2.3 DefaultResourceLoader的源码

DefaultResourceLoader是ResourceLoader的默认实现类,它实现了 ResourceLoader接口的大部分的公共实现,是ResourceLoader接口最重要实现类,源码如下:

public class DefaultResourceLoader implements ResourceLoader {

	@Nullable
	private ClassLoader classLoader;

	private final Set<ProtocolResolver> protocolResolvers = new LinkedHashSet<>(4);

	private final Map<Class<?>, Map<Resource, ?>> resourceCaches = new ConcurrentHashMap<>(4);


	/**
	 * 无参构造函数
	 */
	public DefaultResourceLoader() {
	}

	/**
	 * 带 ClassLoader 参数的构造函数
	 */
	public DefaultResourceLoader(@Nullable ClassLoader classLoader) {
		this.classLoader = classLoader;
	}


	/**
	 * 设置ClassLoader 
	 */
	public void setClassLoader(@Nullable ClassLoader classLoader) {
		this.classLoader = classLoader;
	}

	/**
	 * 获取ClassLoader 
	 */
	@Override
	@Nullable
	public ClassLoader getClassLoader() {
		return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader());
	}

	/**
	 * 添加自定义的协议解析器
	 */
	public void addProtocolResolver(ProtocolResolver resolver) {
		Assert.notNull(resolver, "ProtocolResolver must not be null");
		this.protocolResolvers.add(resolver);
	}

	/**
	 * 返回自定义解析器的集合
	 */
	public Collection<ProtocolResolver> getProtocolResolvers() {
		return this.protocolResolvers;
	}

	@SuppressWarnings("unchecked")
	public <T> Map<Resource, T> getResourceCache(Class<T> valueType) {
		return (Map<Resource, T>) this.resourceCaches.computeIfAbsent(valueType, key -> new ConcurrentHashMap<>());
	}

	public void clearResourceCaches() {
		this.resourceCaches.clear();
	}

	/**
	* 最核心的方法,获取资源对象。
	*/
	@Override
	public Resource getResource(String location) {
		Assert.notNull(location, "Location must not be null");
		// 1、通过 自定义的ProtocolResolvers来加载资源
		for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
			Resource resource = protocolResolver.resolve(location, this);
			if (resource != null) {
				return resource;
			}
		}
		// 2、如果通过自定义的ProtocolResolvers没有获得资源,就尝试 以 / 开头,返回 ClassPathContextResource //类型的资源
		if (location.startsWith("/")) {
			return getResourceByPath(location);
		}
		//3.尝试以 classpath: 开头,返回 ClassPathResource 类型的资源
		else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
			return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
		}
		else {
			try {
			//最后.根据是否为文件 URL ,是则返回 FileUrlResource 类型的资源,否则返回 UrlResource 类型的资源
				URL url = new URL(location);
				return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
			}
			catch (MalformedURLException ex) {
				// 最后,如果出现异常,返回 ClassPathContextResource 类型的资源
				return getResourceByPath(location);
			}
		}
	}

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

		@Override
		public String getPathWithinContext() {
			return getPath();
		}

		@Override
		public Resource createRelative(String relativePath) {
			String pathToUse = StringUtils.applyRelativePath(getPath(), relativePath);
			return new ClassPathContextResource(pathToUse, getClassLoader());
		}
	}
}

2.4 ProtocolResolver的源码

在2.3 DefaultResourceLoader源码中我们看到,getResource这个方法中,首先使用的是自定义的协议解析器来获取资源对象。所以如果我们要实现自己的资源获取方式,可以不需要继承DefaultResourceLoader,可以通过实现ProtocolResolver接口来获取Resource资源对象。ProtocolResolver接口的源码如下:

@FunctionalInterface
public interface ProtocolResolver {
	/**
	 * Resolve the given location against the given resource loader
	 * if this implementation's protocol matches.
	 * @param location the user-specified resource location 资源路径
	 * @param resourceLoader the associated resource loader 指定的加载器 ResourceLoader
	 * @return a corresponding {@code Resource} handle if the given location
	 * matches this resolver's protocol, or {@code null} otherwise 返回为相应的 Resource
	 */
	@Nullable
	Resource resolve(String location, ResourceLoader resourceLoader);

}

这个接口比较简单,就一个resolve方法。如果我们自定义一个ProtocolResolver实现后,直接调用 DefaultResourceLoader#addProtocolResolver(ProtocolResolver) 方法即可。

    /**
	 * 自定义的协议解析器的集合
	 */
	private final Set<ProtocolResolver> protocolResolvers = new LinkedHashSet<>(4);
	/**
	 * 添加自定义的协议解析器
	 */
	public void addProtocolResolver(ProtocolResolver resolver) {
		Assert.notNull(resolver, "ProtocolResolver must not be null");
		this.protocolResolvers.add(resolver);
	}

2.5 DefaultResourceLoader的其他子类

从类图上我们可以看到,FileSystemResourceLoader、ClassRelativeResourceLoader这些都是DefaultResourceLoader的子类。根据名称,我们基本就能猜出各个子类所代表的加载器类型。相关的源码这里不再一一进行分析了。

3.小结

Spring Framework整个资源加载过程就已经基本分析完成了。

  • Resource 和 ResourceLoader 来统一抽象整个资源及其加载方式。使得资源与资源的定位有了一个更加清晰的界限,并且提供了合适的 Default 类,使得自定义实现更加方便和清晰。
  • AbstractResource 为 Resource 的默认抽象实现类,它对 Resource 接口做了一个统一的实现,子类继承该类后只需要覆盖相应的方法即可,同时对于自定义的 Resource 我们也是继承该类。
  • DefaultResourceLoader 同样也是 ResourceLoader 的默认实现,在自定 ResourceLoader 的时候我们除了可以继承该类外还可以实现 ProtocolResolver 接口来实现自定资源加载协议。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

户用光伏开发如何做到病毒式推广?

随着全球对可再生能源的需求日益增加&#xff0c;户用光伏作为一种清洁、高效的能源解决方案&#xff0c;正受到越来越多的关注和青睐。然而&#xff0c;如何有效地推广户用光伏&#xff0c;使其迅速传播&#xff0c;成为当前行业面临的重要课题。 一、明确目标群体&#xff0…

SpringBoot常见问题

1 引言 Spring Boot是一个基于Spring框架的快速开发脚手架&#xff0c;它简化了Spring应用的初始化和搭建过程&#xff0c;提供了众多便利的功能和特性&#xff0c;比如自动配置、嵌入式Tomcat等&#xff0c;让开发人员可以更加专注于业务逻辑的实现。   Spring Boot还提供了…

【CANoe示例分析】EthernetTC8Test

1、工程路径 C:\Users\Public\Documents\Vector\CANoe\Sample Configurations 15.3.89\Ethernet\Test\EthernetTC8Test 在CANoe软件上也可以打开此工程:File|Help|Sample Configurations|Ethernet Testing|TC8Test(Ethernet) 2、示例目的 TC8示例是作者本人使用最多的CANo…

macOS上使用VScode编译配置C++语言开发环境

本文介绍macOS上使用VScode编译配置C语言开发环境 1.准备工作 安装C/C插件 2.配置c_cpp_properties.json文件 [⇧⌘P]打开命令模式&#xff0c;选择[C/Cpp: Edit Configurations(JSON)]命令&#xff0c;回车后会自动生成一个.vscode目录&#xff0c;目录下有一个c_cpp_prope…

ADS-B Receiver Module TT-SC1 for UAV and Drones

目录 Introduction Applications Main features Technical parameters Basic technical information Electrical specification Recommended operation conditions General electrical parameters Introduction TT-SC1 is a high quality and low price OEM ADS-B…

ActiveMQ高可用架构涉及常用功能整理

ActiveMQ高可用架构涉及常用功能整理 1. activemq的集群模式2. 镜像模式高可用系统架构和相关组件2.1 架构说明2.2 相关概念说明2.3 消息模型2.3.1 点对点2.3.2 发布订阅 3. activemq常用命令4. activemq配置集群5. 疑问和思考5.1 activemq的数据删除策略是怎样的&#xff1f;5…

Kubernetes基础(二十二)-K8S的PV/PVC/StorageClass详解

1 概述 先来个一句话总结&#xff1a;PV、PVC是K8S用来做存储管理的资源对象&#xff0c;它们让存储资源的使用变得可控&#xff0c;从而保障系统的稳定性、可靠性。StorageClass则是为了减少人工的工作量而去自动化创建PV的组件。所有Pod使用存储只有一个原则&#xff1a;先规…

京津冀光伏展

京津冀光伏展是一场专门展示京津冀地区光伏产业发展成果的展览会。光伏是指利用太阳能将光线转化为电能的技术&#xff0c;是可再生能源领域的一项重要技术。京津冀地区作为中国重要的经济区域&#xff0c;光伏产业在该地区得到了快速发展&#xff0c;并取得了丰硕的成果。 京津…

SQL注入工具之SQLmap入门操作

了解SQLmap 基础操作 SQLmap是一款自动化的SQL注入工具&#xff0c;可以用于检测和利用SQL注入漏洞。 以下是SQLmap的入门操作步骤&#xff1a; 1.下载SQLmap&#xff1a;可以从官方网站&#xff08;https://sqlmap.org/&#xff09;下载最新版本的SQLmap。 2.打开终端&#…

VSCode React JavaScript Snippets 插件的安装与使用指南

VSCode React JavaScript Snippets 插件的安装与使用指南 在开发 React 项目时&#xff0c;提高效率是每个开发者都追求的目标之一。VSCode React JavaScript Snippets 插件就是为了提升 React 开发效率而设计的&#xff0c;它为常用的 React 代码片段提供了快捷键&#xff0c;…

解决kkFileView4.4.0版本pdf、word不能预览问题

这里使用的是http下载流url预览&#xff0c;遇到的问题。 官方使用指南&#xff1a;kkFileView - 在线文件预览 1 前端测试代码 1.1 官方示例代码 1.2 本人测试代码 注意&#xff1a;要给预览文件的url进行编码encodeURIComponent(Base64.encode(previewUrl))。 <!DOCTYP…

面试redis篇-03缓存击穿

原理 缓存击穿&#xff1a;给某一个key设置了过期时间&#xff0c;当key过期的时候&#xff0c;恰好这时间点对这个key有大量的并发请求过来&#xff0c;这些并发的请求可能会瞬间把DB压垮 解决方案一&#xff1a;互斥锁 解决方案二&#xff1a;逻辑过期 提问与回答 面试官 &a…

滤波电阻器:用于能源系统和工业的高精度解决方案(1)?

滤波电阻器用于防止能源系统中的电源反馈。铝厂或钢铁厂中的大型感应冶炼厂会产生与电源频率的谐波。必须不惜一切代价让这些远离电网。过滤器&#xff0c;通常以 T 或 L 元素的形式用于此目的。中压电源输入端的吸收电路由电容和电感的串联连接组成&#xff0c;对谐波进行负载…

[ansible] playbook运用

一、复习playbook剧本 --- - name: first play for install nginx #设置play的名称gather_facts: false #设置不收集facts信息hosts: webservers:dbservers #指定执行此play的远程主机组remote_user: root #指定执行此play的用…

PCIe学习笔记(2)错误处理和AER/DPC功能

文章目录 PCIe ErrorAER (Advanced Error Reporting)DPC (Downstream Port Containment) 处理器上错误通常可分为detected和undetected error。Undetected errors可能变得良性(benign)&#xff0c;也可能导致系统故障如silent data corruptions (SDC)。Detected errors则又可分…

自养号测评低成本高效率推广,安全可控

测评的作用在于让用户更真实、清晰、快捷地了解产品以及产品的使用方法和体验。通过买家对产品的测评&#xff0c;也可以帮助厂商和卖家优化产品缺陷&#xff0c;提高用户的使用体验。这进而帮助他们获得更好的销量&#xff0c;并更深入地了解市场需求。因此&#xff0c;测评在…

Java 学习和实践笔记(14)

OOP :面向对象编程&#xff0c;object oriented programming. 用表格就可以很好地理解类、对象、属性、以及动作这些概念。 一个表&#xff08;结构&#xff09;就对应一个类&#xff08;结构&#xff09;。所以凡叫什么类&#xff0c;自己就在心里把它叫什么表。反过来&…

消息队列-RabbitMQ:MQ作用分类、RabbitMQ核心概念及消息生产消费调试

1、MQ 的相关概念 1&#xff09;什么是 MQ MQ (message queue)&#xff0c;从字面意思上看&#xff0c;本质是个队列&#xff0c;FIFO 先入先出&#xff0c;只不过队列中存放的内容是 message 而已&#xff0c;还是一种跨进程的通信机制&#xff0c;用于上下游传递消息。在互…

【数据结构】每天五分钟,快速入门数据结构(二)——链表

目录 一 构建一个单向链表 二 特点 三 时间复杂度 四 相关算法 1.判断链表是否成环及成环位置 2.链表反转 五 Java中的LinkedList 类 1.使用 2.LinkedList 方法 一 构建一个单向链表 // 设计链表结构class ListNode {int val;ListNode next;ListNode(){}ListNode(int…

Laravel01 课程介绍以及Laravel环境搭建

Laravel01 课程介绍 1. Laravel2. mac开发环境搭建(通过Homebrew)3. 创建一个项目 1. Laravel 公司中面临着PHP项目与Java项目并行&#xff0c;所以需要我写PHP的项目&#xff0c;公司用的框架就是Laravel&#xff0c;所以在B站上找了一门课学习。 课程地址 2. mac开发环境搭…