springboot 时序图
启动时序图
准备上下文时序图
刷新上下文时序图
Spring中的循环依赖
主要场景
- 单例setter注入(能解决)
- 多例setter注入(不能解决)
- 构造器注入(不能解决)
- 单例代理对象setter注入(有可能解决)
- DependsOn循环依赖(不能解决)
单例setter注入
spring内部有三级缓存(详见org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
):
- singletonObjects: 一级缓存,用于实例化,注入,初始化完成的bean实例
- earylySingletonObjects: 二级缓存,用于保存正在实例化的bean实例
- singletonFactories: 三级缓存,用于保存bean创建工厂,以便于后面扩展有机会创建代理对象
如以下代码:
1 | @Service |
实例化过程:
上图中二级缓存(保存正在实例化的对象)的作用是保证如果有多个类都需要注入同一个类的实例,保证多个类注入的都是同一个对象.否则从三级缓存(bean创建工厂)取,最终多个类拥有某一个类的不同对象.而三级缓存之所以保存bean创建工厂,是为了方便对所要创建的实例对象进行增强.如下:
1 | public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory { |
多例setter注入
多例对象在spring启动不会进行初始化(AbstractApplicationContext#refresh()
->finishBeanFactoryInitialization()
->preInstantiateSingletons()
中可以看到),不过将多例对象注入到单例对象即可在启动时初始化.但此时会报循环依赖错误,因为没有用到缓存.
构造器注入
由于未用到缓存,会导致抛出循环依赖异常
单例代理对象setter注入
这种注入方式也比较常见,比如平时使用的@Async
注解,会通过AOP自动生成代理对象,如下:
1 | @Service |
实例过程:
bean初始化完成之后,后面还有一步去检查:第二级缓存 和 原始对象 是否相等。由于它对前面流程来说无关紧要,所以前面的流程图中省略了.
但如果将TestService1
修改为TestService6
,程序将不会抛出异常.修改后实例过程如下:
默认情况spring按照文件完整路径递归查找,TestService2
将比TestService6
先加载,从而导致程序不会因为检查二级缓存中对象不相等而抛出异常.
DependsOn循环依赖
1 | @DependsOn(value = "testService2") |
实例过程:
1 | public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory { |
解决办法
- 使用
@Lazy
延迟加载 - 使用
@DepondsOn
指定先后关系,并且保证不产生循环依赖 - 修改文件名称,改变加载顺序
- 多例循环依赖修改为单例