关于Spring加载classpath与classpath*的过程剖析

Spring Boot中Classpath资源加载原理与实战指南 在Java开发中,classpath是JVM用于定位类文件和资源的核心机制,它定义了资源搜索的路径顺序。其工作原理类似于操作系统的PATH环境变量,按照配置的目录和JAR文件顺序查找资源。这一机制对框架设计至关重要,Spring Boot通过自定义类加载器LaunchedURLClassLoader,支持了Fat Jar的嵌套结构,实现了资源加载的一致性。在工程实践中,正确理解classpath有助于解决配置文件加载、多模块资源访问等常见问题。本文结合Spring Boot的Fat Jar结构和资源加载AP 阅读详情

    本篇文章是由朋友的一篇博客引出的,博客原文地址:http://jinnianshilongnian.iteye.com/blog/1416322

    他这篇博客比较细的讲解了classpath与classpath*,以及通配符的使用,那些配置能成功加载到资源,那些配置加载不了资源。但是我相信仍然有很多同学不明白,为什么是这样的,知其然,不知其所以然,那么本篇文章将慢慢为你揭开神秘的面纱,让你知其然,更知其所以然。


    关于Spring Resource的资源类型以及继承体系我们已经在上一篇文件粗略的说了一下。Spring加载Resource文件是通过ResourceLoader来进行的,那么我们就先来看看ResourceLoader的继承体系,让我们对这个模块有一个比较系统的认知。


上图仅右边的继承体系,仅画至AbstractApplicationContext,由于ApplicationContext的继承体系,我们已经在前面章节给出,所以为了避免不必要的复杂性,本章继承体系就不引入ApplicationContext。

  



我们还是来关注本章的重点————classpath 与 classpath*以及通配符是怎么处理的


首先,我们来看下ResourceLoader的源码

public interface ResourceLoader {

	/** Pseudo URL prefix for loading from the class path: "classpath:" */
	String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
	
	Resource getResource(String location);

	ClassLoader getClassLoader();

}

我们发现,其实ResourceLoader接口只提供了classpath前缀的支持。而classpath*的前缀支持是在它的子接口ResourcePatternResolver中。

public interface ResourcePatternResolver extends ResourceLoader {

	/**
	 * Pseudo URL prefix for all matching resources from the class path: "classpath*:"
	 * This differs from ResourceLoader's classpath URL prefix in that it
	 * retrieves all matching resources for a given name (e.g. "/beans.xml"),
	 * for example in the root of all deployed JAR files.
	 * @see org.springframework.core.io.ResourceLoader#CLASSPATH_URL_PREFIX
	 */
	String CLASSPATH_ALL_URL_PREFIX = "classpath*:";

	
	Resource[] getResources(String locationPattern) throws IOException;

}

   通过2个接口的源码对比,我们发现ResourceLoader提供 classpath下单资源文件的载入,而 ResourcePatternResolver提供了多资源文件的载入。

  ResourcePatternResolver有一个实现类:PathMatchingResourcePatternResolver,那我们直奔主题,查看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)) {
			//是否包含?或者*
			if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
				// a class path resource pattern
				return findPathMatchingResources(locationPattern);
			}
			else {
				// all class path resources with the given name
				return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
			}
		}
		else {
			// Only look for a pattern after a prefix here
			// (to not get fooled by a pattern symbol in a strange prefix).
			int prefixEnd = locationPattern.indexOf(":") + 1;
			//是否包含?或者*
			if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
				// a file pattern
				return findPathMatchingResources(locationPattern);
			}
			else {
				// a single resource with the given name
				return new Resource[] {getResourceLoader().getResource(locationPattern)};
			}
		}
	}

由此我们可以看出在加载配置文件时,以是否是以classpath*开头分为2大类处理场景,每大类在又根据路径中是否包括通配符分为2小类进行处理,

处理的流程图如下:


从上图看,整个加载资源的场景有三条处理流程

  • 以classpath*开头,但路径不包含通配符的
             让我们来看看findAllClassPathResources是怎么处理的
	protected Resource[] findAllClassPathResources(String location) throws IOException {
		String path = location;
		if (path.startsWith("/")) {
			path = path.substring(1);
		}
		Enumeration<URL> resourceUrls = getClassLoader().getResources(path);
		Set<Resource> result = new LinkedHashSet<Resource>(16);
		while (resourceUrls.hasMoreElements()) {
			URL url = resourceUrls.nextElement();
			result.add(convertClassLoaderURL(url));
		}
		return result.toArray(new Resource[result.size()]);
	}

    我们可以看到,最关键的一句代码是:Enumeration<URL> resourceUrls = getClassLoader().getResources(path); 
	public ClassLoader getClassLoader() {
		return getResourceLoader().getClassLoader();
	}


public ResourceLoader getResourceLoader() {
		return this.resourceLoader;
	}

//默认情况下
public PathMatchingResourcePatternResolver() {
		this.resourceLoader = new DefaultResourceLoader();
	}
其实上面这3个方法不是最关键的,之所以贴出来,是让大家清楚整个调用链,其实这种情况最关键的代码在于ClassLoader的getResources()方法。那么我们同样跟进去,看看源码
 public Enumeration<URL> getResources(String name) throws IOException {
	Enumeration[] tmp = new Enumeration[2];
	if (parent != null) {
	    tmp[0] = parent.getResources(name);
	} else {
	    tmp[0] = getBootstrapResources(name);
	}
	tmp[1] = findResources(name);

	return new CompoundEnumeration(tmp);
    }
是不是一目了然了?当前类加载器,如果存在父加载器,则向上迭代获取资源, 因此能加到jar包里面的资源文件。

  • 不以classpath*开头,且路径不包含通配符的
处理逻辑如下           
return new Resource[] {getResourceLoader().getResource(locationPattern)};
上面我们已经贴过getResourceLoader()的逻辑了, 即默认是DefaultResourceLoader(),那我们进去看看getResouce()的实现
	public Resource getResource(String location) {
		Assert.notNull(location, "Location must not be null");
		if (location.startsWith(CLASSPATH_URL_PREFIX)) {
			return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
		}
		else {
			try {
				// Try to parse the location as a URL...
				URL url = new URL(location);
				return new UrlResource(url);
			}
			catch (MalformedURLException ex) {
				// No URL -> resolve as resource path.
				return getResourceByPath(location);
			}
		}
	}

其实很简单,如果以classpath开头,则创建为一个ClassPathResource,否则则试图以URL的方式加载资源,创建一个UrlResource.
  • 路径包含通配符的
             这种情况是最复杂的,涉及到层层递归,那我把加了注释的代码发出来大家看一下,其实主要的思想就是
1.先获取目录,加载目录里面的所有资源
2.在所有资源里面进行查找匹配,找出我们需要的资源
protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
		//拿到能确定的目录,即拿到不包括通配符的能确定的路径  比如classpath*:/aaa/bbb/spring-*.xml 则返回classpath*:/aaa/bbb/                                     //如果是classpath*:/aaa/*/spring-*.xml,则返回 classpath*:/aaa/
		String rootDirPath = determineRootDir(locationPattern);
		//得到spring-*.xml
		String subPattern = locationPattern.substring(rootDirPath.length());
		//递归加载所有的根目录资源,要注意的是递归的时候又得考虑classpath,与classpath*的情况,而且还得考虑根路径中是否又包含通配符,参考上面那张流程图
		Resource[] rootDirResources = getResources(rootDirPath);
		Set<Resource> result = new LinkedHashSet<Resource>(16);
		//将根目录所有资源中所有匹配我们需要的资源(如spring-*)加载result中
		for (Resource rootDirResource : rootDirResources) {
			rootDirResource = resolveRootDirResource(rootDirResource);
			if (isJarResource(rootDirResource)) {
				result.addAll(doFindPathMatchingJarResources(rootDirResource, subPattern));
			}
			else if (rootDirResource.getURL().getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
				result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirResource, subPattern, getPathMatcher()));
			}
			else {
				result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
			}
		}
		if (logger.isDebugEnabled()) {
			logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result);
		}
		return result.toArray(new Resource[result.size()]);
	}

值得注解一下的是determineRootDir()方法的作用,是确定根目录,这个根目录必须是一个能确定的路径,不会包含通配符。如果classpath*:aa/bb*/spring-*.xml,得到的将是classpath*:aa/  可以看下他的源码

	protected String determineRootDir(String location) {
		int prefixEnd = location.indexOf(":") + 1;
		int rootDirEnd = location.length();
		while (rootDirEnd > prefixEnd && getPathMatcher().isPattern(location.substring(prefixEnd, rootDirEnd))) {
			rootDirEnd = location.lastIndexOf('/', rootDirEnd - 2) + 1;
		}
		if (rootDirEnd == 0) {
			rootDirEnd = prefixEnd;
		}
		return location.substring(0, rootDirEnd);
	}




分析到这,结合测试我们可以总结一下:
1.无论是classpath还是classpath*都可以加载整个classpath下(包括jar包里面)的资源文件。
2.classpath只会返回第一个匹配的资源,查找路径是优先在项目中存在资源文件,再查找jar包。
3.文件名字包含通配符资源(如果spring-*.xml,spring*.xml),   如果根目录为"", classpath加载不到任何资源, 而classpath*则可以加载到classpath中 可以匹配的目录中的资源,但是不能加载到jar包中的资源
    
      第1,2点比较好表理解,大家可以自行测试,第三点表述有点绕,举个例,现在有资源文件结构如下:


classpath:notice*.txt                                                               加载不到资源
classpath*:notice*.txt                                                            加载到resource根目录下notice.txt
classpath:META-INF/notice*.txt                                          加载到META-INF下的一个资源(classpath是加载到匹配的第一个资源,就算删除classpath下的notice.txt,他仍然可以                                                                                                  加载jar包中的notice.txt)
classpath:META-*/notice*.txt                                              加载不到任何资源
classpath*:META-INF/notice*.txt                                        加载到classpath以及所有jar包中META-INF目录下以notice开头的txt文件
classpath*:META-*/notice*.txt                                             只能加载到classpath下 META-INF目录的notice.txt

大家感觉一下吧



Spring Boot 中使用 ResourceLoader 加载资源的完整示例 Component@Autowired// 获取资源// 检查资源是否存在if (!System.out.println("该资源不存在!");return "该资源不存在!System.out.println("资源存在!");= null) {// 添加换行符 :每次读取一行后都会添加一个换行符,确保了每行的结尾都有换行符。// 如果仍然没有效果,请确认文件本身是否包含换行符。 阅读详情

相关推荐

深入理解Java中资源加载的方法及Spring的ResourceLoader应用

在Java开发中,资源加载是一个基础而重要的操作。本文将深入探讨Java中两种常见的资源加载方式:ClassLoader的getResource方法和Class的getResource方法,并介绍Spring框架中的ResourceLoader的应用。

学无止境 958

Spring源码之ResourceLoader(一):实现类DefaultResourceLoader实现getResource

ddd

Fsociety的博客 1786

Vivado新手必看:5分钟搞定FPGA程序固化到Flash(附MCS文件生成全流程)

本文为FPGA新手详细解析了如何将程序固化到Flash存储器的完整流程。核心在于理解SRAM工艺FPGA断电易失的特性,并利用Vivado工具将比特流文件转换为Flash可识别的MCS文件。文章重点拆解了生成MCS文件的关键配置步骤、硬件连接要点,以及通过JTAG烧写Flash的实战操作,并提供了常见启动失败问题的排查清单,帮助开发者实现设计从调试到产品部署的关键跨越。

weixin_29230901的博客 347

带有getResource()示例的Spring Resource loader

Spring的资源加载器提供了一种非常通用的getResource()方法来从文件系统,类路径或URL获取资源(文本文件,媒体文件,图像文件等)。 您可以从应用程序上下文中获取getResource()方法。 这是一个示例,展示了如何使用getResource()从中加载文本文件 1.文件系统 Resource resource = appContext.getResource("f...

一名可爱的技术搬运工 601

win2012没有远程桌面授权服务器可以提供许可证 如何远程

如果远程windows服务器时,提示“没有远程桌面授权服务器可以提供许可证 如何远程”,先通过以下方式进行强制登录,然后在对服务器进行设置即可,延期使用。 强制登录服务器方式; 1、运行—输入:mstsc /admin /v ip:端口 回车即可 2、进入远程桌面,输入 ip:端口 /admin 例如:mstsc /admin /v 192.168.0.131:23568 进入服务器以后,按照以下步骤进行设置即可: ...

蜗牛快爬 1万+

深度学习模型设计的方法和技巧

设计神经网络模型是一个迭代的过程,需要不断试验和调整各个组成部分,以达到最佳性能。同时,遵循良好的工程实践,如模块化设计、版本控制、文档记录等,也是成功的关键。

xw555666的博客 4026

Spring的Resource接口与ResourceLoader接口理解

Spring的Resource接口位于包org.springframework.core.io中;   Spring定义Resource接口是为了提供更强的访问底层资源能力的抽象;   对spring来说Resource接口代表着物理存在的任何资源。   先看一下resource接口及其实现的类层次关系图: 对应的UML类图为:  其中,最常用的有四个:

shadow_zed的博客 5428

ESP32-S3双屏AI吊坠:微型边缘智能终端设计实践

边缘智能终端是指在设备端完成数据感知、处理与响应的嵌入式系统,其核心在于低延迟、高隐私与弱网适应性。基于ESP32-S3等轻量级主控,通过语音唤醒、本地AI推理与双屏异步渲染等关键技术,可构建资源受限场景下的实用化人机交互节点。这类设计显著降低对云端依赖,提升实时性与数据安全性,广泛应用于智能穿戴、IoT交互面板及便携式语音设备。本文以‘璀璨·AI吊坠’为实例,深入解析双面圆形屏驱动、ES8311音频链路优化、VAD+Porcupine本地语音流水线,以及亚微安级低功耗状态机实现,为微型AI终端的软硬协同开

weixin_42348783的博客 632

Spring加载classpath与classpath*的过程剖析

好记忆不如烂笔头 ,能记点什么,就记点什么吧、 原文地址:http://blog.youkuaiyun.com/zl3450341/article/details/9306983 Spring加载Resource文件是通过ResourceLoader来进行的,那么我们就先来看看ResourceLoader的继承体系,让我们对这个模块有一个比较系统的认知。

草原孤狼的专栏 3271

保姆级教程:用Code Blocks搞定中科蓝讯AB5768E蓝牙音响SDK开发环境(附资源包)

本文提供了一份详细的Code Blocks配置指南,帮助开发者快速搭建中科蓝讯AB5768E蓝牙音响SDK开发环境。从工具链安装、环境配置到工程导入和调试技巧,涵盖全流程避坑要点,特别适合K12开发板初学者。附资源包下载,助您高效完成蓝牙音响开发。

weixin_28692817的博客 447

【QEMU系统分析之TPM篇(一)】

本文以 QEMU 8.2.2 为例,分析其 TPM-emulator 模块的实现,并为 QEMU 添加 Windows 平台的TPM 模拟器的支持。

David唐辉的博客 1187

资源——关于Spring加载classpath与classpath*的过程剖析(五)

本篇文章是由朋友的一篇博客引出的,博客原文地址:http://jinnianshilongnian.iteye.com/blog/1416322     他这篇博客比较细的讲解了classpath与classpath*,以及通配符的使用,那些配置能成功加载到资源,那些配置加载不了资源。但是我相信仍然有很多同学不明白,为什么是这样的,知其然,不知其所以然,那么本篇文章将慢慢为你揭开神秘的面纱,

u010502101的博客 745

相似贴子推荐:基于 LangChain4j + Milvus 的混合检索实战

本文提出了一种基于LangChain4j和Milvus的混合检索方案,用于解决论坛社区中的相似帖子推荐问题。该方案结合稠密向量语义检索和BM25关键词检索的优势,通过RRF算法实现结果融合。系统采用bge-small-zh模型生成语义向量,利用Milvus 2.5+原生支持的BM25功能和混合搜索API,在百万级数据规模下实现50ms内的检索延迟,召回率和精确率分别提升15%和22%。文章详细介绍了从数据预处理、向量化到混合检索的完整实现流程,并探讨了进阶优化方向,为推荐系统和RAG应用提供了高效解决方案。

分享AI、区块链技术及应用,未来已来 87

Spring中classpath和classpath*的问题

这一段是从网上抄录(原网址为 关于Spring加载classpath与classpath*的过程剖析) 1.无论是classpath还是classpath*都可以加载整个classpath下(包括jar包里面)的资源文件。 2.classpath只会返回第一个匹配的资源,查找路径是优先在项目中存在资源文件,再查找jar包。 3.文件名字包含通配符资源(如果spring-.xml,

另一个世界 2423

CAN总线介绍及硬件设计

1. CAN是什么 CAN 是 Controller Area Network 的缩写(以下称为 CAN),是 ISO*1 国际标准化的串行通信协议。 在当前的汽车产业中,出于对安全性、舒适性、方便性、低公害、低成本的要求,各种各样的电子控制系统 被开发了出来。由于这些系统之间通信所用的数据类型及对可靠性的要求不尽相同,由多条总线构成的情况很 多,线束的数量也随之增加。为适应“减少线束的数量”、“......

sinat_15677011的博客 9988

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Spring加载classpath与classpath*的过程

Spring加载Resource文件是通过ResourceLoader来进行的,先来看看ResourceLoader的继承体系。 本文重点分析左节点树资源解析器的源码。 1、ResourceLoader类,由下图可见,ResourceLoader接口只提供了classpath前缀的支持 2、接着看ResourceLoader子类ResourcePatternResolver,该类支持...

ly199108171231的博客 2919

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

Spring 源码深度剖析:ResourceLoader 的资源加载与路径解析策略

Spring ResourceLoader 核心机制解析 摘要:本文深入剖析了 Spring 框架中的 ResourceLoader 体系,重点分析了其资源加载与路径解析策略。ResourceLoader 作为资源定位字符串与具体 Resource 实例之间的桥梁,通过智能前缀识别和上下文敏感机制实现灵活的资源加载。 文章首先梳理了 ResourceLoader 的知识体系,包括核心接口(ResourceLoader、ResourcePatternResolver、ProtocolResolver)

chouchouxia的博客 502

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

Spring项目classpath深度解析:从JVM原理到自动装配实战

classpath是Java虚拟机(JVM)用于定位类和资源文件的搜索路径,它定义了JVM在运行时查找字节码和配置文件的目录与jar包集合。其核心原理在于类加载器(ClassLoader)会按照classpath指定的顺序扫描这些位置,从而完成资源的加载与类的初始化。理解classpath对于保障应用稳定性和部署灵活性至关重要,尤其在微服务架构下,它直接关系到配置加载、依赖管理以及第三方starter包的集成。在Spring Boot框架中,classpath不仅是资源访问的基础,更是实现“约定大于配置”和

weixin_31309419的博客 300

【Spring系列-06】Spring 资源加载机制

本文深入解析了Spring框架的资源加载机制。首先介绍了ResourceLoader接口的设计目的,通过统一抽象解决了不同来源资源加载的痛点。重点分析了DefaultResourceLoader的实现机制,它根据资源路径前缀自动选择ClassPathResource、UrlResource或FileSystemResource。文章还展示了加载类路径、文件系统和URL资源的示例代码,并通过类图展示了Resource接口的继承体系。最后讲解了AbstractResource模板方法模式的设计,以及不同资源实现

专注后端技术架构,AI技术分享 940
上一篇: Spring抽象的资源接口Resource的继承体系
下一篇: Solr学习之一 --------环境搭建
老张-AI
博客等级 码龄18年 1645粉丝 110原创
评论 9
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值