1.Spring的bean注入方式
A).构造器注入
@Service public class BeanOne { //注入对象BeanTwo private BeanTwo bt; //构造器 public BeanOne(BeanTwo bt) { this.bt = bt ; } } @Service public class BeanTwo { public BeanTwo(BeanOne bo) { ...... ...... } }
总结:构造器注入方式,Spring 是无法解决这种注入方式的循环依赖的,这种方式会在项目启动时抛出异常(
BeanCurrentlyInCreationException),至于为什么解决不了,暂且先不解释,后面会再说明,
B).Setter方式单例注入
@Service public class BeanOne { //注入对象BeanTwo private BeanTwo bt; //一定要写被注入对象的set方法 public void setBeanTwo(BeanTwo bt) { this.bt = bt; } } @Service public class BeanTwo { ...... ......}
总结:此种方式也会产生循环依赖问题,但Spring 在初始化时会对其进行处理,从而解决循环依赖问题
C).singleton模式field属性注入循环依赖(默认模式)
相信大家在写代码时都会用到这种写法:
@Servicepublic class AServiceImpl implements AService { @Autowired private BService bService; ...}@Servicepublic class BServiceImpl implements BService { @Autowired private AService aService; ...}
总结:属性注入方式其实和Setter注入方式类似,Spring 在初始化bean过程中都会解决,只是两种注入还是有所区别。
2.三种注入方式的不同
属性注入不能有效指明依赖相信很多人都遇见过一个bug,依赖注入的对象为null,在启动依赖容器时遇到这个问题都是配置的依赖注入少了一个注解什么的。
这种方式就过于依赖注入容器了,当没有启动整个依赖容器时,这个类就不能运转,在反射时无法提供这个类需要的依赖。
依赖注入的核心思想之一就是被容器管理的类不应该依赖被容器管理的依赖,换成白话来说就是如果这个类使用了依赖注入的类,那么这个类摆脱了这几个依赖必须也能正常运行。然而使用变量注入的方式是不能保证这点的。
而如果是采用构造器注入或者set注入,就可以避免以上问题:- 使用set方式时,这是一种选择注入,可有可无,即使没有注入这个依赖,那么也不会影响整个类的运行。 使用构造器方式时已经显式注明必须强制注入。通过强制指明依赖注入来保证这个类的运行
2.循环依赖
循环依赖:顾名思义,就是N个类循环(嵌套)引用。
通俗的讲就是N个Bean互相引用对方,最终形成闭环。用一副经典的图示可以表示成这样(A、B、C都代表对象,虚线代表引用关系):
二、Spring如何解决单列循环依赖首先,Spring内部维护了三个Map,也就是我们通常说的三级缓存。
在Spring的DefaultSingletonBeanRegistry类中,你会发现上面定义了三个Map:
singletonObjects(一级缓存),称“单例池”“容器”,存储完整的单例Bean的地方。 singletonFactories(二级缓存) 存放原始的 bean 对象(尚未填充属性),用于解决循环依赖 earlySingletonObjects(三级缓存) 映射Bean的早期引用,也就是说在这个Map里的Bean不是完整的,甚至还不能称之为“Bean”,只是一个实例化Instance.1.bean创建流程
2.关键代码解析
创建bean之前先获取bean,getSingleton()
- 先从一级缓存singletonObjects中去获取。 如果获取不到或者对象正在创建中(isSingletonCurrentlyInCreation()),那就再从二级缓存earlySingletonObjects中获取。 如果还是获取不到,且允许singletonFactories(allowEarlyReference=true)通过getObject()获取。就从三级缓存singletonFactory.getObject()获取。(如果获取到了就从singletonFactories中移除,并且放进earlySingletonObjects。其实也就是从三级缓存移动到了二级缓存
未完,待续。。。