0%

spring-源码分析

springboot 时序图

转载 SpringBoot启动时序图 - 逍遥书虫

启动时序图

准备上下文时序图

刷新上下文时序图

Spring中的循环依赖

转载 Spring 是如何解决循环依赖的? - 苏三说技术的回答 - 知乎

主要场景

  1. 单例setter注入(能解决)
  2. 多例setter注入(不能解决)
  3. 构造器注入(不能解决)
  4. 单例代理对象setter注入(有可能解决)
  5. DependsOn循环依赖(不能解决)

单例setter注入

spring内部有三级缓存(详见org.springframework.beans.factory.support.DefaultSingletonBeanRegistry):

  1. singletonObjects: 一级缓存,用于实例化,注入,初始化完成的bean实例
  2. earylySingletonObjects: 二级缓存,用于保存正在实例化的bean实例
  3. singletonFactories: 三级缓存,用于保存bean创建工厂,以便于后面扩展有机会创建代理对象

如以下代码:

1
2
3
4
5
6
7
8
9
10
11
@Service
public class TestService1 {
@Autowired
private TestService2 testService2;
}

@Service
public class TestService2 {
@Autowired
private TestService1 testService1;
}

实例化过程:

上图中二级缓存(保存正在实例化的对象)的作用是保证如果有多个类都需要注入同一个类的实例,保证多个类注入的都是同一个对象.否则从三级缓存(bean创建工厂)取,最终多个类拥有某一个类的不同对象.而三级缓存之所以保存bean创建工厂,是为了方便对所要创建的实例对象进行增强.如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {

...
@Override
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException {
...
if (earlySingletonExposure) {
if (logger.isDebugEnabled()) {
logger.debug("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
//通过getEarlyBeanReference生成代理对象
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
...
}
...
}

多例setter注入

多例对象在spring启动不会进行初始化(AbstractApplicationContext#refresh()->finishBeanFactoryInitialization()->preInstantiateSingletons()中可以看到),不过将多例对象注入到单例对象即可在启动时初始化.但此时会报循环依赖错误,因为没有用到缓存.

构造器注入

由于未用到缓存,会导致抛出循环依赖异常

单例代理对象setter注入

这种注入方式也比较常见,比如平时使用的@Async注解,会通过AOP自动生成代理对象,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Service
public class TestService1 {
@Autowired
private TestService2 testService2;

@Async
public void test1() {
}
}

@Service
public class TestService2 {
@Autowired
private TestService1 testService1;
}

实例过程:

bean初始化完成之后,后面还有一步去检查:第二级缓存 和 原始对象 是否相等。由于它对前面流程来说无关紧要,所以前面的流程图中省略了.

但如果将TestService1修改为TestService6,程序将不会抛出异常.修改后实例过程如下:

默认情况spring按照文件完整路径递归查找,TestService2将比TestService6先加载,从而导致程序不会因为检查二级缓存中对象不相等而抛出异常.

DependsOn循环依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
@DependsOn(value = "testService2")
@Service
public class TestService1 {
@Autowired
private TestService2 testService2;
}

@DependsOn(value = "testService1")
@Service
public class TestService2 {
@Autowired
private TestService1 testService1;
}

实例过程:

1
2
3
4
5
6
7
8
9
10
11
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
...
for (String dep : dependsOn) {
//是否存在循环依赖,存在则抛出异常
if (isDependent(beanName, dep)) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
}
...
}
...

解决办法

  1. 使用@Lazy延迟加载
  2. 使用@DepondsOn指定先后关系,并且保证不产生循环依赖
  3. 修改文件名称,改变加载顺序
  4. 多例循环依赖修改为单例
您的支持是对我最大的动力 (●'◡'●)