对Spring理解吗?手写一个Spring的Bean容器怎么样!
思考,输出,沉淀。用通俗的语言陈述技术,让自己和他人都有所收获。 作者:毅航😜
- 加载配置文件:
ClassPathXmlApplicationContext
的构造方法会接受一个或多个XML
配置文件的类路径位置作为参数。这些配置文件包含了Spring
应用程序的Bean
定义和配置元数据。 - 解析XML文件:一旦配置文件被加载,构造方法会解析
XML
文件,识别和解释XML
中的各个元素,例如<beans> <bean>
元素等。 - 创建并初始化Spring容器:构造方法会创建一个
Spring
容器,用于管理和维护Bean
信息。进一步,Spring
容器会根据XML
配置文件中定义的Bean
信息创建相应的Bean
实例,并根据配置文件中的属性和依赖关系进行初始化。
前言
当你听到这个需求
的时候肯定会虎躯
一震,心想何必自己动手写呢?网上看一看与ClassPathXmlApplicationContext
相关文章,完全可以花最短的时间了解ClassPathXmlApplicationContext
的功能,何必这样劳神费力的动手亲自书写呢?
这其实也是笔者多年以前的想法,但是随着时间的不断流逝,随着阅历的不断增长,笔者发现,有些东西了解和学会之间其实是存在鸿沟的。 举个最简单的例子,设计模式
想必大家或多或少肯定都听过,但你实际工作中会主动考虑设计模式
吗?我想答案大概率是否定的,实际工作中更追求效率,而不会有太多时间去考虑设计模式
的使用,当项目完成,看着堆积如山的屎山
代码,优化更是不知从何处做起。最后,只能心中默默下定决定,下次一定提前规划
善用设计模式。
如上的这些困境其实大部分程序员都会经历,那有什么办法解决呢?答案其实就是不断地阅读框架源码
,然后不断地动手操练
,水平提升本身也没什么捷径可走,正如卖油翁中油翁所言“无他,但手熟尔。” 只有你一次次不断地尝试,不断练习才能在用的时候信手拈来
。
其实说了这么多笔者想表达的无非就是当你了解到一个知识后,一定要注重实践
!
拆解ClassPathXmlApplicationContext
结构
有多动手
的重要性,我们就说到这里了。接下来,不妨跟着笔者的思路来一步步分析,看看我们究竟该如何来实现一个ClassPathXmlApplicationContext
。
首先,我们来思考第一个问题。如果让你来动手写ClassPathXmlApplicationContext
你会从何入手呢?我想你大概率会进入到Spring
框架的源码。然后,点开ClassPathXmlApplicationContext
类,研究其类结构信息,此时你大概率会得到如下这张图!
至此,你可能会问ClassPathXmlApplicationContext
的重点是什么呢?回答这个问题之前,我们先来看如下这段代码:
public class MyApplicationContext {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 从容器中获取Bean
Car car = context.getBean("car", Car.class);
// 使用car对象的相关方法
car.doSomething();
}
}
可以看到,其中有关ClassPathXmlApplicationContext
的重要
方法其实只有两个,一个是ClassPathXmlApplicationContext
的构造方法,另一个则是其中的getBean
方法。进一步来看ClassPathXmlApplicationContext
构造方法逻辑如下:
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
// 调用父类构造
super(parent);
// 设定配置文件路径信息
setConfigLocations(configLocations);
// 刷新容器操作
refresh();
}
-
设定配置文件路径信息,记录配置文件的路径信息,方便后续加载。
-
容器刷新。其内部的一个重要操作步骤为构建容器,加载
bean
信息。
构思ClassPathXmlApplicationContext
实现
明白了ClassPathXmlApplicationContext
内部关键API
后,下一步就是构思如何实现ClassPathXmlApplicationContext
了。我们先来看ClassPathXmlApplicationContext
对外提供了哪些服务。
通过之前我们对于ClassPathXmlApplicationContext
分析,可以知道ClassPathXmlApplicationContext
提供的功能无非两件事,一件是加载配置文件,将配置信息中转化为一个bean
对象,另一个则是提供获取bean
对象的手段。
那配置文件通过什么配置呢?答案很简单通过xml
文件进行配置。进一步,当配置文件解析完成后,通过什么结构来完成bean
名称和实体对象
间的映射
呢?答案就是也很简单,无非就是通过哈希
这个数据结构,具体到Java
中你可以选择HashMap
或者ConcurrentHashMap
来进行实现。
上述的处理逻辑可以归纳为如下的图示信息:
此处,为了方便理解,再补充一点对于xml
的相关知识的介绍。
XML(eXtensible Markup Language)
是一种用于存储和传输数据的文本格式,它的结构被定义为一系列标签(标记)和数据。 其关键信息如下:
-
XML声明:
XML
文档通常以一个XML
声明开始,用于指定XML
版本和字符编码,例如:<?xml version="1.0" encoding="UTF-8"?>
(注:这是XML
的规范,每个XML
文档开头必须为此!)
-
根元素:每个
XML
文档必须包含一个根元素,它是XML
文档的起始点和结束点。与XML
标签结构十分类似。进一步,根元素包含其他元素,进而形成XML
文档的结构。 -
元素:元素是
XML
文档的基本构建块,由开始标签和结束标签组成。例如:<book> <title>XML Basics</title> <author>John Doe</author> </book>
-
属性:元素可以包含属性,属性提供有关元素的附加信息。属性通常在开始标签中定义。例如:
<book language="English">...</book>
总结来看,XML
的大致结构为一个根元素
下嵌套多个子元素
,从而形成一个文档结构。进一步,每个元素(包括根元素)都可以定义属性信息。 更进一步,对于XML
文档的解析主要有DOM
和SAX
解析两种方式,其中
-
DOM解析
(Document Object Model)
:DOM
解析方式将整个XML
文档加载到内存中,形成一个树状结构,每个元素都是一个节点。- 这种方式允许通过编程方式遍历和修改
XML
文档的内容,但可能占用大量内存。
-
SAX解析
(Simple API for XML)
:SAX
解析方式是事件驱动的,它逐行读取XML
文档,当遇到元素开始、元素结束、文本等事件时触发相应的处理方法。- 这种方式比
DOM
解析更节省内存,适用于大型文档,但难以直接修改XML
文档。
动手实现ClassPathXmlApplicationContext
明白了项目的大致思路后,我们接下来开始动手实现。出于文章排版考虑,笔者
仅会贴出关键部分代码,而并不会将详细代码全部粘贴出来。
首先,项目模块划分大致如下所示:
接下来首先来看ClassPathXmlApplicationContext
的自实现
public class ClassPathXmlApplicationContext implements BeanFactory {
private String[] configLocations;
private BeanFactory beanFactory;
/**
* 从 XML 中加载 BeanDefinition,并刷新上下文
*
* @param configLocations
* @throws BeansException
*/
public ClassPathXmlApplicationContext(String configLocations) throws BeansException {
this(new String[]{configLocations});
}
/**
* 从 XML 中加载 BeanDefinition,并刷新上下文
* @param configLocations
* @throws BeansException
*/
public ClassPathXmlApplicationContext(String[] configLocations) throws BeansException {
// 设定加载路径
this.configLocations = configLocations;
// 刷新容器信息
refresh();
}
/**
* @Description: 刷新容器处理
* @param
* @return void
*/
private void refresh() {
// 构建工厂
this.beanFactory = new DefaultListableBeanFactory();
// 加载配置文件,解析bean
loadBeanDefinitions(beanFactory);
}
// .... 省略其他无关逻辑
}
接下来对上述代码进行一个简单介绍
- 首先
ClassPathXmlApplicationContext
包含两个成员变量,一个为configLocations
用以记录配置我文件的路径信息,另一则则为beanFactory
其为一个bean
工厂。 - 大致逻辑其实和
ClassPathXmlApplicationContext
逻辑基本类似,也是在构造方法内部调用refresh
方法,然后构建一个bean
工厂,进而通过loadBeanDefinitions
加载配置文件信息。
有关更多相关实现细节可前往:gitee.com/ThanksCode/… 进行查看,再次便不再赘述了。
总结
至此,其实我们已经通过自身的努力实现了一个阉割
版的ClassPathXmlApplicationContext
。可能你会想,这也算实现吗?ClassPathXmlApplicationContext
那么复杂结构你通过几行简单逻辑就实现了吗?有这样疑问的小伙伴
你先别着急,不妨点个关注,给笔者一点时间,笔者会在接下来很长的一段时间内不断深入分析,进而不断迭代代码,相信经过多轮迭代后,ClassPathXmlApplicationContext
不再是当初那个简单的样子,而会变得让你感到陌生!
时间不言,却给出了很多答案,请相信时间的力量,共勉~~~
转载自:https://juejin.cn/post/7289692200173125692