【框架源码篇 02】Spring源码-手写DI

news2024/11/18 3:26:56

Spring源码手写篇-手写DI

  简单回顾前面的手写IoC的内容。

image.png

一、DI介绍

  DI(Dependency injection)依赖注入。对象之间的依赖由容器在运行期决定,即容器动态的将某个依赖注入到对象之中。说的直白点就是给Bean对象的成员变量赋值。

  在这里我们就需要明白几个问题。

1. 哪些地方会有依赖

  • 构造参数依赖
  • 属性依赖

2. 依赖注入的本质是什么?

  依赖注入的本质是 赋值。赋值有两种情况

  1. 给有参构造方法赋值
  2. 给属性赋值

3. 参数值、属性值有哪些?

  具体赋值有两种情况:直接值和Bean依赖。比如

public clsss Girl{
     public Girl(String name,int age,char cup,Boy boyfriend){
         ...
     }
}

4. 直接赋值有哪些?

  • 基本数据类型:String、int 等
  • 数组,集合
  • map

二、构造注入

  我们先来看看构造参数注入的情况应该要如何解决。

1.构造注入分析

  我们应该如何定义构造参数的依赖?也就是我们需要通过构造方法来创建实例,然后对应的构造方法我们需要传入对应的参数。如果不是通过IoC来处理,我们可以直接通过如下的代码实现。

    public static void main(String[] args) {
        Boy boy = new Boy();
        Girl girl = new Girl("小丽",20,'C',boy);
    }

  我们通过直接赋值的方式就可以了。但是在IoC中我们需要通过反射的方式来处理。那么我们在通过反射操作的时候就需要能获取到对应的构造参数的依赖了,这时我们得分析怎么来存储我们的构造参数的依赖了。构造参数的依赖有两个特点:

  • 数量
  • 顺序

  上面的例子中的参数

  1. 小丽
  2. 20
  3. ‘C’
  4. boy,是一个依赖Bean

  参数可以有多个,我们完全可以通过List集合来存储,而且通过添加数据的顺序来决定构造参数的顺序了。但是这里有一个问题,如何表示Bean依赖呢?直接值我们直接添加到集合中就可以了,但是Bean依赖,我们还没有创建对应的对象,这时我们可以维护一个自定义对象,来绑定相关的关系。

2. BeanReference

  BeanReference就是用来说明bean依赖的:也就是这个属性依赖哪个类型的Bean

image.png

  可以根据name来依赖,也可以按照Type来依赖。当然我们的程序中还有一点需要考虑,就是如何来区分是直接值还是Bean依赖呢?有了上面的设计其实就很容易判断了。

if ( obj instance BeanReference)

  当然还有一种比较复杂的情况如下:

image.png

  直接值是数组或者集合等,同时容器中的元素是Bean依赖,针对这种情况元素值还是需要用BeanReference来处理的。Bean工厂在处理时需要遍历替换。

3. BeanDefinition实现

  接下来我们看看如何具体的来实现DI基于构造参数依赖的相关操作。首先是定义的相关处理了。我们需要在 BeanDefinition中增加构造参数的获取的方法。

image.png

  然后我们需要在默认的实现GenericBeanDefinition中增加对应的方法来处理。

image.png

  定义后我们可以测试下对应的应用,定义个ABean,依赖了CBean

public class ABean {

	private String name;

	private CBean cb;

	public ABean(String name, CBean cb) {
		super();
		this.name = name;
		this.cb = cb;
		System.out.println("调用了含有CBean参数的构造方法");
	}

	public ABean(String name, CCBean cb) {
		super();
		this.name = name;
		this.cb = cb;
		System.out.println("调用了含有CCBean参数的构造方法");
	}

	public ABean(CBean cb) {
		super();
		this.cb = cb;
	}

	public void doSomthing() {
		System.out.println("Abean.doSomthing(): " + this.name + " cb.name=" + this.cb.getName());
	}

	public void init() {
		System.out.println("ABean.init() 执行了");
	}

	public void destroy() {
		System.out.println("ABean.destroy() 执行了");
	}
}

然后在实例化时我们需要做相关的绑定

		GenericBeanDefinition bd = new GenericBeanDefinition();
		bd.setBeanClass(ABean.class);
		// 定义的构造参数的依赖
		List<Object> args = new ArrayList<>();
		args.add("abean01");
		// Bean依赖 通过BeanReference 来处理
		args.add(new BeanReference("cbean"));
		bd.setConstructorArgumentValues(args);
		bf.registerBeanDefinition("abean", bd);

构造参数传递后,接下来其实我们就需要要在 BeanFactory中来实现构造参数的注入了

4.BeanFactory实现

  前面我们在BeanFactory中实现Bean对象的创建有几种方式

  • 构造方法创建
  • 工厂静态方法
  • 工厂成员方法

  我们在通过构造方法创建其实是通过无参构造方法来处理的,这时我们需要改变这块的逻辑,通过有参构造方法来实现。

	// 构造方法来构造对象
	private Object createInstanceByConstructor(BeanDefinition bd)
			throws InstantiationException, IllegalAccessException {
		try {
			return bd.getBeanClass().newInstance();
		} catch (SecurityException e1) {
			log.error("创建bean的实例异常,beanDefinition:" + bd, e1);
			throw e1;
		}
	}

  我们就需要对上面的方法做出改变。

	// 构造方法来构造对象
	private Object createInstanceByConstructor(BeanDefinition bd)
			throws InstantiationException, IllegalAccessException {
        // 1. 得到真正的参数值
		List<?> constructorArgumentValues = bd.getConstructorArgumentValues(); 
        // 2.根据对应的构造参数依赖获取到对应的 Constructor 
        Constructor  constructor = 得到对应的构造方法
        // 3.用实际参数值调用构造方法创建对应的对象
        return constructor.newInstance(Object ... 实参值); 
	}

  通过上面的分析我们需要获取对应的构造器。这块我们需要通过反射来获取了。下面是具体的实现逻辑

image.png

  根据上面的分析,我们实现的逻辑分为两步

  1. 先根据参数的类型进行精确匹配查找,如果没有找到,继续执行第二步操作
  2. 获得所有的构造方法,遍历构造方法,通过参数数量过滤,再比对形参与实参的类型

  因为这里有个情况,实参是Boy,构造方法的形参是Person,第一种精确匹配就没有办法关联了。

image.png

具体的实现代码如下:

    private Constructor<?> determineConstructor(BeanDefinition bd, Object[] args) throws Exception {
        /*判定构造方法的逻辑应是怎样的?
        1 先根据参数的类型进行精确匹配查找,如未找到,则进行第2步查找;
        2获得所有的构造方法,遍历,通过参数数量过滤,再比对形参类型与实参类型。
        * */

        Constructor<?> ct = null;

        //没有参数,则用无参构造方法
        if (args == null) {
            return bd.getBeanClass().getConstructor(null);
        }

        // 1 先根据参数的类型进行精确匹配查找
        Class<?>[] paramTypes = new Class[args.length];
        int j = 0;
        for (Object p : args) {
            paramTypes[j++] = p.getClass();
        }
        try {
            ct = bd.getBeanClass().getConstructor(paramTypes);
        } catch (Exception e) {
            // 这个异常不需要处理
        }

        if (ct == null) {
            // 2 没有精确参数类型匹配的,获得所有的构造方法,遍历,通过参数数量过滤,再比对形参类型与实参类型。
            // 判断逻辑:先判断参数数量,再依次比对形参类型与实参类型
            outer:
            for (Constructor<?> ct0 : bd.getBeanClass().getConstructors()) {
                Class<?>[] paramterTypes = ct0.getParameterTypes();
                if (paramterTypes.length == args.length) {   //通过参数数量过滤
                    for (int i = 0; i < paramterTypes.length; i++) { //再依次比对形参类型与实参类型是否匹配
                        if (!paramterTypes[i].isAssignableFrom(args[i].getClass())) {
                            continue outer; //参数类型不可赋值(不匹配),跳到外层循环,继续下一个
                        }
                    }

                    ct = ct0;  //匹配上了
                    break outer;
                }
            }
        }

        if (ct != null) {
            return ct;
        } else {
            throw new Exception("不存在对应的构造方法!" + bd);
        }
    }

  上面我们考虑的是BeanFactory通过构造器来获取对象的逻辑,那如果我们是通过静态工厂方法或者成员工厂方法的方式来处理的,那么构造参数依赖的处理是否和前面的是一样的呢?其实是差不多的,我们需要根据对应的构造参数来推断对应的工厂方法

    // 静态工厂方法
    private Object createInstanceByStaticFactoryMethod(BeanDefinition bd) throws Exception {

        Object[] realArgs = this.getConstructorArgumentValues(bd);
        Class<?> type = bd.getBeanClass();
        Method m = this.determineFactoryMethod(bd, realArgs, type);
        return m.invoke(type, realArgs);
    }

    // 工厂bean方式来构造对象
    private Object createInstanceByFactoryBean(BeanDefinition bd) throws Exception {

        Object[] realArgs = this.getConstructorArgumentValues(bd);
        Method m = this.determineFactoryMethod(bd, realArgs, this.getType(bd.getFactoryBeanName()));

        Object factoryBean = this.doGetBean(bd.getFactoryBeanName());
        return m.invoke(factoryBean, realArgs);
    }

5.缓存功能

  对于上面的处理过程相信大家应该清楚了,我们通过推断也得到了对应的构造方法或者对应的工厂方法,那么我们可以不可以在下次需要再次获取的时候省略掉推导的过程呢?显然我们可以在BeanDefinition中增加缓存方法可以实现这个需求。

image.png

6. 循环依赖问题

image.png

  上图是循环依赖的三种情况,虽然方式有点不一样,但是循环依赖的本质是一样的,就你的完整创建要依赖与我,我的完整创建也依赖于你。相互依赖从而没法完整创建造成失败。

  我们通过构造参数依赖是完全可能出现上面的情况的,那么这种情况我们能解决吗?构造依赖的情况我们是解决不了的。

public class Test01 {

    public static void main(String[] args) {
        new TestService1();
    }
}

class TestService1{
    private TestService2 testService2 = new TestService2();
}

class TestService2{
    private  TestService1 testService1 = new TestService1();
}

  既然解决不了,那么我们在程序中如果出现了,应该要怎么来解决呢?其实我们可以在创建一个Bean的时候记录下这个Bean,当这个Bean创建完成后我们在移除这个Bean,然后我们在getBean的时候判断记录中是否有该Bean,如果有就判断为循环依赖,并抛出异常。数据结构我们可以通过Set集合来处理。

image.png

到此构造注入的实现我们就搞定了。

三、属性注入

  上面搞定了构造注入的方式。接下来我们再看看属性注入的方式有什么需要注意的地方。

1. 属性依赖分析

  属性依赖就是某个属性依赖某个值。

public class Girl {

    private String name;
    private int age;
    private char cup;
    private List<Boy> boyFriends;

    // ....
}

  那么在获取实例对象后如何根据相关的配置来给对应的属性来赋值呢?这时我们可以定义一个实体类 PropertyValue来记录相关的属性和值。

image.png

2.BeanDefinition实现

  这时我们就需要在BeanDefinition中关联相关属性信息了。

image.png

3.BeanFactory实现

  然后我们在BeanFactory的默认实现DefaultBeanFactory中实现属性值的依赖注入。

// 创建好实例对象
// 给属性依赖赋值
this.setPropertyDIValues(bd,instance);
// 执行初始化相关方法
this.doInit(bd,instance);

  具体的实现代码如下:

    // 给入属性依赖
    private void setPropertyDIValues(BeanDefinition bd, Object instance) throws Exception {
        if (CollectionUtils.isEmpty(bd.getPropertyValues())) {
            return;
        }
        for (PropertyValue pv : bd.getPropertyValues()) {
            if (StringUtils.isBlank(pv.getName())) {
                continue;
            }
            Class<?> clazz = instance.getClass();
            Field p = clazz.getDeclaredField(pv.getName());
            //暴力访问  private
            p.setAccessible(true);
            p.set(instance, this.getOneArgumentRealValue(pv.getValue()));

        }
    }

4.循环依赖问题

  在构造参数依赖中我们发现没有办法解决,在属性依赖中同样会存在循环依赖的问题,这时我们能解决吗?

image.png

  其实这种情况我们不在IoC场景下非常好解决。如下

Boy b = new Boy();
Girl g = new Girl();
b.setGirl(g);
g.setBoy(b);

  但是在IoC好像不是太好解决:

image.png

  针对这种情况我们需要通过 提前暴露来解决这个问题,具体看代码!!!

    private void doEarlyExposeBuildingBeans(String beanName, Object instance) {
        Map<String,Object> earlyExposeBuildingBeansMap = earlyExposeBuildingBeans.get();
        if(earlyExposeBuildingBeansMap == null) {
            earlyExposeBuildingBeansMap = new HashMap<>();
            earlyExposeBuildingBeans.set(earlyExposeBuildingBeansMap);
        }
        earlyExposeBuildingBeansMap.put(beanName,instance);
    }

最后现阶段已经实现的类图结构

image.png

扩展作业:加入Bean配置的条件依赖生效的支持

在Bean定义配置中可以指定它条件依赖某些Bean或类,当这些Bean或类存在时,这个bean的配置才能生效!

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

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

相关文章

【进阶C语言】C语言文件操作

1. 为什么使用文件 2. 什么是文件 3. 文件的打开和关闭 4. 文件的顺序读写 5. 文件的随机读写 6. 文本文件和二进制文件 7. 文件读取结束的判定 8. 文件缓冲区 一、文件与文件的意义 1.文件的意义 文件的意义&#xff0c;无非就是为什么要使用文件&#xff1f; &#xff08;1&…

当中国走进全球化的“深水区”,亚马逊云科技解码云时代的中国式跃升

中国跨境贸易中支付金融与服务领域的综合创新型企业连连国际的联席CEO沈恩光发现&#xff0c;眼下&#xff0c;很多跨境电商的出海方式已发生了变化。几年前&#xff0c;它们还主要借助第三方电商平台&#xff0c;而现在&#xff0c;更多公司开始选择通过自主渠道进入海外市场&…

线性代数-Python-01:向量的基本运算 - 手写Vector及numpy的基本用法

文章目录 一、代码仓库二、向量的基本运算2.1 加法2.2 数量乘法2.3 向量运算的基本性质2.4 零向量2.5 向量的长度2.6 单位向量2.7 点乘/内积&#xff1a;两个向量的乘法 --答案是一个标量 三、手写Vector代码3.1 在控制台测试__repr__和__str__方法3.2 创建实例测试代码3.3 完整…

磁盘显示脱机状态如何处理

问题描述&#xff1a; 在磁盘管理可以看到磁盘1是脱机状态 脱机&#xff08;由于管理员设置的策略&#xff0c;该磁盘处于脱机状态&#xff09; 解决方法一&#xff1a;CMD窗口处理 1.打开CMD&#xff0c;运行diskpart 2.输入san&#xff0c;打开san策略&#xff0c; 输入san…

2防火墙:基础知识

看了一下相关视频&#xff0c;感觉要会防火墙&#xff0c;还是得补一下网络基础。看一下谢希仁的《计算机网络》&#xff0c;应该主要看网络层就可以。网络以前学的还可以&#xff0c;但现在也就能记得不到50%&#xff0c;正好重新学习一下&#xff0c;或许会有新的感受。 看完…

S4.2.4.3 Electrical Idle Sequence(EIOS)

一 本章节主讲知识点 1.1 EIOS的具体码型 1.2 EIOS的识别规则 1.3 EIEOS的具体码型 二 本章节原文翻译 当某种状态下,发送器想要进入电器空闲状态的时候,发送器必须发送EIOSQ,也既是:电器Electrical Idle Odered Set Sequence。当然,除非在某些情况下,特殊制定,也是…

用这个方法,谁都可以刷到leetcode排名第一(可复制)

前几天&#xff0c;有人分享了一个利用GPT在leetcode刷题&#xff0c;学习算法&#xff0c;启迪思路&#xff0c;提升编程能力的方法。 开始还不信&#xff0c;自己试了一下&#xff0c;惊了&#xff01;AI理解问题&#xff0c;编码解决问题的能力现在已经这么流弊了吗&#xf…

【具身智能综述1】A Survey of Embodied AI: From Simulators to Research Tasks

论文标题&#xff1a;A Survey of Embodied AI: From Simulators to Research Tasks 论文作者&#xff1a;Jiafei Duan, Samson Yu, Hui Li Tan, Hongyuan Zhu, Cheston Tan 论文原文&#xff1a;https://arxiv.org/abs/2103.04918 论文出处&#xff1a;IEEE Transactions on E…

正则表达式之学习笔记

正则表达式学习笔记 一、概念二、正则表达式组成三、常见的正则表达式3.1 .匹配任意字符3.2 * 匹配前一个字符的0个或多个实例3.3 ^ 匹配输入字符串的开头3.4 $ 匹配行尾3.5 [] 匹配字符集合\<\> 精确匹配符号 一、概念 正则表达式是由一系列特殊字符组成的字符串&#…

大数据分析实践 | pandas数据质量分析

文章目录 &#x1f4da;数据质量评估的五个维度&#x1f4da;口袋妖怪数据质量分析&#x1f407;导入库和数据&#x1f407;检查数据&#x1f407;缺失值分析&#x1f407;重复值检测&#x1f407;异常值检测 &#x1f4da;数据质量评估的五个维度 Coherent: without semantic …

Jetson nano 安装Ubuntu20.04系统

一、下载Ubuntu20.04镜像 下载地址&#xff1a;点击 二、格式化SD卡 &#xff08;1&#xff09;工具&#xff1a;SDFormatter &#xff08;2&#xff09;工具下载-百度网盘&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1DcwsGzmqrWwFmzpCV7VCyA 提取码&#xff1a…

【LeetCode】62. 不同路径

1 问题 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。 问总共有多少条不同的路径&#xff1f…

【Python】13.模块

目录 1. 模块化(module)程序设计理念1.1 模块和包概念的进化史1.2 标准库模块(standard library)1.3 为什么需要模块化编程1.4 模块化编程的流程1.5 模块的API 和功能描述要点1.6 模块的创建和测试代码1.7 模块文档字符串和API 设计 2. 模块的导入2.1 import 语句导入2.2 from……

Spark--经典SQL50题

目录 连接数据库准备工作 1、查询"01"课程比"02"课程成绩高的学生的信息及课程分数 2、查询"01"课程比"02"课程成绩低的学生的信息及课程分数 3、查询平均成绩大于等于60分的同学的学生编号和学生姓名和平均成绩 4、查询平均成绩…

ZCU106+ADRV9371+CPRO33-30.72+6 dB 衰减

文章目录 一、ZYNQ 平台二、ADRV9371三、CPRO33-30.72四、衰减器 一、ZYNQ 平台 之后使用 Zynq UltraScale MPSoC ZCU106&#xff0c;XCZU7EV 器件配备四核 ARM Cortex™-A53 应用处理器、双核 Cortex-R5 实时处理器、Mali™-400 MP2 图形处理单元、支持 4KP60 的 H.264/H.265…

SystemVerilog学习(2)——数据类型

一、概述 和Verilog相比&#xff0c;SV提供了很多改进的数据结构。它们具有如下的优点&#xff1a; 双状态数据类型&#xff1a;更好的性能&#xff0c;更低的内存消耗队列、动态和关联数组&#xff1a;减少内存消耗&#xff0c;自带搜索和分类功能类和结构&#xff1a;支持抽…

div透明模糊背景-渐变背景

background: rgba(245, 246, 246, 0.5) !important;-webkit-backdrop-filter: blur(13px);backdrop-filter: blur(13px);-webkit-transition: all 0.2s;transition: all 0.2s; 渐变 上到下 background: linear-gradient(#E7E7F1,#ffffff); 透明度渐变 上到下 background: lin…

rhcsa-8

rha off 状态下 输入virt-manager 先选node1&#xff1b;然后选倒数第二个含console的 1.配置网络设置 num [可以打印出nmcli]地址配置工具&#xff1a;nmcli W 可以打印出网卡链接的字符串 manu [打印出manual]手册 ipv4.方法手册-IP地址-网关-dns ip a s 在虚拟机上查看过…

前端 js 之 this 的绑定规则 04

嘿&#xff0c;加油&#x1f60d; 文章目录 一、this?二、this 的指向三、默认绑定&#xff08;独立函数调用&#xff09;四、隐式绑定五、显式绑定 &#xff08;apply call bind&#xff09;六、new绑定 (后面会详细再补充)七、apply call bind 区别八、内置函数的绑定思考九…

共谋工业3D视觉发展,深眸科技以自研解决方案拓宽场景应用边界

随着中国工业领域自动化程度逐渐攀升&#xff0c;“机器换人”这一需求进一步提升。在传统2D工业视觉易受环境光干扰、无法进一步获取物体深度信息的限制条件下&#xff0c;工业3D视觉凭借着更强的空间和深度感知能力&#xff0c;以及通过点云数据获取物体距离和三维坐标信息的…