(一)实现一个简易版IoC容器【手撸Spring】

news2025/1/11 18:49:38

一、前言

相信大家在看本篇文章的时候,对IoC应该有一个比较清晰的理解,我在这里再重新描述下:它的作用就是实现一个容器将一个个的Bean(这里的Bean可以是一个Java的业务对象,也可以是一个配置对象)统一管理起来。在Java中,我们创建一个对象最简单的方法是使用new关键字。Spring框架的IoC容器则是将创建Bean的动作与使用Bean解耦,将应用层程序员无需关注底层对象的构建以及其生命周期,以便更好的专注于业务开发。

本节我们则开始进入手写Spring框架的第一步:实现一个最简易的IoC容器。

二、一个最简易的IoC容器

1、简易流程

IoC简易流程

我们在面向Spring框架开发时,想要使用一个Bean时,通常会将bean的一些元信息配置在xml文件中(也可以通过注解),Spring IoC容器会加载指定路径的xml文件,将其进一步解析成BeanDefinition并存储到IoC容器中,当我们(应用层)去获取Bean实例(通过getBean)时,如果该Bean没有被初始化,则会触发Bean实例创建的动作,创建实例由反射实现。

2、简易功能下UML图

简易IoC-UML图

3、相关代码

BeanDefinition
package com.tiny.spring.beans.factory.config;

/**
 * @author: markus
 * @date: 2023/10/7 8:14 PM
 * @Description: Bean配置元信息
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class BeanDefinition {
    private String id;
    private String className;

    public BeanDefinition() {
    }

    public BeanDefinition(String id, String className) {
        this.id = id;
        this.className = className;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }
}
ClassPathXmlApplicationContext
package com.tiny.spring.context.support;

import com.tiny.spring.beans.factory.config.BeanDefinition;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author: markus
 * @date: 2023/10/7 8:16 PM
 * @Description: 基于xml的Spring应用上下文
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class ClassPathXmlApplicationContext {
    private List<BeanDefinition> beanDefinitions = new ArrayList<>();
    private Map<String, Object> singletons = new HashMap<>();

    public ClassPathXmlApplicationContext(String pathname) {
        this.readXml(pathname);
        this.instanceBeans();
    }

    private void readXml(String pathname) {
        SAXReader saxReader = new SAXReader();
        try {
            URL xmlPath = this.getClass().getClassLoader().getResource(pathname);
            Document document = saxReader.read(xmlPath);
            Element rootElement = document.getRootElement();
            // 对配置文件的每一个<bean>标签进行处理
            for (Element element : rootElement.elements()) {
                // 获取Bean的基本信息
                String beanId = element.attributeValue("id");
                String beanClassName = element.attributeValue("class");
                BeanDefinition beanDefinition = new BeanDefinition(beanId, beanClassName);
                // 将Bean的定义存放到BeanDefinition
                beanDefinitions.add(beanDefinition);
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        }
    }

    /**
     * 利用反射创建Bean实例,并存储在singletons中
     */
    private void instanceBeans() {
        for (BeanDefinition beanDefinition : beanDefinitions) {
            try {
                singletons.put(beanDefinition.getId(), Class.forName(beanDefinition.getClassName()).newInstance());
            } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 对外提供的方法,让外部程序获取Bean实例
     * @param beanName
     * @return
     */
    public Object getBean(String beanName) {
        return singletons.get(beanName);
    }
}
测试类
package com.tiny.spring.test;

import com.tiny.spring.beans.BeansException;
import com.tiny.spring.context.support.ClassPathXmlApplicationContext;
import com.tiny.spring.test.service.AService;

/**
 * @author: markus
 * @date: 2023/10/7 8:37 PM
 * @Description: 最原始的IoC容器功能测试
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class OriginalIoCContainerTest {
    public static void main(String[] args) throws BeansException {
        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("beans.xml");
        AService aService = (AService) classPathXmlApplicationContext.getBean("aService");
        aService.sayHello();
    }
}

控制台

三、单一职责原则

我们可以看到,上面的ClassPathXmlApplicationContext承担了太多的功能,这不符合对象单一职责原则。

原本ClassPathXmlApplicationContext既承担了对外提供Bean实例访问,对内进行配置文件的加载并解析成BeanDefinition存储起来以及进行Bean实例的创建操作。

我们需要对此进行优化,将IoC容器的核心功能(Bean实例访问+BeanDefinition注册)和外部信息的访问剥离出来。

1、优化后的UML类图

单一职责优化后的UML图

2、相关代码

Resource
package com.tiny.spring.core.io;

import java.util.Iterator;

/**
 * @author: markus
 * @date: 2023/10/7 8:45 PM
 * @Description: 外部的配置信息抽象
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public interface Resource extends Iterator<Object> {
}
ClassPathXmlResource
package com.tiny.spring.core.io;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.net.URL;
import java.util.Iterator;

/**
 * @author: markus
 * @date: 2023/10/7 8:47 PM
 * @Description:
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class ClassPathXmlResource implements Resource {

    Document document;
    Element rootElement;
    Iterator<Element> elementIterator;

    public ClassPathXmlResource(String pathname) {
        SAXReader saxReader = new SAXReader();
        URL xmlPath = this.getClass().getClassLoader().getResource(pathname);
        try {
            this.document = saxReader.read(xmlPath);
            this.rootElement = document.getRootElement();
            this.elementIterator = this.rootElement.elementIterator();
        } catch (DocumentException e) {
            e.printStackTrace();
        }
    }

    @Override
    public boolean hasNext() {
        return this.elementIterator.hasNext();
    }

    @Override
    public Object next() {
        return this.elementIterator.next();
    }
}
XmlBeanDefinitionReader
package com.tiny.spring.beans.factory.xml;

import com.tiny.spring.beans.factory.BeanFactory;
import com.tiny.spring.beans.factory.config.BeanDefinition;
import com.tiny.spring.core.io.Resource;
import org.dom4j.Element;

/**
 * @author: markus
 * @date: 2023/10/7 8:50 PM
 * @Description:
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class XmlBeanDefinitionReader {
    BeanFactory beanFactory;

    public XmlBeanDefinitionReader(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    public void loadBeanDefinitions(Resource resource) {
        while (resource.hasNext()) {
            Element element = (Element) resource.next();
            String beanId = element.attributeValue("id");
            String className = element.attributeValue("class");
            BeanDefinition beanDefinition = new BeanDefinition(beanId, className);
            this.beanFactory.registerBeanDefinition(beanDefinition);
        }
    }
}
BeanFactory
package com.tiny.spring.beans.factory;

import com.tiny.spring.beans.BeansException;
import com.tiny.spring.beans.factory.config.BeanDefinition;

/**
 * @author: markus
 * @date: 2023/10/7 8:43 PM
 * @Description: IoC底层容器的根类
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public interface BeanFactory {
    /**
     * 根据beanName获取Bean实例
     * @param beanName
     * @return
     * @throws BeansException
     */
    Object getBean(String beanName) throws BeansException;

    /**
     * 注册Bean配置元信息
     * @param beanDefinition
     */
    void registerBeanDefinition(BeanDefinition beanDefinition);
}
ClassPathXmlApplicationContext
package com.tiny.spring.context.support;

import com.tiny.spring.beans.BeansException;
import com.tiny.spring.beans.factory.BeanFactory;
import com.tiny.spring.beans.factory.config.BeanDefinition;
import com.tiny.spring.beans.factory.support.SimpleBeanFactory;
import com.tiny.spring.beans.factory.xml.XmlBeanDefinitionReader;
import com.tiny.spring.core.io.ClassPathXmlResource;
import com.tiny.spring.core.io.Resource;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author: markus
 * @date: 2023/10/7 8:16 PM
 * @Description: 基于xml的Spring应用上下文
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class ClassPathXmlApplicationContext implements BeanFactory {
    BeanFactory beanFactory;

    public ClassPathXmlApplicationContext(String pathname) {
        Resource resource = new ClassPathXmlResource(pathname);
        BeanFactory beanFactory = new SimpleBeanFactory();
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
        reader.loadBeanDefinitions(resource);
        this.beanFactory = beanFactory;
    }

    /**
     * 对外提供的方法,让外部程序获取Bean实例
     *
     * @param beanName
     * @return
     */
    public Object getBean(String beanName) throws BeansException {
        return this.beanFactory.getBean(beanName);
    }

    @Override
    public void registerBeanDefinition(BeanDefinition beanDefinition) {
        this.beanFactory.registerBeanDefinition(beanDefinition);
    }
}
功能验证

四、本文总结

可以看到,经过上述构建,我们在使用一个对象时不需要再去手动new一个了,只需要进行一些简单的配置将其交给框架容器去管理就可以获取我们所需的对象。通过功能解耦,我们定义出以下几个核心类:

  • BeanFactory : 底层根容器
  • SimpleBeanFactory : 容器的实现类
  • ClassPathXmlApplicationContext : 应用层容器,交付给上层程序调用
  • Resource : 外部资源对象抽象
  • ClassPathXmlResource : 外部Xml资源对象
  • XmlBeanDefinitionLoader : xml文件加载,并解析为BeanDefinition

通过功能解耦,我们后续对容器进行扩展时就会更方便,适配更多的场景。

就这样,一个简易的IoC容器就实现了,接下来我们就一步一步的将其他功能添加上去,让一颗小树苗发育成一个参天大树。

完整代码参见:https://github.com/markuszcl99/tiny-spring

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1067871.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

无需公网IP,教学系统如何实现远程一站式管理维护?

全国多所高校应用红亚科技研发的一套教学实验系统平台&#xff0c;此实验系统服务器分别部署在学校内部&#xff0c;与校内的各种教学资源整合在一起&#xff0c;向校内师生提供服务。 红亚总部设立在北京&#xff0c;虽说在全国22个省会均设有办事处&#xff0c;在面对全国多…

MM-Camera架构-Preview 流程分析

目录 文章目录 1 log开的好&#xff0c;问题都能搞2 lib3 preview3.1 打开视频流3.1.1 cpp\_module\_start\_session3.1.2 cpp\_thread\_create3.1.3 cpp\_thread\_funcsundp-3.1 cpp\_hardware\_open\_subdev(ctrl->cpphw)sundp-3.2 cpp\_hardware\_process\_command(ctrl-…

ssm+vue的劳务外包管理系统(有报告)。Javaee项目,ssm vue前后端分离项目。

演示视频&#xff1a; ssmvue的劳务外包管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;ssm vue前后端分离项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&…

3BHB003154R0101 GVC707AE01 3BHB003149P201 3BHB003149P104 5SXE05-0156

3BHB003154R0101 GVC707AE01 3BHB003149P201 3BHB003149P104 5SXE05-0156 特征 IT/OT SOC是一种安全监控服务&#xff0c;它使用快速、可扩展和统一的下一代安全信息和事件管理(SIEM)。该服务使用各种CTI和ML工具从客户的IT/OT设备收集事件日志&#xff0c;以检测网络攻击、…

leetCode 300.最长递增子序列 (贪心 + 二分 ) + 图解 + 优化 + 拓展

300. 最长递增子序列 - 力扣&#xff08;LeetCode&#xff09; 给你一个整数数组 nums &#xff0c;找到其中最长严格递增子序列的长度。 子序列 是由数组派生而来的序列&#xff0c;删除&#xff08;或不删除&#xff09;数组中的元素而不改变其余元素的顺序。例如&#xff…

Kafka 高可用

正文 一、高可用的由来 1.1 为何需要Replication 在Kafka在0.8以前的版本中&#xff0c;是没有Replication的&#xff0c;一旦某一个Broker宕机&#xff0c;则其上所有的Partition数据都不可被消费&#xff0c;这与Kafka数据持久性及Delivery Guarantee的设计目标相悖。同时Pr…

算法通关村第17关【青铜】| 贪心

贪心算法&#xff08;Greedy Algorithm&#xff09;是一种常见的算法设计策略&#xff0c;通常用于解决一类优化问题。其核心思想是&#xff1a;在每一步选择中都采取当前状态下的最优决策&#xff0c;从而希望最终能够达到全局最优解。贪心算法不像动态规划算法需要考虑各种子…

电暖产品经营小程序商城搭建

电暖产品的需求度很高&#xff0c;包括地暖系统及壁挂炉、水暖散热器等&#xff0c;尤其每年冬天&#xff0c;部分家庭或办公场所就会有相关需求&#xff0c;庞大市场下为电暖各领域商家及品牌带来了商机。 然而随着互联网深入各行业及实体店生意难做&#xff0c;无论品牌还是…

安卓RecycleView包含SeekBar点击列表底部圆形阴影处理

seekbar在列表中点击底部圆形阴影禁止显示方法 大家好&#xff0c;最近写了自定义的seekbar实现显示进度值&#xff0c;然而呢&#xff0c;我的seekbar控件是作为recycleview的item来使用的&#xff0c;我设置了禁止点击和滑动方法如下&#xff1a; seekBar.setOnTouchListene…

ubuntu使用whisper和funASR-语者分离-二值化

文章目录 一、选择系统1.1 更新环境 二、安装使用whisper2.1 创建环境2.1 安装2.1.1安装基础包2.1.2安装依赖 3测试13测试2 语着分离创建代码报错ModuleNotFoundError: No module named pyannote报错No module named pyannote_whisper 三、安装使用funASR1 安装1.1 安装 Conda&…

SpringBoot+MinIO实现对象存储

一、 MinIO MinIO 是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口&#xff0c;非常适合于存储大容量非结构化的数据&#xff0c;例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等&#xff0c;而一个对象文件可以是任意大小&#…

【好玩】如何在github主页放一条贪吃蛇

前言 &#x1f34a;缘由 github放小蛇&#xff0c;就问你烧不烧 起因看到大佬github上有一条贪吃蛇扭来扭去&#xff0c;觉得好玩&#xff0c;遂给大家分享一下本狗的玩蛇历程 &#x1f95d;成果初展 贪吃蛇 &#x1f3af;主要目标 实现3大重点 1. github设置主页 2. git…

Arcgis日常天坑问题(1)——将Revit模型转为slpk数据卡住不前

这段时间碰到这么一个问题&#xff0c;revit模型在arcgis pro里导出slpk的时候&#xff0c;卡在98%一直不动&#xff0c;大约有两个小时。 首先想到的是revit模型过大&#xff0c;接近300M。然后各种减小模型测试&#xff0c;还是一样的问题&#xff0c;大概花了两天的时间&am…

OpenHarmony父子组件双项同步使用:@Link装饰器

子组件中被Link装饰的变量与其父组件中对应的数据源建立双向数据绑定。 说明&#xff1a; 从API version 9开始&#xff0c;该装饰器支持在ArkTS卡片中使用。 概述 Link装饰的变量与其父组件中的数据源共享相同的值。 装饰器使用规则说明 Link变量装饰器 说明 装饰器参数 无…

实现协议互通:探索钡铼BL124EC的EtherCAT转Ethernet/IP功能

钡铼BL124EC是一种用于工业网络通信的网关设备&#xff0c;专门用于将EtherCAT协议转换成Ethernet/IP协议。它充当一个桥梁&#xff0c;连接了使用不同协议的设备&#xff0c;使它们能够无缝地进行通信和互操作。 具体来说&#xff0c;BL124EC通过支持EtherCAT&#xff08;以太…

5SpringMVC处理Ajax请求携带的JSON格式(“key“:value)的请求参数

SpringMVC处理Ajax 参考文章数据交换的常见格式,如JSON格式和XML格式 请求参数的携带方式 浏览器发送到服务器的请求参数有namevalue&...(键值对)和{key:value,...}(json对象)两种格式 URL请求会将请求参数以键值对的格式拼接到请求地址后面,form表单的GET和POST请求会…

论文阅读——Large Selective Kernel Network for Remote Sensing Object Detection

目录 基本信息标题目前存在的问题改进网络结构另一个写的好的参考 基本信息 期刊CVPR年份2023论文地址https://arxiv.org/pdf/2303.09030.pdf代码地址https://github.com/zcablii/LSKNet 标题 遥感目标检测的大选择核网络 目前存在的问题 相对较少的工作考虑到强大的先验知…

HTML5+CSS3+移动web 前端开发入门笔记(二)HTML标签详解

HTML标签&#xff1a;排版标签 排版标签用于对网页内容进行布局和样式的调整。下面是对常见排版标签的详细介绍&#xff1a; <h1>: 定义一级标题&#xff0c;通常用于标题栏或页面主要内容的标题。<p>: 定义段落&#xff0c;用于将文字分段展示&#xff0c;段落之…

mysql面试题25:数据库自增主键可能会遇到什么问题?应该怎么解决呢?

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:数据库自增主键可能会遇到什么问题? 数据库自增主键可能遇到的问题: 冲突问题:自增主键是通过自动递增生成的唯一标识符,但在某些情况下可能会…

Sentinel入门

文章目录 初始Sentinel雪崩问题服务保护技术对比认识Sentinel微服务整合Sentinel 限流规则快速入门流控模式关联模式链路模式 流控效果warm up排队等待 热点参数限流全局参数限流热点参数限流 隔离和降级FeignClient整合Sentinel线程隔离熔断降级慢调用异常比例、异常数 授权规…