Spring的循环依赖

一、循环依赖出现原因(单例setter注入)

A属性注入B,B属性注入A。存在A中有属性B,B中有属性A,那么当依赖注入的时候,就会产生当A还没有创建完的时候,由于对B的创建再次返回创建A,最终造成循环依赖。

@Component
public class A {

    //单例的setter注入
    @Autowired
    private B b;


    public void test(){
        System.out.println(b);
    }

}


@Component
public class B {

    @Autowired
    private A a;

    public void dosth(){
        a.test();   //输出b对象信息
    }

}

@EnableAspectJAutoProxy
@ComponentScan(basePackages = "com.igeek.ch03.singleton")
public class MyConfig {
}

@Component
@Aspect
public class LogAspect {

    @Before("execution(* A.test())")
    public void beforeAdvice(){
        System.out.println("LogAspect A test()之前~");
    }

}

public class MainTest {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig.class);


        a.test();*/

        B b = ac.getBean(B.class);
        b.dosth();
    }

}

二、DefaultSingletonBeanRegistry 类

2.1 三级缓存
2.1.1 一级缓存

Map<beanName,bean实例> singletonObjects,一级缓存,又名"线程池",作用:存放进过完整生命周期(实例化、注入、初始化完成)的bean实例

2.1.2 二级缓存

Map<beanName,bean实例> earlySingletonObjects,二级缓存,存储实例化后的bean实例

2.1.3 三级缓存

Map<String,ObjectFactory<?>> singletonFactories lambda表达式,可以为beanName,value为ObjectFactor bean工厂。在出现循环依赖时,用于存储bean创建工厂,以便创建代理对象。

2.2 解决循环依赖最为核心的代码

DefaultSingletonBeanRegistry类中的 getSingleton() 方法

    //解决循环依赖最核心的代码:一二级缓存找不到会去执行三级缓存的lambda表达式,并放入二级缓存!
    @Nullable
	protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		// Quick check for existing instance without full singleton lock
        // 先从一级缓存(单例池)中获取单实例bean
		Object singletonObject = this.singletonObjects.get(beanName);
        
        //若一级缓存中没有,且当前单实例bean正在创建中
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            
            //就会从二级缓存中查询
			singletonObject = this.earlySingletonObjects.get(beanName);
            
            //若二级缓存中也没有查询到
			if (singletonObject == null && allowEarlyReference) {
                
                //确保两个动作put、remove的原子性
				synchronized (this.singletonObjects) {
					// Consistent creation of early reference within full singleton lock
					singletonObject = this.singletonObjects.get(beanName);
					if (singletonObject == null) {
						singletonObject = this.earlySingletonObjects.get(beanName);
						if (singletonObject == null) {
                            
                            //就会从三级缓存中查找,只要是单实例bean,都会找到
							ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                            
                            //若在三级缓存中找到单实例bean
							if (singletonFactory != null) {
                                
                                //则去执行Lambda表达式,即执行AOP,获得其代理对象
								singletonObject = singletonFactory.getObject();
                                
								//将代理对象,存入二级缓存中
								this.earlySingletonObjects.put(beanName, singletonObject);
                                //并将其移除三级缓存,避免其他地方可以重复获取进行AOP操作
								this.singletonFactories.remove(beanName);
							}
						}
					}
				}
			}
		}
		return singletonObject;
	} 
2.3 实例A创建过程 生命周期

singletonsCurrentlyInCreation add(A) ,A正是当前正在创建的bean

1.推断构造方法(优先使用无参构造方法),确定构造方法后,实例化目标对象A(new A()),如果提前暴露,则需要调用三级缓存的lambda表达式 Map<a,ObjectFactory>(将创建bean实例存放在二级缓存中,然后移出三级缓存中的lambda表达式,再从二级缓存中获取到bean实例).

2.属性注入 B,从一级缓存中查找B的实例,如果没有,则需要创建B的实例

        B创建过程 生命周期

        1.推断构造方法,实例化目标对象B

        2.属性注入A,一级缓存中没有,并检测到singletonsCurrentlyInCreation中已经存在A,则                 说明出现循环依赖问题,再从二级缓存中寻找A的实例;如果没有,从三级缓存                             (ObjectFactory)中查找, 如果需要用到AOP,则是A的代理对象,如果不需要则是A的目             标对象;先存入二级缓存中,再移出三级缓存中的bean(确保单例),再从二级缓存中个获             取A的实例

        3.初始化后,判断是否需要AOP,

        4.将完整走完生命周期过程的实例bean,存入到 singletonObjects “一级缓存”单例池中

3. 初始化后,判断是否需要AOP(此时判断是否提起进行过AOP,在earlyProxyReferences中查找,若有,则可以从二级缓冲中查找A代理对象)
4. 将完整走完生命周期过程的实例bean,存入到 singletonObjects “一级缓存”单例池中

singletonsCurrentlyInCreation remove(A),  移出已创建完成的A。

2.4 二级缓存的作用

在2.3中我们可以看到,当A与B依赖共享时,二级缓存并没有起到作用,当三级缓存创建好bean实例放入二级缓存中后,B就从二级缓存中获取到A的实例,看似存入二级缓存是多余的步骤,实则不然。

如果存在第三个类C,A注入B与C,B与C都注入A,则当B注入A,且将A放入二级缓存中时,C再注入属性A就可以从二级缓存中直接获得(同时三级为了实现单例,三级缓存中A的lambda表达式singletonFactories在创建一个实例A后,便被移出,C也无法通过三级缓存获取到A的实例),确保了单例setter注入。

2.5 三级缓存的作用

二级缓存中只有beanName属性,bean实例不完全,如果想对实例对象进行增强,直接使用实例对象是行不通的,因此,需要在第三季缓存中添加ObjecFactory对象,bean工厂的lambda表达式,包含id、class、beanName、scope等多个属性,才能进行更多的操作。

3. 其他循环依赖

3.1 多例循环依赖(多例的setter注入)

解决方案:尽量使用单例setter注入方式

@Scope("prototype")
@Component
public class A {

    @Autowired
    private B b;

}

@Scope("prototype")
@Component
public class B {

    @Autowired
    private A a;

}

@ComponentScan(basePackages = "com.igeek.ch03.prototype")
public class MyConfig {
}

public class MainTest {

    public static void main(String[] args) {

        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig.class);

        //多例setter注入,设置
        //出现原因:A属性注入B,B属性注入A,出现循环依赖问题,Spring设计三级缓存不能够解决此问题,每一次获取实例bean都是新的
        //BeanCurrentlyInCreationException:  Requested bean is currently in creation: Is there an unresolvable circular reference?
        //解决方案:尽量使用单例setter注入方式,又可以使用Spring的三级缓存
        B b1 = ac.getBean(B.class);
        B b2 = ac.getBean(B.class);
        System.out.println(b1);
        System.out.println(b2);

    }

}
3.2 构造器注入

解决方法:使用@Lazy注解字 延迟加载

@Component
public class A {

    private B b;

    //延迟加载
    @Lazy
    @Autowired(required = false)
    public A(B b){
        this.b = b;
    }
}

@Component
public class B {

    private A a;

    public B(A a){
        this.a = a;
    }

}

@ComponentScan(basePackages = "com.igeek.ch03.constructor")
public class MyConfig {
}

public class MainTest {

    public static void main(String[] args) {

        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig.class);

        //构造器注入
        //出现原因:A属性注入B,B属性注入A,出现循环依赖问题,Spring三级缓存不能解决此情况,原因是构造器注入没能添加到三级缓存,也没有使用缓存,所以也无法解决循环依赖问题。
        //BeanCurrentlyInCreationException:Requested bean is currently in creation: Is there an unresolvable circular reference?
        //解决方案:@Lazy 延迟加载
        B b = ac.getBean(B.class);
        System.out.println(b);
    }

}
3.3  DependsOn 循环依赖

有一种特殊的场景,比如我们需要在实例化Bean A之前,先实例化Bean B,这个实例就可以使用@DependsOn注解

@DependsOn("b")
@Component
public class A {

    @Autowired
    private B b;

}

@Component
public class B {

    @Autowired
    private A a;

}

@ComponentScan(basePackages = "com.igeek.ch03.dependson")
public class MyConfig {
}

public class MainTest {

    public static void main(String[] args) {

        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig.class);

        //前置依赖 @DependsOn("b")
        //出现原因:循环依赖 + 使用@DependsOn注解
        //Circular depends-on relationship between 'b' and 'a'
        //解决方案:不要在循环依赖发生的情况下,去使用@DependsOn ; 指定加载先后关系
        B b = ac.getBean(B.class);
        System.out.println(b);
    }

}

四、总结

4.1 出现循环依赖如何捷俊
4.1.1 生成代理对象产生的循环依赖

1. 使用@Lazy注解,延迟加载

2.使用@DependsOn注解,指定加载先后顺序

3. 修改文件名称,改变循环依赖项的加载属性

4.1.2 使用@Depends参数的依赖地方

这类循环依赖问题要找到@DependsOn注解循环依赖的地方,迫使它不循环一来就就可以解决

4.1.3 多例循环依赖

可以通过把bena改为单例的方法解决

4.1.4

这类依赖问题可以通过@Lazy来注解解决

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值