Spring原理分析--获取Resource资源对象

1.获取资源对象

ApplicationContext接口是BeanFactory的子接口,意味着它扩展了BeanFactory的功能,其中继承ResourcePatternResolver接口,提供获取Resource资源的功能,示例如下:

@SpringBootApplication
public class A01 {public static void main(String[] args) throws IOException {ConfigurableApplicationContext context = SpringApplication.run(A01.class, args);// 获取类路径下及所有jar包下的spring.factories文件资源Resource[] resources = context.getResources("classpath*:META-INF/spring.factories");for (Resource resource : resources) {System.out.println(resource);}}
}

2.获取资源原理

这里的context是AnnotationConfigServletWebServerApplicationContext,调用父类GenericApplicationContext的getResources获取资源,源码如下:

public Resource[] getResources(String locationPattern) throws IOException {// resourceLoader默认为nullif (this.resourceLoader instanceof ResourcePatternResolver) {return ((ResourcePatternResolver) this.resourceLoader).getResources(locationPattern);}return super.getResources(locationPattern);
}

默认情况下会调用父类AbstractApplicationContext#getResources

public Resource[] getResources(String locationPattern) throws IOException {// resourcePatternResolver在构造函数中设置的PathMatchingResourcePatternResolverreturn this.resourcePatternResolver.getResources(locationPattern);
}

PathMatchingResourcePatternResolver#getResources源码如下:

public Resource[] getResources(String locationPattern) throws IOException {Assert.notNull(locationPattern, "Location pattern must not be null");// 如果以classpath*:开头if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {// 调用AntPathMatcher#isPattern判断是查找多个文件还是单个文件,即判断是否包含*、?或者{}if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {// 处理带通配符的路径return findPathMatchingResources(locationPattern);}else {// 通过是单个文件,则查找所有路径下的资源,包括jar包中的资源return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));}}else {// 获取路径前缀int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 :locationPattern.indexOf(':') + 1);// 去掉前缀后,调用AntPathMatcher#isPattern判断是否包含通配符*、?或者{}if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {// 处理带通配符的路径return findPathMatchingResources(locationPattern);}else {// 调用DefaultResourceLoader#getResource获取单个资源return new Resource[] {getResourceLoader().getResource(locationPattern)};}}
}

PathMatchingResourcePatternResolver在获取资源时有3种可能:

1)findPathMatchingResources处理带通配符的路径

protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {// 获取不带通配符的目录地址String rootDirPath = determineRootDir(locationPattern);// 路径中带通配符的部分String subPattern = locationPattern.substring(rootDirPath.length());// 递归调用,获取不带通配符目录的资源,会走findAllClassPathResources或者DefaultResourceLoader的逻辑Resource[] rootDirResources = getResources(rootDirPath);Set<Resource> result = new LinkedHashSet<>(16);// 遍历所有目录资源for (Resource rootDirResource : rootDirResources) {// 获取目录的地址rootDirResource = resolveRootDirResource(rootDirResource);URL rootDirUrl = rootDirResource.getURL();// 处理OSGI相关的资源if (equinoxResolveMethod != null && rootDirUrl.getProtocol().startsWith("bundle")) {URL resolvedUrl = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirUrl);if (resolvedUrl != null) {rootDirUrl = resolvedUrl;}rootDirResource = new UrlResource(rootDirUrl);}// 处理VFS协议文件资源if (rootDirUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl, subPattern, getPathMatcher()));}// 处理jar文件资源else if (ResourceUtils.isJarURL(rootDirUrl) || isJarResource(rootDirResource)) {result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirUrl, subPattern));}// 处理普通文件资源else {result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));}}if (logger.isTraceEnabled()) {logger.trace("Resolved location pattern [" + locationPattern + "] to resources " + result);}return result.toArray(new Resource[0]);
}

处理普通文件资源doFindPathMatchingFileResources中主要调用了doFindMatchingFileSystemResources(rootDir, subPattern),逻辑如下:

protected Set<Resource> doFindMatchingFileSystemResources(File rootDir, String subPattern) throws IOException {if (logger.isTraceEnabled()) {logger.trace("Looking for matching resources in directory tree [" + rootDir.getPath() + "]");}// 查找匹配的文件Set<File> matchingFiles = retrieveMatchingFiles(rootDir, subPattern);Set<Resource> result = new LinkedHashSet<>(matchingFiles.size());// 包装成FileSystemResource返回for (File file : matchingFiles) {result.add(new FileSystemResource(file));}return result;
}

retrieveMatchingFiles中核心方法是doRetrieveMatchingFiles,逻辑如下:

protected void doRetrieveMatchingFiles(String fullPattern, File dir, Set<File> result) throws IOException {if (logger.isTraceEnabled()) {logger.trace("Searching directory [" + dir.getAbsolutePath() +"] for files matching pattern [" + fullPattern + "]");}// 遍历目录下的文件和目录for (File content : listDirectory(dir)) {String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");// 如果当前是目录,则比较带上一级目录的部分是否匹配if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) {// 没有读权限,则放弃查找此目录if (!content.canRead()) {if (logger.isDebugEnabled()) {logger.debug("Skipping subdirectory [" + dir.getAbsolutePath() +"] because the application is not allowed to read the directory");}}else {// 有读权限,则递归调用,匹配下一级目录doRetrieveMatchingFiles(fullPattern, content, result);}}// 如果当前是文件,则校验全路径是否匹配if (getPathMatcher().match(fullPattern, currPath)) {// 如果匹配上,则添加到结果中result.add(content);}}
}

路径匹配算法在AntPathMatcher#doMatch中实现,源码如下:

protected boolean doMatch(String pattern, @Nullable String path, boolean fullMatch,@Nullable Map<String, String> uriTemplateVariables) {// 如果path为空,匹配失败// 如果path与pattern不是都以/开头,或都不以/开头,则匹配失败if (path == null || path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) {return false;}// 根据/分割pattern字符串String[] pattDirs = tokenizePattern(pattern);// 如果是全匹配,则调用isPotentialMatch粗略判断下是否匹配if (fullMatch && this.caseSensitive && !isPotentialMatch(path, pattDirs)) {return false;}// 根据/分割path字符串String[] pathDirs = tokenizePath(path);int pattIdxStart = 0;int pattIdxEnd = pattDirs.length - 1;int pathIdxStart = 0;int pathIdxEnd = pathDirs.length - 1;// 从前往后遍历两个分割后的数组while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {String pattDir = pattDirs[pattIdxStart];// 如果在pattDirs中找到了**,则退出循环,因为**可以匹配多级路径if ("**".equals(pattDir)) {break;}// 如果pattDir和pathDirs中不匹配,则直接返回falseif (!matchStrings(pattDir, pathDirs[pathIdxStart], uriTemplateVariables)) {return false;}// 两个字符串数组索引同步向后移动pattIdxStart++;pathIdxStart++;}// 如果path数组已经遍历完成if (pathIdxStart > pathIdxEnd) {// 如果pattern数组也遍历完成,需要比较不是都以/开头,或都不以/开头// 需要比较的原因是tokenizePattern方法会忽略前后的/// 例如:"/aa/bb/*", "/aa/bb/cc"// 例如:"/aa/bb/*/", "/aa/bb/cc/"if (pattIdxStart > pattIdxEnd) {return (pattern.endsWith(this.pathSeparator) == path.endsWith(this.pathSeparator));}// 如果不是匹配整个字符串,即匹配前缀部分,则匹配成功if (!fullMatch) {return true;}// 如果pattern字符串为xx/*且path字符串为xx/,则匹配成功// 例如:pattern为/aa/bb/cc/*或/aa/bb/cc/*/,path为/aa/bb/cc/if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) {return true;}// 如果pattern的路径层级更多,则多出的部分必须是**才能匹配上// 例如:"/aa/bb/cc/**/**", "/aa/bb/cc"for (int i = pattIdxStart; i <= pattIdxEnd; i++) {if (!pattDirs[i].equals("**")) {return false;}}return true;}else if (pattIdxStart > pattIdxEnd) {// 如果path数组未遍历完成,但pattern数组已遍历完成,则返回false// 例如:"/aa/*/", "/aa/bb/cc"return false;}else if (!fullMatch && "**".equals(pattDirs[pattIdxStart])) {// 如果不是匹配整个字符串,并且pattern中有**,则返回truereturn true;}// 从后往前遍历两个分割后的数组,找到最后一个**while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {String pattDir = pattDirs[pattIdxEnd];// 如果在pattDirs中找到了**,则退出循环,因为**可以匹配多级路径if (pattDir.equals("**")) {break;}// 如果pattDir和pathDirs中不匹配,则直接返回falseif (!matchStrings(pattDir, pathDirs[pathIdxEnd], uriTemplateVariables)) {return false;}// 两个字符串数组索引同步向后移动pattIdxEnd--;pathIdxEnd--;}// 如果path路径已经遍历完成,则pattern中间部分必须全是**才能匹配上if (pathIdxStart > pathIdxEnd) {for (int i = pattIdxStart; i <= pattIdxEnd; i++) {if (!pattDirs[i].equals("**")) {return false;}}return true;}// 匹配第一次**与最后一次**中间的部分while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) {int patIdxTmp = -1;// 从第一次**+1的索引处开始遍历,找到下一个**for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) {if (pattDirs[i].equals("**")) {patIdxTmp = i;break;}}if (patIdxTmp == pattIdxStart + 1) {// 如果是**/**的情况,继续循环pattIdxStart++;continue;}// 计算出pattern两个**之间的路径个数int patLength = (patIdxTmp - pattIdxStart - 1);// 计算path中间的路径个数int strLength = (pathIdxEnd - pathIdxStart + 1);int foundIdx = -1;// 判断pattern中**和下一个**中间固定路径是否匹配strLoop:for (int i = 0; i <= strLength - patLength; i++) {for (int j = 0; j < patLength; j++) {// pattern第一个需要匹配的字符串String subPat = pattDirs[pattIdxStart + j + 1];String subStr = pathDirs[pathIdxStart + i + j];// j始终为0,path不断向后平移,直到找到能匹配的路径if (!matchStrings(subPat, subStr, uriTemplateVariables)) {continue strLoop;}}// 记录path平移的位置foundIdx = pathIdxStart + i;break;}// 如果平移没有匹配上,返回falseif (foundIdx == -1) {return false;}// pattIdxStart设置为下一个**的位置pattIdxStart = patIdxTmp;// pathIdxStart设置为匹配上的字符串的下一个字符串pathIdxStart = foundIdx + patLength;}// 上述情况都匹配完成后,如果pattern还有剩余数据,则必须全部是**,否则不匹配for (int i = pattIdxStart; i <= pattIdxEnd; i++) {if (!pattDirs[i].equals("**")) {return false;}}return true;
}

2)findAllClassPathResources处理Classpath:*下的不带通配符路径

protected Resource[] findAllClassPathResources(String location) throws IOException {String path = location;if (path.startsWith("/")) {path = path.substring(1);}// 查找资源Set<Resource> result = doFindAllClassPathResources(path);if (logger.isTraceEnabled()) {logger.trace("Resolved classpath location [" + location + "] to resources " + result);}return result.toArray(new Resource[0]);
}protected Set<Resource> doFindAllClassPathResources(String path) throws IOException {Set<Resource> result = new LinkedHashSet<>(16);ClassLoader cl = getClassLoader();// 通过类加载器获取path的资源Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path));while (resourceUrls.hasMoreElements()) {URL url = resourceUrls.nextElement();// 转换成UrlResource对象result.add(convertClassLoaderURL(url));}// 如果path为空,即原始路径为classpath*:/,添加所有jar包路径if (!StringUtils.hasLength(path)) {addAllClassLoaderJarRoots(cl, result);}return result;
}

类加载器获取path的资源源码如下:

public Enumeration<URL> getResources(String name) throws IOException {// tmp[0]存放父加载器查找的URL,tmp[1]存放当前类加载器查找的URL@SuppressWarnings("unchecked")Enumeration<URL>[] tmp = (Enumeration<URL>[]) new Enumeration<?>[2];if (parent != null) {tmp[0] = parent.getResources(name);} else {tmp[0] = getBootstrapResources(name);}tmp[1] = findResources(name);// 嵌套的迭代器return new CompoundEnumeration<>(tmp);
}

ClassLoader的getResources方法中会递归查找父类加载器加载的资源,返回CompoundEnumeration对象是一个嵌套的迭代器,内部还封装了Enumeration迭代器,因此父类查找到的资源会放入内部的迭代器中,层层嵌套

3)DefaultResourceLoader获取单个资源

public Resource getResource(String location) {Assert.notNull(location, "Location must not be null");// 获取协议解析器,默认为空,可以添加自定义的协议解析器,如果有添加协议解析器,则遍历for (ProtocolResolver protocolResolver : getProtocolResolvers()) {// 使用协议解析器解析路径获取对应的资源Resource resource = protocolResolver.resolve(location, this);if (resource != null) {return resource;}}// 如果路径以/开头,则调用getResourceByPath,默认是返回ClassPathContextResource对象// 但是web环境下ServletWebServerApplicationContext重写了该方法会返回ServletContextResourceif (location.startsWith("/")) {return getResourceByPath(location);}// 如果路径以classpath:/开头,则返回ClassPathResource对象else if (location.startsWith(CLASSPATH_URL_PREFIX)) {return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());}else {try {// 尝试将路径解析为URL对象URL url = new URL(location);// 如果是文件协议,则返回FileUrlResource对象,否则返回UrlResource对象return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));}catch (MalformedURLException ex) {// 如果不能解析成URL对象,则调用getResourceByPathreturn getResourceByPath(location);}}
}

通过getResources获取到资源文件后,就可以使用Resource对象中的方法进行文件操作,例如获取输入流、判断资源是否存在、获取资源的文件名等

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

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

相关文章

TQZC706开发板教程:编译zynq linux内核2019_R1

您需要下载对应版本的Linux系统文件以及IMG1.3.1镜像文件。为了方便您的操作&#xff0c;本文所使用的所有文件以及最终生成的文件&#xff0c;我都已经整理并放置在本文末尾提供的网盘链接中。您可以直接通过该链接进行下载&#xff0c;无需在其他地方单独搜索和获取。希望这能…

C语言:数据结构(单链表)

目录 1. 链表的概念及结构2. 实现单链表3. 链表的分类 1. 链表的概念及结构 概念&#xff1a;链表是一种物理存储结构上非连续、非顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表的指针链接次序实现的。 链表的结构跟火车车厢相似&#xff0c;淡季时车次的车厢会相应…

linux之进程通信

目录 一、进程通信介绍 1.目的 2.发展 3.进程通信是什么&#xff0c;怎么通信&#xff1f; 二、管道 1.介绍 2.匿名管道 1.单向通信管道原理 2.代码实现 3.管道特征 4.管道的四种情况 5.管道的应用场景 使用管道实现一个简易版本的进程池 3.命名管道 1.思考 2.…

使用JXLS+Excel模板制作灵活的excel导出

前期一直卡在模板的批注上&#xff0c;改了很多遍的模板批注最终才成功导入&#xff0c;记录下方便以后寻找。 话不多说直接上代码&#xff1a; Report package com.example.jxls.common;import java.io.IOException; import java.io.InputStream; import java.io.OutputStr…

使用 Meta Llama 3 构建人工智能的未来

使用 Meta Llama 3 构建人工智能的未来 现在提供 8B 和 70B 预训练和指令调整版本,以支持广泛的应用 使用 Meta AI 体验 Llama 3 我们已将 Llama 3 集成到我们的智能助手 Meta AI 中,它扩展了人们完成工作、创造和与 Meta AI 联系的方式。通过使用 Meta AI 进行编码任务和解…

python安装时遇到A newer version of the Python launcher is already installed.

由于业务需要&#xff0c;我得用python3.9版本&#xff08;好像是因为python3.9对深度学习等的支持比较好&#xff09;&#xff0c;需要涉及到python3.9和python3.12的共存问题&#xff0c;方法有很多&#xff0c;什么创建虚拟环境使得不同版本的共存之类的。最后我选择最笨的&…

RHCA证书含金量高吗?Linux认证难考吗?

在IT行业&#xff0c;Linux系统认证作为衡量专业人士Linux技能水平的重要标准&#xff0c;越来越受到重视。 特别是红帽认证架构师&#xff08;RHCA&#xff09;证书&#xff0c;它代表着Linux领域的高级专业技能。 那么&#xff0c;RHCA证书的含金量如何&#xff1f;Linux认…

通过Dos批量对程序进行打包

本文介绍如何编写dos可执行程序来进行软件的发包,通过dos自动获取系统当前时间复制软件模版到指定的产品目录,然后将指定的产品内容复制到程序内部。最终通过360压缩工具进行一个打包操作。提供程序发布效率。 当前时间日期: set predate=%date:~0,4%%date:~5,2%%date:~8,2%…

C语言.字符函数与字符串函数

字符函数与字符串函数 1.字符分类函数2.字符转换函数3.[strlen](https://cplusplus.com/reference/cstring/strlen/?kwstrlen) 的使用和模拟实现4.[strcpy](https://legacy.cplusplus.com/reference/cstring/strcpy/?kwstrcpy) 的使用和模拟实现5.[strcat](https://legacy.cp…

Remote access minikube cluster远程访问minikube k8s集群

minikube是启动一个虚拟机来模拟单节点环境&#xff0c;容器运行在单独的网络环境 可以看到192.168.49.2:8443是api server地址&#xff0c;是虚拟的ip (base) [rootlocalhost access]# kubectl config view apiVersion: v1 clusters: - cluster:certificate-authority: /roo…

信息系统及其技术发展

目录 一、信息系统基本概念 1、信息系统项目开发 2、信息系统项目管理 3、信息系统 Ⅰ、生命周期 Ⅱ、新基建 ①信息基础设施 ②融合基础设施 ③创新基础设施 Ⅲ、工业互联网 Ⅳ、车联网 ①体系框架 ②连接方式 4、习题 二、信息技术发展 1、SDN 2、5G 3、存储…

AI小知识----什么是RAG

RAG的概念 RAG的全称是Retrieval-Augmented Generation&#xff0c;中文翻译为检索增强生成。它是一个为大模型提供外部知识源的概念&#xff0c;这使它们能够生成准确且符合上下文的答案&#xff0c;同时能够减少模型幻觉。 「检索(Retrive)」 根据用户请求从外部知识源检索相…

书生·浦语大模型第二期实战营(5)笔记

大模型部署简介 难点 大模型部署的方法 LMDeploy 实践 安装 studio-conda -t lmdeploy -o pytorch-2.1.2conda activate lmdeploypip install lmdeploy[all]0.3.0模型 ls /root/share/new_models/Shanghai_AI_Laboratory/ln -s /root/share/new_models/Shanghai_AI_Laborato…

【Vue3源码学习】— CH3.2 VNode解析(上)

VNode解析—上 1. VNode 的作用和优势1.1 抽象表示1.2 更新优化1.3 跨平台2. _createVNode 函数解析2.1 调用链路2.2 _createVNode函数实现关键操作说明:注1:注2:2.3 _createVNode小结3. createBaseVNode 详解3.1 源码解析3.2 关键部分解析3.3 类型说明3.3.1 ts | 符号3.3.2 解…

探索Java设计模式:命令模式

深入理解与实践Java设计模式之命令模式 一、简要介绍 命令模式&#xff08;Command Pattern&#xff09;是一种行为型设计模式&#xff0c;它将请求封装为一个对象&#xff0c;使得使用命令对象的客户端与请求接收者&#xff08;即具体执行命令的对象&#xff09;解耦。这样做…

只需几步,即可享有笔记小程序

本示例是一个简单的外卖查看店铺点菜的外卖微信小程序&#xff0c;小程序后端服务使用了MemFire Cloud&#xff0c;其中使用到的MemFire Cloud功能包括&#xff1a; 其中使用到的MemFire Cloud功能包括&#xff1a; 云数据库&#xff1a;存储外卖微信小程序所有数据表的信息。…

【linux】软件工具安装 + vim 和 gcc 使用(上)

目录 1. linux 安装软件途径 2. rzsz 命令 3. vim 和 gcc 使用 a. vim的基本概念 b. 命令模式下的指令 c. 底行模式下的指令 1. linux 安装软件途径 源代码安装rpm安装 -- linux安装包yum安装&#xff08;最好&#xff0c;可以解决安装源&#xff0c;安装版本&#xff0…

0418WeCross搭建 + Caliper测试TPS

1. 基本信息 虚拟机名称&#xff1a;Pure-Ununtu18.04 WeCross位置&#xff1a;/root/wecross-demo 2. 搭建并启动WeCross 参考官方指导文档 https://wecross.readthedocs.io/zh-cn/v1.2.0/docs/tutorial/demo/demo.html 访问WeCross网页管理平台 http://localhost:8250/s/…

【Java框架】Spring框架(六)——Spring中的Bean的作用域

目录 Bean的作用域1.singleton(默认)代码示例 2.prototype代码示例 3.request代码示例 4.session代码示例 5.application代码示例 websocket Bean的作用域 Spring支持6个作用域&#xff1a;singleton、prototype、request、session、application、websocket 1.singleton(默认…

python基础知识二(标识符和关键字、输出、输入)

目录 标识符和关键字&#xff1a; 什么是标识符&#xff1f; 1. 标识符 2. 标识符的命名规则 什么是关键字&#xff1f; 1. 关键字 2. 关键字的分类 标识符和关键字的区别&#xff1a; ​​​输出&#xff1a; 1. 普通的输出 2. 格式化输出 格式化操作的目的&#…