手把手带你写一个 MiniSpring
郭屹
前 Sun Microsystems Java 研发工程师,开源软件 MiniSpring、MiniTomcat 开发者
6170 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 26 讲
手把手带你写一个 MiniSpring
15
15
1.0x
00:00/00:00
登录|注册

03|依赖注入:如何给Bean注入值并解决循环依赖问题?

你好,我是郭屹,今天我们继续手写 MiniSpring,探讨 Bean 的依赖注入。
上节课,我们定义了在 XML 配置文件中使用 setter 注入和构造器注入的配置方式,但同时也留下了一个悬念:这些配置是如何生效的呢?

值的注入

要理清这个问题,我们要先来看看 Spring 是如何解析 <property><constructor-arg> 标签。
我们以下面的 XML 配置为基准进行学习。
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
<bean id="aservice" class="com.minis.test.AServiceImpl">
<constructor-arg type="String" name="name" value="abc"/>
<constructor-arg type="int" name="level" value="3"/>
<property type="String" name="property1" value="Someone says"/>
<property type="String" name="property2" value="Hello World!"/>
</bean>
</beans>
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

Spring框架中依赖注入的原理和实现方式是本文的重点。通过手写MiniSpring框架的实现过程,详细介绍了Bean的依赖注入,包括XML配置文件中使用setter注入和构造器注入的方式。文章通过代码展示了Spring是如何解析 `<property>` 和 `<constructor-arg>` 标签,并通过反射机制实现了对Bean的构造器注入和setter注入。此外,还解释了IoC和依赖注入的概念,帮助读者理解这两个术语的区别和联系。另外,文章还探讨了解决循环依赖问题的方法,引入了“毛胚Bean”的概念,通过早期实例的缓存解决了Bean之间的复杂依赖关系。最后,文章介绍了Spring中的核心方法refresh(),该方法包装了容器启动的各个步骤,从Bean工厂的创建到Bean对象的实例化和初始化,再到完成Spring容器加载。整篇文章通过实际代码和详细解释,帮助读者深入理解了依赖注入的原理和实现方式,对于想深入了解Spring框架的读者具有很高的参考价值。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《手把手带你写一个 MiniSpring》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(27)

  • 最新
  • 精选
  • 每天晒白牙
    回复BattleMan1994 老师这个用了两个缓存,spring多一个创建bean实例工厂缓存,详细如下 三级缓存机制包括以下三个缓存: 1. singletonObjects:用于存储完全创建好的单例bean实例。 2. earlySingletonObjects:用于存储早期创建但未完成初始化的单例bean实例。即老师说的毛坯 3. singletonFactories:用于存储创建单例bean实例的工厂对象。 当Spring发现两个或更多个bean之间存在循环依赖关系时,它会将其中一个bean创建的过程中尚未完成的实例放入earlySingletonObjects缓存中,然后将创建该bean的工厂对象放入singletonFactories缓存中。接着,Spring会暂停当前bean的创建过程,去创建它所依赖的bean。当依赖的bean创建完成后,Spring会将其放入singletonObjects缓存中,并使用它来完成当前bean的创建过程。在创建当前bean的过程中,如果发现它还依赖其他的bean,Spring会重复上述过程,直到所有bean的创建过程都完成为止。 需要注意的是,当使用构造函数注入方式时,循环依赖是无法解决的。因为在创建bean时,必须先创建它所依赖的bean实例,而构造函数注入方式需要在创建bean实例时就将依赖的bean实例传入构造函数中。如果依赖的bean实例尚未创建完成,就无法将其传入构造函数中,从而导致循环依赖无法解决。此时,可以考虑使用setter注入方式来解决循环依赖问题。

    作者回复: 你完全理解了,赞! 我的三级缓存,最后一级就是直接创建bean实例。我在后面的再回首中章节有说明这个所谓的“缓存”是什么,不用拘泥于名词术语。

    2023-03-18归属地:北京
    2
    28
  • 大胖子呀、
    个人感觉循环依赖是一种非常糟糕的设计,往往意味着写出这段代码的程序员没有理清层级关系,没有设计好上下层的依赖,是一种非常明显的坏味道。 Spring对于循环依赖的支持,反而导致了程序员写出了坏味道代码而不自知,或许从一开始Spring就不该支持循环依赖。 所以Spring官方也建议大家使用构造器注入,一个是避免写出这种层级依赖不清晰的糟糕代码,二是也方便了后续单元测试的编写。

    作者回复: 太对了。Spring 6已经开始限制了。

    2023-04-20归属地:广东
    10
  • 每天晒白牙
    思考题 Spring支持一个Bean构造器注入另一个Bean,工作中也都是尽量通过构造器注入,有很多优点 通过属性注入的方式能解决循环依赖的问题,原理是通过缓存的方式解决的,这里的关键点是属性注入是在bean创建后注入的 而构造器注入不能解决循环依赖问题 因为需要在创建bean时就需要将依赖的bean传入到构造函数中,如果依赖的bean尚未创建完成,就不能传入到构造函数中,循环依赖就不能解决

    作者回复: 赞

    2023-03-17归属地:北京
    3
    9
  • Geek_320730
    loadBeanDefinitions结束的时候会registerBeanDefinition,看代码中registerBeanDefinition又会根据这个Bean是否是单例来判断要不要getBean。如果getBean的话:如果这个Bean有依赖的Bean,会继续getBean,如果xml中 这个被依赖的Bean定义在这个Bean后面,那么后面被依赖的Bean的BeanDefintion还没有被loadBeanDefinitions,createBean的时候就会报错。

    作者回复: 你说的对,是这样的,有另外的人也指出了这一点。赞你! 比较简单的解决方案是一次性先把Definition加载完,然后再getBean,这样保证所有bean的定义都预先准备好了。

    2023-03-19归属地:北京
    8
  • 风轻扬
    老师,我看其他同学提了这个问题。就是如果xml中A定义在前,依赖B,但是B定义在后。此时会因为beanDefinitionMap中不存在beanDefinition而报错。我看您给你的解决方案是先将beanDefinition对象一次性全部加载完成。那是不是将SimpleBeanFactory类中的方法registerBeanDefinition中的以下逻辑去掉就可以了。 if (!bd.isLazyInit()) { getBean(name); } 我试了试,这样是ok的,因为ClassPathXmlApplicationContext中的refresh方法会执行到getBean

    作者回复: 对的,isLazyInit()返回值改为true也可以。这是当时留给学生的扩展练习。你们能提出这些问题,用心了,赞!

    2023-03-22归属地:北京
    4
  • 木 昜
    您好,目前所写的逻辑是加载一个BeanDefinition,然后放入Map,同时判断是否为懒加载,不是的话就创建该bean,然后加载下一个bean定义。 如果xml在a的bean定义在b之前,并且a依赖了b。 此时 加载a的定义,创建a,发现a依赖b,就去getBean(b),但是此时b的定义还没有加载进map,就会抛出异常。 是否可以改为加载完全部的bean定义之后再进行bean的创建。把两步骤分开?

    作者回复: 你的思考很好,用心了。 你说的是对的,赞!

    2023-03-19归属地:北京
    3
  • createBean从哪里冒出来的,上面的课程里面SimpleBeanFactory类里没有看到

    作者回复: 文稿是一个主干,还是要读源代码,github上有。

    2023-04-12归属地:湖南
    3
    2
  • 追梦
    老师好,这个反射构造器和反射setXXX()方法这样写有点硬编码的味道,有没有简洁的写法,如何不硬编码解决基本类型的反射问题

    作者回复: 看起来是,其实不是。这是bean的约定。

    2023-03-27归属地:广东
    2
    2
  • Jackwey
    老师,Cbean依赖的是A的毛坯实例,那A的属性岂不是没有被Cbean依赖了?

    作者回复: 毛坯后来又被填充了,又不是另一个对象。

    2023-06-03归属地:广东
    2
    1
  • 康Geek
    文稿中 ClassPathXmlApplicationContext 这个类的构造方法中 isRefresh 有个错误: if (!isRefresh) { this.beanFactory.refresh(); } 这个 if 的条件中时取反的,但是在老师 github 仓库中 geek_ioc3 分支的 ClassPathXmlApplicationContext.java 构造方法中是没有取反的: if (isRefresh) { this.beanFactory.refresh();}

    作者回复: 多谢提醒。

    2023-04-13归属地:广东
    1
收起评论
显示
设置
留言
27
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部