Spring循环依赖

Spring循环依赖

什么是Spring循环依赖?

这个很好理解,就是多个bean之间相互依赖,形成了一个闭环。

比如:A依赖于B、B依赖于C、C依赖于A。

代码中表示:

public class A{
    B b;
}
public class B{
    C c;
}
public class C{
    A a;
}

如何解决循环依赖?

Spring为了解决这个问题,引入了一个三级缓存。

在这里插入图片描述

这里我们假设一个场景进行讲解:ServiceA、ServiceB相互依赖。

  1. Spring容器依次创建两个bean时,发现在缓存中没有ServiceA,因此将新创建好的未注入属性的ServiceA放到三级缓存中去。
  2. 然后ServiceA进行属性注入时,发现依赖ServiceB,转而去实例化B。
  3. 同样创建对象ServiceB,注入属性时发现依赖ServiceA,依次从一级到三级缓存查询ServiceA,从三级缓存通过对象工厂拿到ServiceA,把ServiceA放入二级缓存,同时删除三级缓存中的ServiceA,此时,ServiceB已经实例化并且初始化完成,把ServiceB放入一级缓存。
  4. 接着继续创建ServiceA,顺利从一级缓存拿到实例化且初始化完成的ServiceB对象,ServiceA对象创建也完成,删除二级缓存中的ServiceA,同时把ServiceA放入一级缓存
  5. 最后,一级缓存中保存着实例化、初始化都完成的ServiceA、ServiceB对象

总的来说,当一个bean具有循环依赖时,是按照如下操作解决循环依赖问题的:

1.从容器中获取serviceA
2.容器尝试从3个缓存中找serviceA,找不到
3.准备创建serviceA
4.调用serviceA的构造器创建serviceA,得到serviceA实例,此时serviceA还未填充属性,未进行其他任何初始化的操作
5.将早期的serviceA暴露出去:即将其丢到第3级缓存singletonFactories中
6.serviceA准备填充属性,发现需要注入serviceB,然后向容器获取serviceB
7.容器尝试从3个缓存中找serviceB,找不到
8.准备创建serviceB
9.调用serviceB的构造器创建serviceB,得到serviceB实例,此时serviceB还未填充属性,未进行其他任何初始化的操作
10.将早期的serviceB暴露出去:即将其丢到第3级缓存singletonFactories中
11.serviceB准备填充属性,发现需要注入serviceA,然后向容器获取serviceA
12.容器尝试从3个缓存中找serviceA,发现此时serviceA位于第3级缓存中,经过处理之后,serviceA会从第3级缓存中移除,然后会存到第2级缓存中,然后将其返回给serviceB,此时serviceA通过serviceB中的setServiceA方法被注入到serviceB中
13.serviceB继续执行后续的一些操作,最后完成创建工作,然后会调用addSingleton方法,将自己丢到第1级缓存中,并将自己从第2和第3级缓存中移除
14.serviceB将自己返回给serviceA
15.serviceA通过setServiceB方法将serviceB注入进去
16.此时serviceA实例化可以完成(创建完成,然后将自己丢到第1级缓存并将自己从第2级缓存中剔除)

为什么要使用三级缓存?二级缓存不可以吗?

不可以,主要是为了生成代理对象。如果使用二级缓存,会导致被暴露出去的和最终的bean不是同一个bean。

可能有点难理解,这里需要一个例子进行讲解:该例子给Spring的getSingleton(beanName, false)传入了一个false参数,表示不会从三级缓存中获取bean,等同于现在只有两级缓存。

Service1

@Component
public class Service1 {
    public void m1() {
        System.out.println("Service1 m1");
    }

    private Service2 service2;

    @Autowired
    public void setService2(Service2 service2) {
        this.service2 = service2;
    }
}

Service2

@Component
public class Service2 {

    public void m1() {
        System.out.println("Service2 m1");
        this.service1.m1();// 这里调用了Service1的m1方法
    }

    private Service1 service1;

    @Autowired
    public void setService1(Service1 service1) {
        this.service1 = service1;
    }

    public Service1 getService1() {
        return service1;
    }
}

然后我们需要在Service1上面加个拦截器,要求在调用Service1的任何方法前都要输出一行日志你好,service1"

@Component
public class MethodBeforeInterceptor implements BeanPostProcessor {
    @Nullable
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if ("service1".equals(beanName)) {
            //代理创建工厂,需传入被代理的目标对象
            ProxyFactory proxyFactory = new ProxyFactory(bean);
            //添加一个方法前置通知,会在方法执行之前调用通知中的before方法
            proxyFactory.addAdvice(new MethodBeforeAdvice() {
                @Override
                public void before(Method method, Object[] args, @Nullable Object target) throws Throwable {
                    System.out.println("你好,service1");
                }
            });
            //返回代理对象
            return proxyFactory.getProxy();
        }
        return bean;
    }
}

按照我们的思路,应该说无论是Service1还是Service2调用m1方法,最终的结果都应该是有日志。但是事与愿违,

在只有两级缓存的情况下,并不会出现我们预期的情况:

System.out.println("----A-----");
service2.m1(); 
System.out.println("----B-----");
service1.m1(); 
System.out.println("----C-----");

----A-----
Service2 m1
Service1 m1
----B-----
你好,service1
Service1 m1
----C-----

是的,在这里你可能会猜测,是不是service2中注入的service1不是代理对象,所以没有加上拦截器的功能?恭喜你,你的猜想是正确的,上面代码注入到service2中的service1是早期的service1,而最终spring容器中的service1变成一个代理对象了,早期的和最终的不一致了。

注意:结合上面的循环依赖解决流程,我们可以判断出Service2此时在二级缓存中找到Service1**(注意此时还没有进行代理)**,然后将Service1提升到一级缓存,注入Service1,完成一系列操作后Service2返回给Service1,然后Service1的实例化才完成,**才会开始进行代理。**不同于三级缓存,两级缓存的情况下这里会更新一级缓存。

因为service2中注入的是早期的service1,注入的时候service1还不是一个代理对象,所以没有拦截器中的功能。

那么,我们怎么在三级缓存的情况下解决上面Service2调用m1方法没有日志的情况呢?

自定义一个SmartInstantiationAwareBeanPostProcessor,然后在其getEarlyBeanReference中来创建代理

@Component
public class MethodBeforeInterceptor implements SmartInstantiationAwareBeanPostProcessor {
    @Override
    public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
        if ("service1".equals(beanName)) {
            //代理创建工厂,需传入被代理的目标对象
            ProxyFactory proxyFactory = new ProxyFactory(bean);
            //添加一个方法前置通知,会在方法执行之前调用通知中的before方法
            proxyFactory.addAdvice(new MethodBeforeAdvice() {
                @Override
                public void before(Method method, Object[] args, @Nullable Object target) throws Throwable {
                    System.out.println("你好,service1");
                }
            });
            //返回代理对象
            return proxyFactory.getProxy();
        }
        return bean;
    }
}

因此,我们可以总结下这个问题:三级缓存可以验证早期暴露的bean被其他bean使用过,并且用来判断被使用的和最终暴露的不一致的问题,二级缓存做不到。二级缓存无法判断早期bean是否被使用过,早期bean若没被使用过就无需判断早期暴露的bean和最终bean是否一致的问题。

循环依赖无法解决的情况

只有单例的bean会通过三级缓存提前暴露来解决循环依赖的问题,而非单例的bean,每次从容器中获取都是一个新的对象,都会重新创建,所以非单例的bean是没有缓存的,不会将其放到三级缓存中。

那就会有下面几种情况需要注意。

还是以2个bean相互依赖为例:serviceA和serviceB

情况1

条件

serviceA:多例

serviceB:多例

结果

此时不管是任何方式都是无法解决循环依赖的问题,最终都会报错,因为每次去获取依赖的bean都会重新创建。

情况2

条件

serviceA:单例

serviceB:多例

结果

若使用构造器的方式相互注入,是无法完成注入操作的,会报错。

若采用set方式注入,所有bean都还未创建的情况下,若去容器中获取serviceB,会报错,为什么?我们来看一下过程:

1.从容器中获取serviceB
2.serviceB由于是多例的,所以缓存中肯定是没有的
3.检查serviceB是在正在创建的bean名称列表中,没有
4.准备创建serviceB
5.将serviceB放入正在创建的bean名称列表中
6.实例化serviceB(由于serviceB是多例的,所以不会提前暴露,必须是单例的才会暴露)
7.准备填充serviceB属性,发现需要注入serviceA
8.从容器中查找serviceA
9.尝试从3级缓存中找serviceA,找不到
10.准备创建serviceA
11.将serviceA放入正在创建的bean名称列表中
12.实例化serviceA
13.由于serviceA是单例的,将早期serviceA暴露出去,丢到第3级缓存中
14.准备填充serviceA的属性,发现需要注入serviceB
15.从容器中获取serviceB
16.先从缓存中找serviceB,找不到
17.检查serviceB是在正在创建的bean名称列表中,发现已经存在了,抛出循环依赖的异常
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值