对Spring理解吗?手写一个Spring的Bean容器怎么样!


思考,输出,沉淀。用通俗的语言陈述技术,让自己和他人都有所收获。

作者:毅航😜


在前一章从简单的配置文件开始,重新审视Spring的上下文环境中,我们从Spring的原生使用方式开始着手,循序渐进的分析了Spring中的ClassPathXmlApplicationContext背后的工作原理。总结来看ClassPathXmlApplicationContextSpring框架中的一个应用上下文环境,此处你可将上下文简单理解为就是一个容器,后续随着理解的深入笔者会对这个上下文进行详细分析。进一步,ClassPathXmlApplicationContext主要用于从类路径(Classpath)下加载XML配置文件并完成初始化。此外,在调用ClassPathXmlApplicationContext的构造器时,其内部会完成如下逻辑:

  1. 加载配置文件ClassPathXmlApplicationContext的构造方法会接受一个或多个XML配置文件的类路径位置作为参数。这些配置文件包含了Spring应用程序的Bean定义和配置元数据。
  2. 解析XML文件 :一旦配置文件被加载,构造方法会解析XML文件,识别和解释XML中的各个元素,例如 <beans> <bean> 元素等。
  3. 创建并初始化Spring容器 :构造方法会创建一个Spring容器,用于管理和维护Bean信息。进一步,Spring容器会根据XML配置文件中定义的Bean信息创建相应的Bean实例,并根据配置文件中的属性和依赖关系进行初始化。

前言

经过前一章从简单的配置文件开始,重新审视Spring的上下文环境中对于ClassPathXmlApplicationContext的介绍和分析,相信你对于ClassPathXmlApplicationContext背后实现的功能其实已经有了一个清晰的认识。但如果我现在问你这样一个问题,根据之前的讲解,你亲自动手书写出一个ClassPathXmlApplicationContext吗? 换句话,现在如果让你来设计实现ClassPathXmlApplicationContext你该如何去做呢?

当你听到这个需求的时候肯定会虎躯一震,心想何必自己动手写呢?网上看一看与ClassPathXmlApplicationContext相关文章,完全可以花最短的时间了解ClassPathXmlApplicationContext的功能,何必这样劳神费力的动手亲自书写呢?

这其实也是笔者多年以前的想法,但是随着时间的不断流逝,随着阅历的不断增长,笔者发现,有些东西了解和学会之间其实是存在鸿沟的。 举个最简单的例子,设计模式想必大家或多或少肯定都听过,但你实际工作中会主动考虑设计模式吗?我想答案大概率是否定的,实际工作中更追求效率,而不会有太多时间去考虑设计模式的使用,当项目完成,看着堆积如山的屎山代码,优化更是不知从何处做起。最后,只能心中默默下定决定,下次一定提前规划善用设计模式。

如上的这些困境其实大部分程序员都会经历,那有什么办法解决呢?答案其实就是不断地阅读框架源码,然后不断地动手操练,水平提升本身也没什么捷径可走,正如卖油翁中油翁所言"无他,但手熟尔。" 只有你一次次不断地尝试,不断练习才能在用的时候信手拈来

其实说了这么多笔者想表达的无非就是当你了解到一个知识后,一定要注重实践!

拆解ClassPathXmlApplicationContext结构

有多动手的重要性,我们就说到这里了。接下来,不妨跟着笔者的思路来一步步分析,看看我们究竟该如何来实现一个ClassPathXmlApplicationContext

首先,我们来思考第一个问题。如果让你来动手写ClassPathXmlApplicationContext你会从何入手呢?我想你大概率会进入到Spring框架的源码。然后,点开ClassPathXmlApplicationContext类,研究其类结构信息,此时你大概率会得到如下这张图!

看到ClassPathXmlApplicationContext类这错综复杂的UML结构,想必你心中已经开始萌生退意了。那面对这样复杂结构有什么解决方法呢?答案其实笔者已经在编程学习的这些坑别在踩了有过介绍了,其核心无非就是 将精力集中在ClassPathXmlApplicationContext,而忘记掉其复杂的层级结果! 这样做的原因其实很简单,因为一个人的大脑在短时间内接受的东西是有限的,如果一次性记忆太多东西,只会让你更加焦虑,进而不断内耗!进一步,你可能会问那如果中方法很多,我们如何聚焦呢?答案其实也很简单,聚焦你学习时用到的API即可,这其实也是阅读源码的一个小技巧!

至此,你可能会问ClassPathXmlApplicationContext的重点是什么呢?回答这个问题之前,我们先来看如下这段代码:

java 复制代码
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构造方法逻辑如下:

java 复制代码
public ClassPathXmlApplicationContext(
      String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
      throws BeansException {
   // 调用父类构造
   super(parent);
   // 设定配置文件路径信息
   setConfigLocations(configLocations);
   // 刷新容器操作
   refresh();
   
}

之前我们其实已经在从简单的配置文件开始,重新审视Spring的上下文环境一文中对上述逻辑进行了分析,其无非完成如下几个任务

  • 设定配置文件路径信息,记录配置文件的路径信息,方便后续加载。

  • 容器刷新。其内部的一个重要操作步骤为构建容器,加载bean信息

构思ClassPathXmlApplicationContext实现

明白了ClassPathXmlApplicationContext内部关键API后,下一步就是构思如何实现ClassPathXmlApplicationContext了。我们先来看ClassPathXmlApplicationContext对外提供了哪些服务。

通过之前我们对于ClassPathXmlApplicationContext分析,可以知道ClassPathXmlApplicationContext提供的功能无非两件事,一件是加载配置文件,将配置信息中转化为一个bean对象,另一个则是提供获取bean对象的手段。

那配置文件通过什么配置呢?答案很简单通过xml文件进行配置。进一步,当配置文件解析完成后,通过什么结构来完成bean名称和实体对象间的映射呢?答案就是也很简单,无非就是通过哈希这个数据结构,具体到Java中你可以选择HashMap或者ConcurrentHashMap来进行实现。

上述的处理逻辑可以归纳为如下的图示信息:

此处,为了方便理解,再补充一点对于xml的相关知识的介绍。

XML(eXtensible Markup Language)是一种用于存储和传输数据的文本格式,它的结构被定义为一系列标签(标记)和数据。 其关键信息如下:

  1. XML声明XML文档通常以一个XML声明开始,用于指定XML版本和字符编码,例如:

    xml 复制代码
    <?xml version="1.0" encoding="UTF-8"?>

(注:这是XML的规范,每个XML文档开头必须为此!)

  1. 根元素 :每个XML文档必须包含一个根元素,它是XML文档的起始点和结束点。与XML标签结构十分类似。进一步,根元素包含其他元素,进而形成XML文档的结构。

  2. 元素 :元素是XML文档的基本构建块,由开始标签和结束标签组成。例如:

    xml 复制代码
    <book>
        <title>XML Basics</title>
        <author>John Doe</author>
    </book>
  3. 属性:元素可以包含属性,属性提供有关元素的附加信息。属性通常在开始标签中定义。例如:

    xml 复制代码
    <book language="English">...</book>

总结来看,XML的大致结构为一个根元素下嵌套多个子元素,从而形成一个文档结构。进一步,每个元素(包括根元素)都可以定义属性信息。 更进一步,对于XML文档的解析主要有DOMSAX解析两种方式,其中

  1. DOM解析 (Document Object Model)

    • DOM解析方式将整个XML文档加载到内存中,形成一个树状结构,每个元素都是一个节点。
    • 这种方式允许通过编程方式遍历和修改XML文档的内容,但可能占用大量内存。
  2. SAX解析 (Simple API for XML)

    • SAX解析方式是事件驱动的,它逐行读取XML文档,当遇到元素开始、元素结束、文本等事件时触发相应的处理方法。
    • 这种方式比DOM解析更节省内存,适用于大型文档,但难以直接修改XML文档。

动手实现ClassPathXmlApplicationContext

明白了项目的大致思路后,我们接下来开始动手实现。出于文章排版考虑,笔者仅会贴出关键部分代码,而并不会将详细代码全部粘贴出来。

首先,项目模块划分大致如下所示:

其中context包下为关键的ClassPathXmlApplicationContext,而在beans下定义了BeanFactory以及其实现类DefaultListableBeanFactory。至于,为何要定义BeanFactory可参考从简单的配置文件开始,重新审视Spring的上下文环境,此处便不再赘述。

接下来首先来看ClassPathXmlApplicationContext的自实现

java 复制代码
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);
    }
  // .... 省略其他无关逻辑
}

接下来对上述代码进行一个简单介绍

  1. 首先ClassPathXmlApplicationContext包含两个成员变量,一个为configLocations用以记录配置我文件的路径信息,另一则则为beanFactory其为一个bean工厂。
  2. 大致逻辑其实和ClassPathXmlApplicationContext逻辑基本类似,也是在构造方法内部调用refresh方法,然后构建一个bean工厂,进而通过loadBeanDefinitions加载配置文件信息。

有关更多相关实现细节可前往:gitee.com/ThanksCode/... 进行查看,再次便不再赘述了。

总结

至此,其实我们已经通过自身的努力实现了一个阉割版的ClassPathXmlApplicationContext。可能你会想,这也算实现吗?ClassPathXmlApplicationContext那么复杂结构你通过几行简单逻辑就实现了吗?有这样疑问的小伙伴你先别着急,不妨点个关注,给笔者一点时间,笔者会在接下来很长的一段时间内不断深入分析,进而不断迭代代码,相信经过多轮迭代后,ClassPathXmlApplicationContext不再是当初那个简单的样子,而会变得让你感到陌生!

时间不言,却给出了很多答案,请相信时间的力量,共勉~~~

相关推荐
m0_571957582 小时前
Java | Leetcode Java题解之第543题二叉树的直径
java·leetcode·题解
魔道不误砍柴功4 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
NiNg_1_2344 小时前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
闲晨4 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
Chrikk5 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*5 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue5 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man5 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang
测开小菜鸟5 小时前
使用python向钉钉群聊发送消息
java·python·钉钉
P.H. Infinity6 小时前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq