// A依赖了B,B是A对象中的一个属性
class A{
public B b;
}
// B依赖了A
class B{
public A a;
}
在普通的代码中,对象之间有依赖是很正常的,但是在Spring中,Bean对象的创建是要经过一系列的生命周期的。其中,有些循环依赖,Spring可以帮我们解决,但是有些就只能靠我们程序员手动解决。
在了解循环依赖之前,我们需要先熟悉Bean的生命周期,即:创建过程。
首先,被Spring所管理的对象就叫做Bean。
Bean的主要生命周期(创建过程)如下:
①Spring扫描加了特殊注解(@Bean、@Component…)的类,得到对应的BeanDefinition
BeanDefinition包含了我们对bean的配置,就类似于XML配置中的
标签,其中包含: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还在创建过程中)
Spring采用三级缓存机制,帮助我们解决了部分的循环依赖问题
三级缓存:
一级缓存:singletonObjects
二级缓存:earlySingletonObjects
三级缓存:singletonFactories
产生循环依赖原因是:
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类
@Component
public class User {
}
@Component
public class TestBeanPostProcessor implements BeanPostProcessor {
@Override
public 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@5e025e34
com.test.service.User@1b0375c6
因此,BeanPostProcessor完全可以替换掉某个beanName对应的bean对象
而BeanPostProcessor的执行时间在bean的属性注入之后,循环依赖是发生在属性注入过程中的,所以就有可能导致,注入给B对象的A对象和经历过完整生命周期的A对象不是同一个对象,这样就产生了问题。【因此,这种情况下的循环依赖,Spring是无法解决的,因为在对bean的属性注入时,Spring也不知道A对象后续会经过哪些BeanPostProcessor以及会对A对象进行什么处理。】
AOP就是通过一个BeanPostProcessor来实现的,Spring中的AOP分为两种:JDK动态代理、CGLib的动态代理,一个使用的是接口,一个使用的是继承;所以如果给一个类的某个方法设置了切面,那么这个类最终就需要生成一个代理对象。
那么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(单例池)中
至此,整个循环依赖解决完毕。