03|依赖注入:如何给Bean注入值并解决循环依赖问题?
值的注入
- 深入了解
- 翻译
- 解释
- 总结
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归属地:北京228 - 大胖子呀、个人感觉循环依赖是一种非常糟糕的设计,往往意味着写出这段代码的程序员没有理清层级关系,没有设计好上下层的依赖,是一种非常明显的坏味道。 Spring对于循环依赖的支持,反而导致了程序员写出了坏味道代码而不自知,或许从一开始Spring就不该支持循环依赖。 所以Spring官方也建议大家使用构造器注入,一个是避免写出这种层级依赖不清晰的糟糕代码,二是也方便了后续单元测试的编写。
作者回复: 太对了。Spring 6已经开始限制了。
2023-04-20归属地:广东10 - 每天晒白牙思考题 Spring支持一个Bean构造器注入另一个Bean,工作中也都是尽量通过构造器注入,有很多优点 通过属性注入的方式能解决循环依赖的问题,原理是通过缓存的方式解决的,这里的关键点是属性注入是在bean创建后注入的 而构造器注入不能解决循环依赖问题 因为需要在创建bean时就需要将依赖的bean传入到构造函数中,如果依赖的bean尚未创建完成,就不能传入到构造函数中,循环依赖就不能解决
作者回复: 赞
2023-03-17归属地:北京39 - Geek_320730loadBeanDefinitions结束的时候会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归属地:湖南32 - 追梦老师好,这个反射构造器和反射setXXX()方法这样写有点硬编码的味道,有没有简洁的写法,如何不硬编码解决基本类型的反射问题
作者回复: 看起来是,其实不是。这是bean的约定。
2023-03-27归属地:广东22 - Jackwey老师,Cbean依赖的是A的毛坯实例,那A的属性岂不是没有被Cbean依赖了?
作者回复: 毛坯后来又被填充了,又不是另一个对象。
2023-06-03归属地:广东21 - 康Geek文稿中 ClassPathXmlApplicationContext 这个类的构造方法中 isRefresh 有个错误: if (!isRefresh) { this.beanFactory.refresh(); } 这个 if 的条件中时取反的,但是在老师 github 仓库中 geek_ioc3 分支的 ClassPathXmlApplicationContext.java 构造方法中是没有取反的: if (isRefresh) { this.beanFactory.refresh();}
作者回复: 多谢提醒。
2023-04-13归属地:广东1