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之后编译问题不断。主要还是和混淆相关,经常报错ÿ…...
【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型
摘要 拍照搜题系统采用“三层管道(多模态 OCR → 语义检索 → 答案渲染)、两级检索(倒排 BM25 向量 HNSW)并以大语言模型兜底”的整体框架: 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后,分别用…...
变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析
一、变量声明设计:let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性,这种设计体现了语言的核心哲学。以下是深度解析: 1.1 设计理念剖析 安全优先原则:默认不可变强制开发者明确声明意图 let x 5; …...
MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例
一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...
FFmpeg 低延迟同屏方案
引言 在实时互动需求激增的当下,无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作,还是游戏直播的画面实时传输,低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架,凭借其灵活的编解码、数据…...
Linux简单的操作
ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...
条件运算符
C中的三目运算符(也称条件运算符,英文:ternary operator)是一种简洁的条件选择语句,语法如下: 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true,则整个表达式的结果为“表达式1”…...
深入理解JavaScript设计模式之单例模式
目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式(Singleton Pattern&#…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序
一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...
iOS性能调优实战:借助克魔(KeyMob)与常用工具深度洞察App瓶颈
在日常iOS开发过程中,性能问题往往是最令人头疼的一类Bug。尤其是在App上线前的压测阶段或是处理用户反馈的高发期,开发者往往需要面对卡顿、崩溃、能耗异常、日志混乱等一系列问题。这些问题表面上看似偶发,但背后往往隐藏着系统资源调度不当…...
嵌入式学习笔记DAY33(网络编程——TCP)
一、网络架构 C/S (client/server 客户端/服务器):由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序,负责提供用户界面和交互逻辑 ,接收用户输入,向服务器发送请求,并展示服务…...
