本文着重分析执行流程。
DI流程简介
- ioc容器:BeanFactory,工厂容器
- 初始化获取BeanDefinition:InstantiationStrategy,实例化策略
- 依赖注入:AbstractPropertyAccessor,属性访问器,用于各种方式的注入,如setter方法注入,构造器注入等
- 实例化Bean:BeanWrapper,Bean的包装类
DI过程概述
- 核心方法
getBean()
- 非lazyInit在refresh的方法
finishBeanFactoryInitialization
中执行 - lazyInit时,用到才加载
注入方式
- 按名称注入
getBean(“name”):按名字注入过程,核心方法AbstractBeanFactory.doGetBean()
- 按类型注入
getBean(xxx.class):按类型注入,核心方法DefaultListableBeanFactory.resolveBean()
按名称注入流程描述:
普通的单例Bean注入流程简述:(非代理情况,如AOP)
AbstractBeanFactory.getBean()
->doGetBean()
->DefaultSingletonBeanRegistry.getSingleton() //查询缓存,并标记各种状态,比如正在创建的状态
->AbstractAutowireCapableBeanFactory.createBean() //在上一步的调用过程中执行
->doCreateBean()
->createBeanInstance()//创建BeanWrapper,此步中,如果使用构造器注入,则会跳到处理成员的过程中,即getBean,直到成员初始化完成,再回来,此处是循环依赖处理的重要步骤
->populateBean() //填充BeanWrapper,如果有成员也是Bean,则去加载其他成员Bean,也会调用getBean
//普通的单例bean到这里也就结束了,但是如果需要使用动态代理的时候,后面还有一个方法会生成一个代理,来替代当前的Bean。
按类型注入流程描述:
- 目标是去寻找
NamedBeanHolder
这个实例,里面包含一个name和bean实例 - 会拿到传入的类型去BeanFactory里检查两个bean名称的缓存
allBeanNamesByType
和singletonBeanNamesByType
(二选一)。这两个map是以class:name[]
形式存储的,也就是说会根据类型去获取bean的所有名称(比如默认的名称,别名等) - 然后根据这个名称去调用
getBean(name)
的方法去获取Bean实例,组装成NamedBeanHolder返回。 - 如果那两个map里没有对应的class,那么会去拿到所有的BeanNames去遍历并检查类型是否匹配,如果匹配,则返回所有匹配的name,并且在当前选择的map里写进去一个缓存。
- 经过上一步操作,如果还是没有获取到对应的name,那么就会调用当前BeanFactory的parent或者parent的ObjectProvider去处理这个类(一般在扩展的情况下才会用到)
- 拿到所有的名字以后,会去检查,这个bean是不是单例,是的话调用getBean,放进当前缓存
- 如果不止一个bean符合,那么检查是不是标注Primary,是的话,用标注了primary的Bean的名字;如果不是,检查优先级,拿优先级最高的bean的名字
- 然后根据当前获取到的名字,去当前的备选bean缓存中获取bean,然后封装成NamedBean返回
循环依赖问题产生条件:
- 使用构造器注入
- A类在构造器中需要传入B类,同理,B也需要A
- 没有使用注解
@Lazy
加载流程:
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}catch (BeansException ex) {
...
}
});
- 先加载A,执行到上面这段代码时,
getSingleton
先执行,在此方法中会标记当前bean正在创建中。标记是在beforeSingletonCreation(beanName)
中 - 发现A的带参构造器里需要使用B,然后对B进行解析加载,
resolveDependency
中最后一段,getLazyResolutionProxyIfNecessary
方法用于检查是不是用了懒加载的处理方法,如果不是则直接去解决这个依赖
Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(descriptor, requestingBeanName);
if (result == null) {
result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
}
- 在
doResolveDependency
方法中,有一个resolveCandidate
方法;在此方法之前,会获取到很多符合要求的备选Bean名称,而这一步是在能够找到这些名称后的最终方法,解决备选Bean,而它实际就是BeanFactory.getBean - 于是现在去加载B,重复上面的步骤,会再一次的去加载A,发现A已经被标记为正在创建中,于是爆出循环依赖的问题
流程概括:A和B相互依赖,构造器为A(B),B(A)
- 加载A,标记A在创建中
- 调用A(B)构造,发现B被依赖
- 加载B,标记B在创建中
- 检查B是否使用@Lazy,没有的话,调用B(A)构造,发现A被依赖
- 加载A,标记A时,发现A在创建中,循环依赖
- 异常
循环依赖解决方案之使用@Lazy
循环依赖的解决方法有几个:
- 解藕代码,分离循环依赖的属性
- 使用属性注入
- 使用@Lazy
具体说第三种。在写法上,下面两种都是可以的,也没啥区别。
@Lazy
public A(B b) {
this.b = b;
}
public A(@Lazy B b) {
this.b = b;
}
原理说明
如上面讲述,循环依赖产生的流程,在getLazyResolutionProxyIfNecessary
调用到这里时,该方法先检查了是否是懒加载,如果是则创建了一个Cglib的代理对象返回。也就是说流程变成了
- 加载A,标记A在创建中
- 调用A(B)构造,发现B被依赖
- 加载B,标记B在创建中
- 检查到B是否使用@Lazy,是的话,创建一个Cglib的动态代理
- 解决了B的创建,返回到A的构造中,注入B的代理
- 完成A的实例的创建
需要注意的是,在实际spring项目中,A被创建以后,在A里的成员B永远都是动态代理。
但是在使用A涉及到B时,会被spring实现的MethodInterceptor拦截并处理,将B动态代理的地址转换为实际的B。