1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > 面试——Spring中的循环依赖

面试——Spring中的循环依赖

时间:2024-05-19 20:15:11

相关推荐

面试——Spring中的循环依赖

1 什么是Spring循环依赖

// A依赖了B,B是A对象中的一个属性class A{public B b;}// B依赖了Aclass B{public A a;}

在普通的代码中,对象之间有依赖是很正常的,但是在Spring中,Bean对象的创建是要经过一系列的生命周期的。其中,有些循环依赖,Spring可以帮我们解决,但是有些就只能靠我们程序员手动解决。

在了解循环依赖之前,我们需要先熟悉Bean的生命周期,即:创建过程。

2 Bean的生命周期

首先,被Spring所管理的对象就叫做Bean。

Bean的主要生命周期(创建过程)如下:

①Spring扫描加了特殊注解(@Bean、@Component…)的类,得到对应的BeanDefinition

BeanDefinition包含了我们对bean的配置,就类似于XML配置中的<bean>标签,其中包含:bean的全限定类名、scope等一些系列配置

②根据BeanDefinition生成对应bean

③根据class判断类的构造方法

④根据推断出来的构造方法,通过反射,得到一个对象(原始对象【未进行依赖注入、AOP等操作】)

⑤填充原始对象中的属性(依赖注入)【加了@AutoWired】

⑥如果原始对象中的某个方法被AOP(增强)了,则需要根据原始对象生成一个代理对象

⑦把最终生成的代理对象放入单例池(singletonObjects),下次我们需要获取bean中,就可以直接从单例池中获取。【类比数据库连接池】

那么在第④步通过构造方法反射时,就容易出现问题:比如上文说的A,A类中存在一个B类的b属性。

1、在A类生成了一个原始对象时,就会给b属性赋值,此时就需要B类的对象,那么Spring就会根据b属性的类型和属性名去BeanFactory获取B类对应的单例Bean。

2、如果BeanFactory中存在B类对应的bean,则直接拿来赋值,那么就不会产生循环依赖。但是如果BeanFactory不存在B类对应的bean(B类的一个对象),那么就需要生成B类的对象,就会经历B的生命周期,但是在创建B的bean过程中,B又依赖A,此时又需要获取A类对应的bean,而此时A类的bean还在创建过程中,于是就出现了循环依赖。

主要过程:

A(bean)创建-》依赖B-》创建B(bean)-》B依赖A(A的bean还在创建过程中)

3 Spring解决循环依赖方法(三级缓存)

Spring采用三级缓存机制,帮助我们解决了部分的循环依赖问题

3、1 三级缓存概念

三级缓存:

一级缓存:singletonObjects

二级缓存:earlySingletonObjects

三级缓存:singletonFactories

singletonObjects中缓存的是已经经历了完整生命周期的bean对象。【完整生命周期】earlySingletonObjects存入的是还没经历完整个生命周期的bean对象【部分生命周期】singletonFactories缓存的是ObjectFactory对象工厂(用来创建某个对象的)

3、2 解决循环依赖思路

产生循环依赖原因是:

A创建时— —>需要B— —>去创建B— —>需要A,于是产生了循环

此时,我们通过加入缓存(中间人)来破循环,如下图所示:

A的bean在创建过程中,在进行依赖注入之前,就先把A的原始对象放入缓存earlySingletonObjects(提早暴露,方便其他Bean获取),在将A放入缓存后再进行依赖注入,此时A的bean依赖了B的bean,如果发现此时BeanFactory中没有B的bean,则去创建。在创建B的bean时,需要A的bean,此时去一级缓存(单例池)中未获取到,去二级缓存(earlySingletonObjects)中拿到了A的原始对象【此时是A的原始对象,不是最终的bean,没有走完全部生命周期】,B的原始对象依赖注入完之后,B的生命周期结束,A的生命周期也结束。

在整个过程中,都只有一个A的原始对象,对于B而言,就算注入的是A的原始对象也没有关系,因为A原始对象在后续的生命周期都在堆中没有发生变化。

这时,可能有人会想,只需要一个缓存就能解决循环依赖了,那么为什么Spring中还要又第三级缓存singletonFactories呢?

B依赖的A和最终的A不是同一个对象,因为在一个bean的生命周期最后,Spring提供了BeanPostProcess,可以对bean进行加工,这个加工不仅而可以修改bean的属性值,还可以替换掉当前的bean。

//此时有一个User类@Componentpublic class User {}

@Componentpublic class TestBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {// 注意这里,生成了一个新的User对象if (beanName.equals("user")) {System.out.println(bean);User user = new User();//生成新的user对象return user;}return bean;}}

public class Test {public static void main(String[] args) {AnnotationConfigApplicationContext context =new AnnotationConfigApplicationContext(AppConfig.class);User user = context.getBean("user", User.class);System.out.println(user);}}

最终得到的user对象就是不同的:

com.test.service.User@5e025e34com.test.service.User@1b0375c6

因此,BeanPostProcessor完全可以替换掉某个beanName对应的bean对象

而BeanPostProcessor的执行时间在bean的属性注入之后,循环依赖是发生在属性注入过程中的,所以就有可能导致,注入给B对象的A对象和经历过完整生命周期的A对象不是同一个对象,这样就产生了问题。【因此,这种情况下的循环依赖,Spring是无法解决的,因为在对bean的属性注入时,Spring也不知道A对象后续会经过哪些BeanPostProcessor以及会对A对象进行什么处理。】

某个beanName对应的最终对象和原始对象不是一个对象=》AOP

AOP就是通过一个BeanPostProcessor来实现的,Spring中的AOP分为两种:JDK动态代理、CGLib的动态代理,一个使用的是接口,一个使用的是继承;所以如果给一个类的某个方法设置了切面,那么这个类最终就需要生成一个代理对象。

过程:A类 -> 生成一个普通A-> 属性注入 ->基于切面生成代理对象->把代理对象放入singletonObjects单例池

那么Spring是如何解决对象不一致的情况呢,这里就需要用到第三级缓存singletonFactories

首先,singletonFactories存的是某个beanName对应的ObjectFactory,在bean的生命周期中,生成完原始对象之后,就会构造一个ObjectFactory放入singletonFactoies中。【ObjectFactory是一个函数式接口,所以支持Lambda表达式:()->getEarlyReference(beanName, mbd, bean)

ObjectFactory(上文的Lambda表达式),中间有getEarlyBeanReference方法【获取代理对象】,注意此时我们存入singletonFactories时不会执行Lambda表达式,也就是不会执行getEarlyBeanReference方法。

getEarlyBeanReference():①得到cachekey(beanName) ②将beanName和bean(原始对象)存入earlyProxyReferences ③调用wraplfNecessary进行AOP,得到代理对象

1、从singletonFactories根据beanName得到ObjectFactory,然后执行其getEarlyBeanReference方法,此时会得到A经过AOP后的代理对象,然后将其放入earlySingletonObjects中,此时并没有将代理对象放入singletonObjects中

2、earlySingletonObjects作用:此时,我们只得到了A的代理对象,这个对象还不完整,因为还未进行属性注入,所以此时只能将A的代理对象放入earlySingletonObjects(singletonObjects单例池中放入的是全部生命周期后的bean),这样就能保证其他依赖了A对象的类获取到的就是同一个代理对象了。

3、在B创建完之后,A继续进行生命周期,在A完成属性注入后,会按照本来的逻辑进行AOP,而此时A的原始对象已经经历过了AOP,所以对于A本身而言就不会再去进行AOP了。

如何判断是否已经进行AOP?

会利用上文提到的earlyProxyReferences,在AbstractAutoProxyCreator的postProcessAfterInitialization方法中,会去判断档期按beanName是否存在earlyProxyReferences,如果存在则表示已经提前进行过AOP,无需再次进行。

4、对于A而言,进行AOP判断以及BeanPostProcessor(AOP实现)执行后,需要将A对应的对象放入singletonObjects中,但是,此时应该是从earlySingletonObjects中得到A的代理对象,然后放入singletonObjects(单例池)中

至此,整个循环依赖解决完毕。

4 总结

singletonObjects:缓存某个经历了完整生命周期的beanearlySingletonObjects:缓存提前拿到原始对象并进行了AOP之后的代理对象;未进行属性注入的以及后续的BeanPostProcessor【AOP】等生命周期的beansingletonFactories:缓存的是一个ObjectFactory,用来生成进行了AOP之后的代理对象(在每个Bean的生成过程中都会提前暴露一个工厂),这个工厂可能用得到,也可能用不到

4.earlyProxyReferences:记录对象是否进行了AOP

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。