ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Spring循环依赖问题

2022-08-17 23:02:08  阅读:132  来源: 互联网

标签:缓存 Spring public testA instance 依赖 spring class 循环


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实例初始化完成后放入一级缓存,删除二级对应的缓存

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

从缓存中获取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

标签:缓存,Spring,public,testA,instance,依赖,spring,class,循环
来源: https://www.cnblogs.com/WJQ2017/p/16597085.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有