Spring资源加载模块,原来XML就这,活该被注解踩在脚下 手写Spring第六篇了
这一篇让我想起来学习 Spring 的时,被 XML 支配的恐惧。明明是写Java,为啥要搞个XML呢?大佬们永远不知道,我认为最难的是 XML 头,但凡 Spring 用 JSON来做配置文件,Java 界都有可能再诞生一个扛把子。
<?xml version="1.0" encoding="UTF-8"?>
那今天我就要来盘一下,突破自己的心里障碍。把 Spring XML 的底裤都给扒掉,最后会发现,原来每个人的身上都有毛毛。
设计一下子
首先想想应该怎么设计这个模块?BeanDefinitionRegistry 这个伙计是大门的保安,把守着资源加载的大门。他的口头禅就是:穿内裤者 或 不打领带者不得入内。

作为一个专业的前端后端运维测试攻城狮,高内聚低耦合必须手到擒来,面向接口编程更是基本操作。BeanDefinitionReader 接口就是我们的协议,只要符合这个接口协议都能进入我们的大门。

这可是比武招亲严格多了,你再有本事,不按照规矩来,那也是白搭。

BeanDefinitionReader 接口一放出去,有两个年轻人,三十多岁,一个叫 XmlBeanDefinitionReader,一个叫 ResouceLoader,他们说要试试。这两个年轻人不知道天高地厚,以为我的类图只有这么点。实际上我只是按照传统功夫的点到为止截图而已。

不仅如此,我还有流程图。

上次我太将武德了,右眼睛被人蹭了一下。今天我 18 岁老码农是乱打的,类图,流程图,还有一个不知道什么图,训练有素。现在我就是这么不讲武德,来骗,来偷袭年轻人。你们年轻人自己耗子尾汁。
实现一下子
首先,我们来看看门面担当BeanDefinitionReader:
public interface BeanDefinitionReader { void loadBeanDefinitions(String location) throws IOException;
}
这家伙就一个方法,简单得很!但是别小看它,这可是整个系统的总指挥!
然后是我们的主角 XmlBeanDefinitionReader:
public class XmlBeanDefinitionReader implements BeanDefinitionReader { private final BeanDefinitionRegistry beanDefinitionRegistry; public XmlBeanDefinitionReader(BeanDefinitionRegistry beanDefinitionRegistry) { this.beanDefinitionRegistry = beanDefinitionRegistry; } @Override public void loadBeanDefinitions(String location) throws IOException { ResourceLoader resourceLoader = new DefaultResourceLoader(); loadBeanDefinitions(resourceLoader.getResource(location)); } private void loadBeanDefinitions(Resource resource) throws IOException { InputStream inputSteam = resource.getInputSteam(); doLoadBeanDefinitions(inputSteam); } private void loadBeanDefinitions(Resource... resources) throws IOException { for (Resource resource : resources) { loadBeanDefinitions(resource); } } private void doLoadBeanDefinitions(InputStream inputStream) { Document document = XmlUtil.readXML(inputStream); Element root = document.getDocumentElement(); NodeList childNodes = root.getChildNodes(); for (int i = 0; i < childNodes.getLength(); i++) { Node item = childNodes.item(i); if (!(item instanceof Element)) continue; if (!"bean".equals(item.getNodeName())) continue; // bean 信息 Element element = (Element) item; String id = element.getAttribute("id"); String name = element.getAttribute("name"); String className = element.getAttribute("class"); String beanName = StrUtil.isNotBlank(id) ? id : name; Class<?> clazz; try { clazz = Class.forName(className); } catch (ClassNotFoundException e) { throw new BeanException(e.getMessage()); } PropertyValues propertyValues = new PropertyValues(); BeanDefinition beanDefinition = new BeanDefinition(clazz, propertyValues); // properties 信息 NodeList propertyNodes = element.getChildNodes(); for (int j = 0; j < propertyNodes.getLength(); j++) { Node property = propertyNodes.item(j); if (!(property instanceof Element)) continue; if (!"property".equals(property.getNodeName())) continue; Element propertyElement = (Element) property; String propertyName = propertyElement.getAttribute("name"); String value = propertyElement.getAttribute("value"); String ref = propertyElement.getAttribute("ref"); PropertyValue propertyValue; if (StrUtil.isNotBlank(ref)) { BeanReference beanReference = new BeanReference(ref); propertyValue = new PropertyValue(propertyName, beanReference); } else { propertyValue = new PropertyValue(propertyName, value); } propertyValues.addPropertyValues(propertyValue); } // 注册 beanDefinitionRegistry.registerBeanDefinition(beanName, beanDefinition); } }
}
资源加载皮条哥,就看你选的哪个小弟给你干活。目前他能 hold 住资源内容读取三兄弟。
public interface ResourceLoader { Resource getResource(String location);
}public class DefaultResourceLoader implements ResourceLoader { private final String CLASS_PATH_PREFIX = "classpath:"; @Override public Resource getResource(String location) { Objects.requireNonNull(location); if (location.startsWith(CLASS_PATH_PREFIX)) { String name = location.substring(CLASS_PATH_PREFIX.length()); return new ClassPathResource(name, getClassLoader()); } try { URL url = new URL(location); return new UrlResource(url); } catch (MalformedURLException e) { return new FileSystemResource(location); } } private ClassLoader getClassLoader() { ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); if (contextClassLoader != null) { return contextClassLoader; } return ClassUtil.class.getClassLoader(); }
}
资源内容读取三兄弟:
ClassPathResource:专门找项目里的文件FileSystemResource:负责找电脑里的文件UrlResource:负责找网上的资源文件
public interface Resource { InputStream getInputSteam() throws IOException;
}
public class ClassPathResource implements Resource { private final String name; private final ClassLoader classLoader; public ClassPathResource(String name, ClassLoader classLoader) { Objects.requireNonNull(name); this.name = name; this.classLoader = classLoader; } @Override public InputStream getInputSteam() throws IOException { InputStream inputStream = classLoader.getResourceAsStream(name); if (Objects.isNull(inputStream)){ throw new FileNotFoundException("Not found this file: "+ name); } return inputStream; }
}
public class FileSystemResource implements Resource { private final String path; private final File file; public FileSystemResource(String path) { this.path = path; this.file = new File(path); } @Override public InputStream getInputSteam() throws IOException { return Files.newInputStream(file.toPath()); } public String getPath() { return path; }
}
public class UrlResource implements Resource { private final URL url; public UrlResource(URL url) { this.url = url; } @Override public InputStream getInputSteam() throws IOException { URLConnection connection = url.openConnection(); try { return connection.getInputStream(); } catch (IOException e) { if (connection instanceof HttpURLConnection) { ((HttpURLConnection) connection).disconnect(); } throw e; } }
}
测试一下子
首先准备一张菜单吗?告诉Spring:
- 老板,来一个testDao
- 再来一个testService,要加karl调料,顺便把刚才的testDao也放进去
<?xml version="1.0" encoding="UTF-8"?>
<beans> <bean id="testDao" class="pri.hongweihao.smallspring.bean.TestDao"/> <bean id="testService" class="pri.hongweihao.smallspring.bean.TestService"> <property name="name" value="karl"/> <property name="testDao" ref="testDao"/> </bean>
</beans>
// 模拟dao对象
public class TestDao { public void test() { System.out.print("testDao"); }
}// 模拟service对象
public class TestService { private final String name; private final TestDao testDao; public TestService(String name, TestDao testDao) { this.name = name; this.testDao = testDao; } public void test() { System.out.println("testService.name: " + this.name); this.testDao.test(); }
}
开干,我玩的就是真实
public class BeanFactoryTest { @Test public void test() throws IOException { DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory(); // 读取配置文件并自动注册 BeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(defaultListableBeanFactory); beanDefinitionReader.loadBeanDefinitions("classpath:spring.xml"); // 从工厂中获取bean对象 TestService service = (TestService) defaultListableBeanFactory.getBean("testService", "", null); service.test(); /* 打印结果 testService.name: karl testDao */ }
}
搞定!是不是感觉XML也没那么可怕了?
总结
XML配置其实就是一张"菜单":
- Spring通过【资源】和【资源加载】帮我们找到这些配置文件
- 然后通过大厨
XmlBeanDefinitionReader解析配置 - 最后把"菜"(Bean)放到"厨房"(容器)里
本文由 https://github.com/hongweihao/small-spring/tree/6_resource_load 赞注完成
本文完 | 求赞求关注求转发 !

相关文章:
Spring资源加载模块,原来XML就这,活该被注解踩在脚下 手写Spring第六篇了
这一篇让我想起来学习 Spring 的时,被 XML 支配的恐惧。明明是写Java,为啥要搞个XML呢?大佬们永远不知道,我认为最难的是 XML 头,但凡 Spring 用 JSON来做配置文件,Java 界都有可能再诞生一个扛把子。 <…...
[运维][Nginx]Nginx学习(2/5)-Nginx高级
Nginx服务器基础配置实例 前面我们已经对Nginx服务器默认配置文件的结构和涉及的基本指令做了详细的阐述。通过这些指令的合理配置,我们就可以让一台Nginx服务器正常工作,并且提供基本的web服务器功能。 接下来我们将通过一个比较完整和最简单的基础配…...
【快捷入门笔记】mysql基本操作大全-SQL数据库
SQL数据库 一、创建数据库 – 创建一个新数据库 fang_fang CREATE DATABASE fang_fang;– 显示所有数据库以确认创建 SHOW DATABASES;– 使用新数据库fang_fang USE fang_fang;– 检查我们正在使用哪个数据库 SELECT DATABASE();二、 删除数据库 –当你确定数据库存在并…...
【LeetCode】【算法】15. 三数之和
LeetCode 15. 三数之和 题目描述 给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k ,同时还满足 nums[i] nums[j] nums[k] 0 。请你返回所有和为 0 且不重复的三元组。 注意:答案中不…...
传输协议设计与牧村摆动(Makimoto‘s Wave)
有一条活鱼和一条死鱼,你准备怎么做,你会将活鱼红烧或将死鱼清蒸吗?好的食材只需要最简单的烹饪,不好的食材才需要花活儿。 我此前的文字几乎都在阐述一个观点,广域网就是那条死鱼,数据中心则是那条活鱼。…...
JMeter进阶篇
目录 上篇导航: 总目录: 一、逻辑控制器: 1.逻辑控制器和关联: 2.if逻辑控制器: 3.forEach控制器: 4.循环控制器: 二、关联: 1.xpath: 2.正则表达式提取器&…...
LabVIEW编程基础教学(一)--介绍
LabVIEW(Laboratory Virtual Instrument Engineering Workbench)是一种基于图形化编程的开发环境,专为工程应用、测试、测量、控制系统等设计。与传统的文本编程语言不同,LabVIEW 使用图形化的方式通过“数据流”模型来表示程序逻…...
HVV蓝队基础
免责声明 学习视频来自B 站up主泷羽sec,如涉及侵权马上删除文章。 笔记的只是方便各位师傅学习知识,以下代码、网站只涉及学习内容,其他的都与本人无关,切莫逾越法律红线,否则后果自负。 企业网络架构 企业技术和信…...
[运维][Nginx]Nginx学习(1/5)--Nginx基础
Nginx简介 背景介绍 Nginx一个具有高性能的【HTTP】和【反向代理】的【WEB服务器】,同时也是一个【POP3/SMTP/IMAP代理服务器】,是由伊戈尔赛索耶夫(俄罗斯人)使用C语言编写的,Nginx的第一个版本是2004年10月4号发布的0.1.0版本。另外值得一…...
创客节小学组C++模拟题
来源:加码未来2024年深圳罗湖区创客节模拟题(小学组) 第一题 题目描述 给你n个数,找出出现次数超过一半的数。题目保证这样的数一定存在。 输入格式 第一行一个整数n,(n<=1000) 第二行n个整数(<1000000) 输出格式 输出一个整数 样例输入 5 1 2 3 3 3 样例输…...
阿里云ECS服务器使用限制及不允许做的事情
阿里云ECS(Elastic Compute Service)是一种高性能的弹性计算服务,允许用户在云端创建和管理虚拟服务器。尽管ECS提供了强大的功能,但在使用过程中,阿里云有一些限制和不允许的行为。以下是一些主要的使用限制和禁止行为…...
Linux开发讲课49--- Linux 启动过程分析
理解运转良好的系统对于处理不可避免的故障是最好的准备。 启动过程非常简单。内核在单核上以单线程和同步状态启动,似乎可以理解。但内核本身是如何启动的呢?initrd(initial ramdisk) 和引导程序(bootloader)具有哪些功能&#…...
Java-03
目录 算法 1.小美的因子查询 2.小美的密码 3.小美的数组删除 4.小美和大富翁 知识点 InnoDB中的行级锁是怎么实现的? 介绍一下Java中的IO流 讲讲Java的跨平台原理 COUNT(1)与COUNT(*)区别 Redis 为什么要用…...
微积分复习笔记 Calculus Volume 1 - 5.3 The Fundamental Theorem of Calculus
5.3 The Fundamental Theorem of Calculus - Calculus Volume 1 | OpenStax...
c++如何绑定一个类与类内成员的关系
在 C 中,成员函数和成员变量的归属关系(即某个成员属于哪个类)是通过编译器的多种机制和语言特性来实现和管理的。理解这些机制有助于更深入地掌握 C 的面向对象特性、内存管理以及编译过程。以下是 C 如何确定某个成员函数或成员变量属于特定…...
关于解决使用VMWare内的虚拟机无法识别USB问题小结
目录 前言 0. 查看是不是没有开启USB3.0的支持 1. 检查一下是否禁用了VMWare USB服务 2. 无奈之举 前言 笔者今天帮助一位同志解决了VMWare内的虚拟机不识别挂载设备的办法。这里对笔者使用的排查手段做一个总结。 0. 查看是不是没有开启USB3.0的支持 我们的第一件事情就…...
抢抓5G机遇,AORO A23防爆手机如何直击园区巡检挑战?
矗立在沙漠高原的铁塔,遍布都市的电线网络,远离郊区的海港油田……大型园区对智能巡检提出了新的需求,选择一款智能且高效的巡检设备,以确保园区高效运营,成为了管理者关注的重点。在调研多个智慧园区后,小…...
索引【MySQL】
文章目录 聚簇索引 VS 非聚簇索引索引MySQL与磁盘交互的基本单位主键索引索引操作唯一索引的创建普通索引的创建复合索引 索引创建原则 聚簇索引 VS 非聚簇索引 MyISAM存储引擎 - 主键索引结构 MyISAM存储引擎同样采用B树作为索引的基本数据结构 与InnoDB存储引擎的B树不同的…...
【Allure】mac下环境配置
安装 1.Mac 可以使用 brew 安装 allure,安装命令如下 brew install allure 2.与 pytest 结合需要安装 allure-pytest 插件: pip install allure-pytest3.查看allure版本 allure --version...
Android 开启混淆R8编译问题处理
Android R8是一个代码混淆和压缩工具,可以将应用程序的大小和安全性优化。它引入了一些新功能,如成员内省、混淆指针、类内省等。 但R8使用起来一直不友好,因为自从使用R8之后编译问题不断。主要还是和混淆相关,经常报错ÿ…...
SEO网站广告如何与本地化营销相结合
SEO网站广告与本地化营销的结合:如何提升本地企业的市场竞争力 在当今数字化经济的浪潮中,SEO网站广告和本地化营销已经成为企业营销的两大重要手段。如何将这两者有机地结合,以实现最大的营销效益,是许多企业面临的重要课题。本…...
OpenClaw二次开发指南:Qwen3.5-9B模型适配与API扩展
OpenClaw二次开发指南:Qwen3.5-9B模型适配与API扩展 1. 为什么需要二次开发OpenClaw? 去年冬天,当我第一次尝试用OpenClaw对接本地部署的Qwen3.5-9B模型时,遇到了几个棘手问题:模型返回的JSON格式与框架预期不符、长…...
Java虚拟线程调试黄金组合:jstack -l + jcmd VM.native_memory + JMC Thread Group视图(生产环境零侵入诊断法)
第一章:Java虚拟线程调试黄金组合:jstack -l jcmd VM.native_memory JMC Thread Group视图(生产环境零侵入诊断法)虚拟线程(Virtual Threads)作为 Project Loom 的核心特性,在高并发场景下显著…...
Shell编程避坑指南:为什么你的while循环总出问题?7个常见错误排查
Shell编程避坑指南:为什么你的while循环总出问题?7个常见错误排查 在Shell脚本开发中,while循环是处理未知迭代次数的利器,但也是错误的高发区。很多开发者在使用while时经常遇到脚本卡死、逻辑异常或结果不符合预期等问题。本文将…...
如何轻松获取网页媒体资源?猫抓开源工具让资源提取效率提升3倍
如何轻松获取网页媒体资源?猫抓开源工具让资源提取效率提升3倍 【免费下载链接】cat-catch 猫抓 浏览器资源嗅探扩展 / cat-catch Browser Resource Sniffing Extension 项目地址: https://gitcode.com/GitHub_Trending/ca/cat-catch 你是否曾在浏览网页时遇…...
嵌入式系统电源时序控制原理与实现
1. 电源时序控制基础概念在现代电子系统中,多电压域设计已成为常态。一个典型的嵌入式系统可能同时需要1.2V(核心逻辑)、3.3V(外设接口)和1.5V(特殊功能模块)等多种电压。这些电源的上电顺序对系…...
OpenClaw多模态聊天机器人:Qwen2.5-VL-7B实现图片问答与表情包生成
OpenClaw多模态聊天机器人:Qwen2.5-VL-7B实现图片问答与表情包生成 1. 为什么选择OpenClaw构建多模态聊天机器人 去年我在运营一个技术社群时,经常遇到群成员发截图提问的场景。传统聊天机器人要么只能处理文字,要么需要将图片上传到第三方…...
告别‘千人千脑’:用DMMR模型搞定EEG情感识别的跨被试难题(附PyTorch代码)
突破脑电情感识别的个体差异壁垒:DMMR模型实战指南与PyTorch实现 当你在实验室里看着屏幕上跳动的脑电波形时,是否曾为不同受试者数据间的巨大差异而头疼?这种被称为"脑电指纹"的个体特异性,一直是情感识别领域最棘手的…...
若依框架实战:如何优雅地实现静态资源权限校验(附完整代码)
若依框架静态资源权限校验实战指南 在企业级应用开发中,静态资源的安全访问控制是一个常见需求。无论是小程序图片资源管理,还是企业内部文档权限控制,都需要确保只有授权用户才能访问特定资源。本文将深入探讨如何在若依(RuoYi)框架中实现静…...
新手福音:在快马平台开启你的云端代码编程第一课
作为一名刚接触编程的新手,我最近发现了一个特别适合入门的学习方式——云端代码编程。以前总觉得学编程要先装一堆软件、配置环境,光是这些准备工作就能劝退不少人。但在InsCode(快马)平台上,这些烦恼都不存在了。 零门槛的编程初体验 打开平…...
