Spring循环依赖问题

Spring Boot版本

pom.xml

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.5.2</version>
    <relativePath/>
</parent>

Spring Boot 2.6.0之后,Spring不会自动处理循环依赖的问题,需要设置开启。

setter注入导致循环依赖(支持)

@Service
public class A {

  @Autowired
  private B b;

  public void test() {}
}

@Service
public class B {

  @Autowired
  private A a;

  public void test() {}
}

Spring解决循环依赖流程
1.    Spring容器开始创建A,标记A为正在创建中,反射创建实例,A对象工厂放入三级缓存
2.    初始化A实例时发现需要依赖注入B,标记B为正在创建中,反射创建实例,B对象工厂放入三级缓存
3.    初始化B实例时发现需要依赖注入A,依次从一级、二级、三级缓存中取值,一级和二级缓存都没有,通过三级缓存中A对象工厂的getObject方法获得A实例,把该A实例放入二级缓存,删除对应的三级缓存
4.    B实例初始化完成后放入一级缓存,删除二级和三级对应的缓存
5.    回到第2步,A实例初始化完成后放入一级缓存,删除二级对应的缓存

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {

一级缓存、二级缓存和三级缓存数据结构

从缓存中获取bean

对象工厂和getObject方法

Spring引入三级缓存来解决这个问题(代码方便扩展),一级缓存也能搞定(如果存在AOP增强,那么一级缓存里面直接存代理类对象)。
一级缓存存放初始化完成的bean;
二级缓存是临时缓存,存放三级缓存经过BeanPostProcessor实现类增强处理后的代理对象;
三级缓存是临时缓存,存放持有原始bean(直接new出来的bean,属性未赋值)的对象工厂。

构造器注入导致循环依赖(不支持)

@Service
public class A {
    public A(B b) {}
}

@Service
public class B {
    public B(A a) {}
}

在准备创建A对象时,发现构造方法需要B对象。
在准备创建B对象时,发现构造方法需要A对象。
最开始的A对象没有创建,无法解决。

Spring 4.x之后推荐使用构造器注入,防止循环依赖。
@Lazy避免注入最终版本,真正使用时才注入最终版本。

通过一级缓存来解决循环依赖问题

import lombok.Getter;
import lombok.Setter;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class MySpring {
    // 同singletonObjects,缓存beanName和实例
    private Map<String, Object> beanInstanceMap = new HashMap<>();
    // 保存beanName对应的class对象
    private Map<String, Class> beanDefinationMap = new HashMap<>();
    // 保存bean之间的引用关系
    private Map<String, String> beanPropertyMap = new HashMap<>();

    private void init() {
        beanDefinationMap.put("testA", TestA.class);
        beanDefinationMap.put("testB", TestB.class);
        beanDefinationMap.put("testC", TestC.class);

        beanPropertyMap.put("testA", "testB");
        beanPropertyMap.put("testB", "testC");
        beanPropertyMap.put("testC", "testA");
    }

    private Object getBean(String beanName) throws Exception {
        // 1.从缓存中获取
        Object instance = beanInstanceMap.get(beanName);
        if (instance == null) {
            // 2.反射创建实例
            instance = beanDefinationMap.get(beanName).newInstance();
            // 3.缓存bean
            beanInstanceMap.put(beanName, instance);
            // 4.注入bean中的属性
            String property = beanPropertyMap.get(beanName);
            if (!(property == null || property.length() == 0)) {
                Object propertyValue = getBean(property);
                setPropertyValue(instance, property, propertyValue);
            }
        }
        return instance;
    }

    private void setPropertyValue(Object instance, String property, Object propertyValue) throws Exception {
        Field field = instance.getClass().getDeclaredField(property);
        field.setAccessible(true);
        field.set(instance, propertyValue);
    }

    public static void main(String[] args) throws Exception {
        MySpring spring = new MySpring();
        spring.init();
        TestA testA = (TestA) spring.getBean("testA");
        // true
        System.out.println(testA.getTestB().getTestC().getTestA() == testA);
    }

    @Getter
    @Setter
    static class TestA {
        TestB testB;
    }

    @Getter
    @Setter
    static class TestB {
        TestC testC;
    }

    @Getter
    @Setter
    static class TestC {
        TestA testA;
    }
}

参考资料

【spring源码深度解析】别再盲目的说spring有三级缓存了,两个缓存只是启动时为了解决循环依赖,spring启动后只有一个缓存有用
https://zhuanlan.zhihu.com/p/358802637
【超级干货】为什么spring一定要弄个三级缓存?
https://zhuanlan.zhihu.com/p/496273636

posted on 2022-08-17 22:45  王景迁  阅读(159)  评论(0编辑  收藏  举报

导航