Spring解决循环引用
循环引用是指以下情况:
@Component("TestService1")
public class TestService1 {
@Autowired
TestService2 testService2;
public TestService1(){
System.out.println("construct TestService1");
}
String name = "test1";
public void print(){
System.out.println("this is in TestService1");
System.out.println(testService2.name);
}
}
@Component("TestService2")
public class TestService2 {
@Autowired
TestService1 testService1;
public TestService2(){
System.out.println("construct TestService2");
}
String name = "test2";
public void print(){
System.out.println("this is in TestService2");
System.out.println(testService1.name);
}
}
对于这两个类,他们分别在自己的成员变量引用了对方,这就是循环依赖。
Spring默认是支持循环依赖的
private boolean allowCircularReferences = true;
下面是容器的初始化代码:
public class Test {
public static void main(String[] args) {
// ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
// IAccountDao aDao = (IAccountDao) ac.getBean("accountDao");
// System.out.println(aDao);
/**
* spring bean和对象的区别
* bean:是一个有生命周期的在spring容器中的java对象
* 对象:就是new出来的java对象
*
* bean创建的大致步骤:
* 首先根据注解或者xml文件的配置知道要扫描哪一些类
* 通过classloader加载类文件,根据类的注解信息配置将这个得到一个类的beandefination对象。
* 然后这些对象会用一个map来进行索引。
* 随后spring会根据每个defination对象来初始化每个单例对象。并将单例对象放入到一个map中索引。
* 在初始化之前,spring会检查是否有用户扩展的前置处理器可以执行,如果有的话就执行。
*
*
* spring的生命周期:
* 1.new
* 2.注入
* 3.执行生命周期方法
* 4.AOP代理
* 5.放入单例池
*/
/**
* spring bean的初始化过程
*/
//spring的bean初始化是在这里完成的
AnnotationConfigApplicationContext ac= new AnnotationConfigApplicationContext(SpringConfig.class);
TestService1 t1 = (TestService1) ac.getBean(TestService1.class);
t1.print();
}
}
spring 的循环引用的解决是在bean的创建过程中。方法可以概括为如下:
假设A中引用了B,B中引用了A。那么这个时候初始化A的时候,首先会在单例池中找A,如果找不到。那么就创建A。创建的方式是通过反射的方式。创建完成之后,我们只是拥有了A的对象,这个时候还不是一个bean。创建完成之后,spring会在AOP的后置处理器当中将一个能创建这个对象的工厂放入到二级缓存当中,这个是解决循环引用的关键。然后通过spring的后置处理器,为这个A对象注入B对象,注入B对象是通过反射的方式。首先还是要找B,然后找不到开始创建,这个过程和创建A的过程是一样的。然后注入B中的A的时候,又会去找,首先是在单例池中找,然后在缓存中找。找到之后注入到B中。然后返回回去又将创建好的B注入到A中。
以下是源码解析:
从doGetBean这个方法开始。
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
/**
* 通过name获取beanName,这里不使用name直接作为beanName有两个原因
* 1.name可能以&字符开头,表明调用者想获取FactoryBean本身,而不是FactoryBean实现类所创建的
* bean。在BeanFactory中,FactoryBean的实现类和其他的bean存储方式是一样的。区别在于名字上多了个&
* 所以要移除这个&
* 2.还是别名的问题,转换需要。
*/
final String beanName = transformedBeanName(name);
Object bean;
/**
* 第一次getSingleton(beanName)是解决循环依赖最重要的方法
* 首先spring会去单例池中根据名字获取这个bean,单例池就是一个map,如果对象被创建了就会直接去map当中
* 拿出来并且返回。但是问题来了,为什么会在创建的时候去获取一次呢,因为我们肯定是知道这个bean这个时候是
* 没有创建的。我们可以对doGetBean这个方法分析一下。
* 比如这个时候是A依赖B,B依赖A。A开始创建,第一次调用getSingleton(A)方法没有获取到。那么就会调用Create
* 方法进行创建,这个时候就有了new A这个对象,在实例化的时候又会调用getSingleton方法,两次嗲用的方法是重载的
* 第二次调用就会判断出A实际在创建当中,然后将A放入到一个set当中。这个set当中记录的就是当前正在创建的类
* 所以实在第二次getSingleton(A)方法中记录了自己已经开始实例化这个类。到这里A还只是一个对象。并不是一个bean
* 在spring生命周期当中,一共有三个map
* 第一个:singletonObjects:存放单例bean对象
* 第二个:singletonFactores:存放临时对象,这个里面存的是一个工厂
* 第三个:存放临时对象
* 第二个和第三个是不同的,但是他们都是为了解决循环依赖而存在的
* 接着上面的,如果说A这个时候进行
*/
// Eagerly check singleton cache for manually registered singletons.
//如果这里是我们的bean,那么第一次获取肯定是空的,那么这个时候就会走else
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
......
}
else {
// Fail if we're already creating this bean instance:
// We're assumably within a circular reference.
//判断这个对象是否在创建中,如果这个线程判断当前在创建中,那么肯定出现了并发问题
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
// Check if bean definition exists in this factory.
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
..........
}
if (!typeCheckOnly) {
markBeanAsCreated(beanName);
}
try {
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
.......
}
// Create bean instance.
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
//这里才开始正式创建
return createBean(beanName, mbd, args);
}
.............
...............
}
return (T) bean;
}
doGetBean()方法中第一次调用getSingleton()方法,试图从单例池或者缓存中取出对象。但是如果一开始这个肯定是空的。
那么最后就会第二次调用getSingleton()方法,然后调用createBean方法开始创建bean。
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
RootBeanDefinition mbdToUse = mbd;
...............
// Prepare method overrides.
try {
mbdToUse.prepareMethodOverrides();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),
beanName, "Validation of method overrides failed", ex);
}
try {
// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
//第一次调用后置处理器(会有9个地方调用了后置处理器)
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {
return bean;
}
}
.....
...........
try {
//调用doCreateBean
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
return beanInstance;
}
}
最后调用doCreateBean方法,真正解决循环引用的代码就在这里。
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
if (instanceWrapper == null) {
//实例化对象,里面第二次调用后置处理器(主要是推断构造方法,然后下面会利用构造方法来构造bean)
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
//对象被构造出来了,这里它还不是一个bean,到这里属性并没有注入,里面的声明周期方法也没有执行
final Object bean = instanceWrapper.getWrappedInstance();
Class<?> beanType = instanceWrapper.getWrappedClass();
if (beanType != NullBean.class) {
mbd.resolvedTargetType = beanType;
}
....................
..................
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
//判断是否需要循环依赖,默认为ture的。以为在这个判断中,第一个和第三个条件是坑定成立的。所以开启循环依赖主要就是看第二个条件
//所以默认开启循环依赖的证明就在于allowCircularReferences这个属性默认为true
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isDebugEnabled()) {
logger.debug("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
//第四次调用后置处理器,判断是否需要AOP
//这里将创建好的对象放入了二级缓存
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
//填充属性,也就是自动注入
//里面会完成第5次和第6次后置处理器的调用
populateBean(beanName, mbd, instanceWrapper);
//初始化spring
//里面进行第7次和第8次后置处理器的调用
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
..........
............
return exposedObject;
}doCreateBean方法当中,首先推断类的构造方法,然后利用构造方法来反射创建对象。但是创建之后这仅仅是一个对象。从名字也可以判断,addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean))**;这个方法将这个对象放入了二级缓存**。这个是解决循环引用的关键。最终到populateBean方法中开始属性注入。
进入到populateBean方法当中,这个方法如下:
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
..........
..........
if (hasInstAwareBpps) {
//将后置处理器全部取出来,找到主动注入的那个处理器
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof InstantiationAwareBeanPostProcessor) {
InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
//这句代码真正完成了属性填充,其中的一个后置处理器会解析斌处理@Autow这个朱姐。循环依赖也是在这个处理器当中完成的
pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
if (pvs == null) {
return;
}
}
}
}
if (needsDepCheck) {
checkDependencies(beanName, mbd, filteredPds, pvs);
}
}
if (pvs != null) {
applyPropertyValues(beanName, mbd, bw, pvs);
}
}
在这个处理器当中,对于当前类TestService1,它会和刚刚一样用doGetBean方法去找TestService2.
然后又会重复上诉过程,直到当TestService2执行到属性注入的时候。这个时候使用doGetBean到单例池中去找TestService1。
//传入的第二个参数就是指是否允许延迟加载
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//第一次调用getSongleton,返回肯定为空
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
//从三级缓存拿,如果是第一次坑定是拿不到的
singletonObject = this.earlySingletonObjects.get(beanName);
//如果从三级缓存中拿不到,那么判断是否支持循环依赖,再从二级缓存中拿
/**
* 为什么不直接从二级缓存中拿,而是要put到三级缓存中呢
* 性能问题:因为二级缓存是一个工厂,代码相当复杂。从二级缓存中创建出对象的时候进行大量的操作
* 为了避免每次引用这个对象都要使用工厂来创建,就需要使用到三级缓存。
* 既然有性能消耗,那么为什么还要使用工厂呢?
* 解答:因为在前面说过spring的生命周期:
* 1.new
* 2.注入
* 3.执行生命周期方法
* 4.AOP代理
* 5.放入单例池
* 还是循环依赖的场景。如果此时A到了注入的那一步,那么就会进入到B的生命周期。如果这个地方我们不使用工厂
* 那么在B中注入的A就是只执行到第二步的一个A的对象。B的A就不是AOP代理过后的对象。为了解决这个问题使用工厂,因为
* 它可以使这个A的AOP操作提前到这个工厂中来执行。所以说这个工厂的代码也是极其复杂的。为了避免重复执行这里面
* 的代码,这里也用到了三级缓存的。
*/
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
//从二级缓存中拿到了
singletonObject = singletonFactory.getObject();
//将其放到三级缓存中
this.earlySingletonObjects.put(beanName, singletonObject);
//将二级缓存中的清除掉
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
这个时候会首先从单例池拿。因为TestService1还没有完成bean的生命周期,所以在池中是拿不到的。首先判断三级缓存有没有,如果有直接返回,如果没有,我们就会从二级缓存中取出可以创建TestService1的工厂对象。然后将使用工厂对象将TestService1对象弄出来之后,在放入到三级缓存,然后删除二级缓存中的工厂,然后返回。这样TestService2就可以完成属性注入,然后返回使TestService1也可以完成属性注入,这就解决了循环引用的问题。
三级缓存
spring解决循环引用主要就是靠的三级缓存,注释中已经说过了。这里总结一下:
一级缓存:单例池singletonObjects
二级缓存:singletonFactory 工厂
三级缓存:earlySingletonObjects 一个map
单例池中保存的就是经历过生命周期的bean对象。二三级缓存是解决循环引用的。
为什么要将二级缓存的数据取了再放到三级缓存中呢?
因为工厂是一个很庞大的对象如果每次解决循环引用都使用工厂去new对象,那么会有很大的性能消耗。所以这里使用map来保存。
为什么要使用工厂呢?
注释中解释了,主要的目的是工厂中完成了AOP