Spring循环依赖

目录

一、Spring循环依赖

1.什么是循环依赖

2.三级缓存方案

非AOP的二级缓存

结合了AOP的循环依赖

3.循环依赖的总结

二、如何手写一个Spring框架

1、一个手写IoC容器的思路

2、一个手写SpringMVC的思路

3、一个手写SpringAOP的思路


这一篇主要想围绕着Spring的循环依赖问题以及终极灵魂拷问如何手写Spring的问题讲讲。

一、Spring循环依赖

1.什么是循环依赖

Spring中的循环依赖一直是Spring中一个很重要的话题,一方面是因为源码中为了解决循环依赖做了很多处理,另外一方面是因为面试的时候,如果问到Spring中比较高阶的问题,那么循环依赖必定逃不掉。所以还是可以看一下这块的源码,看看Spring是如何解决循环依赖的问题的。

Spring中之所以会出现循环依赖跟Bean的生命周期有关系,在创建一个Bean的过程中如果依赖的另外一个Bean还没有创建,就会需要去创建依赖的那个Bean,而如果两个Bean相互依赖的话,就会出现循环依赖的问题。

①:构造器的循环依赖。【这个Spring解决不了,原因:因为加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决

StudentA有参构造是StudentB。StudentB的有参构造是StudentC,StudentC的有参构造是StudentA ,这样就产生了一个循环依赖的情况,

我们都把这三个Bean交给Spring管理,并用有参构造实例化

public class StudentA {  
  
    private StudentB studentB ;  
  
    public void setStudentB(StudentB studentB) {  
        this.studentB = studentB;  
    }  
  
    public StudentA() {  
    }  
      
    public StudentA(StudentB studentB) {  
        this.studentB = studentB;  
    }  
}  
public class StudentB {  
  
    private StudentC studentC ;  
  
    public void setStudentC(StudentC studentC) {  
        this.studentC = studentC;  
    }  
      
    public StudentB() {  
    }  
  
    public StudentB(StudentC studentC) {  
        this.studentC = studentC;  
    }  
}  
public class StudentC {  
  
    private StudentA studentA ;  
  
    public void setStudentA(StudentA studentA) {  
        this.studentA = studentA;  
    }  
  
    public StudentC() {  
    }  
   
    public StudentC(StudentA studentA) {  
        this.studentA = studentA;  
    }  
} 
<bean id="a" class="com.zfx.student.StudentA">  
    <constructor-arg index="0" ref="b"></constructor-arg>  
</bean>  
<bean id="b" class="com.zfx.student.StudentB">  
    <constructor-arg index="0" ref="c"></constructor-arg>  
</bean>  
<bean id="c" class="com.zfx.student.StudentC">  
    <constructor-arg index="0" ref="a"></constructor-arg>  
</bean>

下面是测试类:

public class Test {  
    public static void main(String[] args) {  
        ApplicationContext context = new ClassPathXmlApplicationContext("com/zfx/student/applicationContext.xml");  
        //System.out.println(context.getBean("a", StudentA.class));  
    }  
}  

执行结果报错信息为:

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException:   
    Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?  

②【setter循环依赖】field属性的循环依赖【setter方式 单例,默认方式-->通过递归方法找出当前Bean所依赖的Bean,然后提前缓存【会放入Cach中】起来。通过提前暴露 -->暴露一个exposedObject用于返回提前暴露的Bean。】

setter方式注入:

图中前两步骤得知:Spring是先将Bean对象实例化【依赖无参构造函数】--->再设置对象属性的

 这就不会报错了:

原因:Spring先用构造器实例化Bean对象----->将实例化结束的对象放到一个Map中,并且Spring提供获取这个未设置属性的实例化对象的引用方法。结合我们的实例来看,,当Spring实例化了StudentA、StudentB、StudentC后,紧接着会去设置对象的属性,此时StudentA依赖StudentB,就会去Map中取出存在里面的单例StudentB对象,以此类推,不会出来循环的问题喽。

2.三级缓存方案

假设按照上面代码Class A 和 B,按照从A->B的顺序来实例化,Spring创建bean的过程可以分为三个阶段:
1、实例化,对应方法:AbstractAutowireCapableBeanFactory # createBeanInstance方法
2、属性注入,对应方法:AbstractAutowireCapableBeanFactory # populateBean方法
3、初始化,对应方法:AbstractAutowireCapableBeanFactory # initializeBean

所以执行顺序是先在这个类中的 AbstractBeanFactory 按调用链执行如下三个方法:

//AbstractBeanFactory 
1、getBean("a")
2、doGetBean("a")
3、getSingleton("a") 

在调用getSingleton(a)方法,这个方法又会调用getSingleton(beanName, true),所以才进入到下面这个方法:

//DefaultSingletonBeanRegistry
public Object getSingleton(String beanName) {
    return getSingleton(beanName, true);
}

getSingleton(beanName, true)

这是个重点方法,该方法实际上就是到缓存中尝试去获取Bean,整个缓存分为三级
singletonObjects,一级缓存,存储的是所有创建好了的单例Bean
earlySingletonObjects,完成实例化,但是还未进行属性注入及初始化的对象
singletonFactories,提前暴露的一个单例工厂,二级缓存中存储的就是从这个工厂中获取到的对象

//DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        //一级缓存中获取-->完整bean
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
                //二级缓存中-->获取未属性注入的bean
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null && allowEarlyReference) {
                    //三级缓存中-->获取工厂
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        singletonObject = singletonFactory.getObject();
                        //三级缓存中的工厂getObject的对象-->放入二级缓存中
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }

因为是第一次创建,因此上面的三级缓存都未命中,此时会进入getSingleton的另外一个重载方法getSingleton(beanName, singletonFactory)。这里我们知道 singletonFactory 是需要等待createBean(beanName, mbd, args) 方法的返回,然后作为第二个输入参数给到下面 getSingleton 方法。

createBean 方法的返回将作为 getSingleton 的输入,然后会进入到下面这段代码中:

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(beanName, "Bean name must not be null");
        synchronized (this.singletonObjects) {
            Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
                //省略...
                try {
                    //入参的lambda会提供一个singletonFactory
                    //调用createBean方法创建一个Bean后返回
                    singletonObject = singletonFactory.getObject();
                    newSingleton = true;
                }
                //省略...
                if (newSingleton) {
                    //添加到一级缓存singletonObjects中
                    addSingleton(beanName, singletonObject);
                }
            }
            return singletonObject;
        }
    }

上面的代码主要实现了:将已经完全创建好了的单例Bean放入一级缓存中。在前面一步 createBean()方法的创建实例过程中还有一个 doCreateBean 方法,里面还有这样一段代码:

这个也就是在Bean实例化后,属性注入之前Spring将Bean包装成一个工厂添加进了三级缓存中,addSingletonFactory 对应源码如下:

// 这里传入的参数也是一个lambda表达式,() -> getEarlyBeanReference(beanName, mbd, bean)
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            // 添加到三级缓存中
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

那么getEarlyBeanReference方法又做了什么呢?进入源码看下:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
    }
    return exposedObject;
}

非AOP的二级缓存

这个地方的BeanPostProcessor后置处理器,只在处理AOP的实例对象时才会发挥作用,如果不考虑AOP,代码就是:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    return exposedObject;
}

可见,对于非Aop实例对象,这个工厂直接将实例化阶段创建的对象返回了!

现在整体来梳理一下,继续走A对象创建的流程,通过this.singletonFactories.put(beanName, singletonFactory)这个方法只是添加了一个工厂,通过这个工厂(ObjectFactory)的getObject方法可以得到一个对象。当A完成了实例化并添加进了三级缓存后,就要开始为A进行属性注入了,在注入时发现A依赖了B,那么这个时候Spring又会去getBean(b),然后反射调用setter方法完成属性注入。因为B需要注入A,所以在创建B的时候,又会去调用getBean(a),这个时候就又回到之前的流程了,但是不同的是,之前的getBean是为了创建Bean,而此时再调用getBean不是为了创建了,而是要从缓存中获取,因为之前A在实例化后已经将其放入了三级缓存singletonFactories中,此时getBean(a)的二级缓存会通过调用三级缓存的facotry,通过工厂的getObject方法将对象放入到二级缓存中并返回,所以此时getBean(a)的流程就是这样子了,一个清晰的流程图如下:

 

结合了AOP的循环依赖

如果在开启AOP的情况下,那么就是调用 getEarlyBeanReference 方法对应的源码如下:

public Object getEarlyBeanReference(Object bean, String beanName) {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    this.earlyProxyReferences.put(cacheKey, bean);
    // 如果需要代理,返回一个代理对象,不需要代理,直接返回当前传入的这个bean对象
    return wrapIfNecessary(bean, beanName, cacheKey);
}

对A进行了AOP代理的话,那么此时getEarlyBeanReference将返回一个代理后的对象,而不是实例化阶段创建的对象,这样就意味着B中注入的A将是一个代理对象而不是A的实例化阶段创建后的对象。整个注入的流程图就变成了如下:

3.循环依赖的总结

1、Spring到底是如何解决循环依赖的呢,这里来一波文字的总结:

Spring通过三级缓存解决了循环依赖,其中一级缓存为单例池(singletonObjects),二级缓存为早期曝光对象earlySingletonObjects,三级缓存为早期曝光对象工厂(singletonFactories)。

当A、B两个类发生循环引用时,在A完成实例化后,就使用实例化后的对象去创建一个对象工厂,并添加到三级缓存中,如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象,

如果A没有被AOP代理,那么这个工厂获取到的就是A实例化的对象。

当A进行属性注入时,会去创建B,同时B又依赖了A,所以创建B的同时又会去调用getBean(a)来获取需要的依赖,此时的getBean(a)会从缓存中获取,

第一步,先获取到三级缓存中的工厂;

第二步,调用对象工工厂的getObject方法来获取到对应的对象,得到这个对象后将其注入到B中。

紧接着B会走完它的生命周期流程,包括初始化、后置处理器等。

当B创建完后,会将B再注入到A中,此时A再完成它的整个生命周期。至此,循环依赖结束!

2、为啥要用三级缓存,是否可以用二级缓存

在普通的循环依赖的情况下,三级缓存没有任何作用。三级缓存实际上跟Spring中的AOP相关。AOP场景下的getEarlyBeanReference 会拿到一个代理的对象,但是不确定有没有依赖,需不需要用到这个依赖对象,所以先给一个工厂放到三级缓存里。

这个工厂的目的在于延迟对实例化阶段生成的对象的代理,只有真正发生循环依赖的时候,才去提前生成代理对象,否则只会创建一个工厂并将其放入到三级缓存中,但是不会去通过这个工厂去真正创建对象。

二、如何手写一个Spring框架

1、一个手写IoC容器的思路

IOC的实现思路如下:

  • 首先有一个配置文件定义了应用的基础包, 也就是Java源码路径.
  • 读取基础包名, 然后通过类加载器获取到应用中所有的Class对象, 存储到一个集合中.
  • 获取应用中所有Bean (Controller和Service) 的Class对象, 通过反射创建实例, 然后存储到 Bean容器中.
  • 遍历Bean容器中的所有Bean, 为所有带 @Autowired 注解的属性注入实例.
  • IOC操作要在应用启动时就完成, 所以必须写在静态代码块中.

2、一个手写SpringMVC的思路

(1)读取配置

SpringMVC本质上是一个Servlet,这个 Servlet 继承自 HttpServlet。FrameworkServlet负责初始化SpringMVC的容器,并将Spring容器设置为父容器。因为本文只是实现SpringMVC,对于Spring容器不做过多讲解。

为了读取web.xml中的配置,我们用到ServletConfig这个类,它代表当前Servlet在web.xml中的配置信息。通过web.xml中加载我们自己写的MyDispatcherServlet和读取配置文件。

(2)初始化阶段

在前面我们提到DispatcherServlet的initStrategies方法会初始化9大组件,但是这里将实现一些SpringMVC的最基本的组件而不是全部,按顺序包括:

  • 加载配置文件
  • 扫描用户配置包下面所有的类
  • 拿到扫描到的类,通过反射机制,实例化。并且放到ioc容器中(Map的键值对 beanName-bean) beanName默认是首字母小写
  • 初始化HandlerMapping,这里其实就是把url和method对应起来放在一个k-v的Map中,在运行阶段取出

(3)运行阶段

每一次请求将会调用doGet或doPost方法,所以统一运行阶段都放在doDispatch方法里处理,它会根据url请求去HandlerMapping中匹配到对应的Method,然后利用反射机制调用Controller中的url对应的方法,并得到结果返回。按顺序包括以下功能:

  • 异常的拦截
  • 获取请求传入的参数并处理参数
  • 通过初始化好的handlerMapping中拿出url对应的方法名,反射调用

3、一个手写SpringAOP的思路

1 扫描 aop 包, 获取 aspect 的类
2 根据 切点 获取该切点的 类 和 方法
3 根据配置的 类 和 方法 为该类生成一个代理对象
4 将改代理对象放入 bean Map 中
5 调用的时候 将代理对象 转换成需要的对象

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring循环依赖指的是在Spring中,多个Bean之间存在相互依赖的情况。具体来说,当一个Bean A依赖于另一个Bean B,同时Bean B也依赖于Bean A时,就形成了循环依赖。这种情况下,Spring需要解决Bean的创建和依赖注入的顺序问题。 在Spring中,循环依赖问题是由于Bean的生命周期所引起的。Spring的Bean生命周期包括了Bean的实例化、属性注入、初始化以及销毁等过程。当出现循环依赖时,Spring会通过使用“提前暴露”的方式来解决这个问题。 具体来说,当Spring创建Bean A时,发现它依赖于Bean B,于是会创建一个A的半成品对象,并将其暂时放入一个缓存中。然后,Spring会继续创建Bean B,并将其注入到A的属性中。接着,Spring会继续完成B的创建,并将其放入缓存中。最后,Spring会将A的半成品对象交给B进行依赖注入,完成A的创建,并将其从缓存中移除。 需要注意的是,Spring循环依赖有一定的限制条件。例如,如果Bean A和Bean B都是单例模式,那么它们之间的循环依赖是无法解决的。因为单例模式下,Bean的创建和依赖注入是同时进行的,无法通过缓存来解决循环依赖。在这种情况下,程序员需要手动调整Bean的依赖关系或使用其他解决方案来避免循环依赖的问题。 综上所述,Spring循环依赖是指在Spring中多个Bean之间存在相互依赖的情况。Spring通过使用缓存和提前暴露的方式来解决循环依赖问题,但在某些情况下有一定的限制条件需要注意。<span class="em">1</span><span class="em">2</span><span class="em">3</span><span class="em">4</span>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值