Spring Bean加载过程 如何解决循环依赖问题?

这篇文章探讨一下Spring如果解决循环依赖问题
这里只探讨单例bean的循环依赖,多例bean,Spring不支持循环依赖(实在想支持可以在成员变量上加@Lazy注解)

一.什么是循环依赖

如下面代码所示:A类中有个B对象的成员变量,B类中有个A对象的成员变量,形成了一个相互依赖的关系

public class A {
	private B b;
	public void setB(B b) {
		this.b = b;
	}
}

public class B {
	private A a;
	public void setA(A a) {
		this.a = a;
	}
}

首先明确一点:我们如果不用Spring,单纯的自己创建对象来用,是不需要关注什么循环依赖问题的
还是上面的例子,我们可以分别把A、B类各自的成员变量变量通过set方法给设置进去,这样循环依赖就不是什么问题

A a = new A();
B b = new B();
a.setB(b);
b.setA(a);

二.Spring为什么要解决循环依赖问题

Spring之所以要解决循环依赖问题,是因为其特殊的依赖注入流程
创建bean大致流程如下

  1. 从单例池中获取bean
  2. 获取到,直接返回,获取不到继续流程
  3. 实例化bean
  4. 依赖注入
  5. 完成bean创建,对象存入单例池

把上面的例子改成Spring的写法

@Component
public class A {
	@Autowired
	private B b;
}

@Component
public class B {
	@Autowired
	private A a;
}

测试代码

public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        context.getBean(A.class);
    }

这里调用context.getBean(A.class)的流程大致如下

  1. 创建A对象
  2. A对象依赖注入,解析一番后,调用getBean(“b”);
  3. 创建B对象
  4. B对象依赖注入,同样解析一番后,调用getBean(“a”);

到这里就可以看出问题了
因为只有在bean依赖注入完成之后,才会存入单例池,如果Spring不管循环依赖问题的话,那上面的流程就会无限循环下去,因为每次getBean(“a”)、getBean(“b”)都无法从单例池获取到对应的对象,每次都去创建新对象,一直循环下去,同时也无法保证对象都是单例的

三.为什么是三级缓存,一级、二级不行吗?

为了讲清楚这个东西
我这里简单的实现了一个MyBeanFactory类来模拟getBean的过程,看看一级二级缓存存在什么问题

先看看只有一级缓存的情况
经过上面的分析,出现循环依赖的原因就是在依赖注入之前,没有保存对象的引用,导致循环调用getBean(“a”)方法时,获取不到之前创建的对象a
那一级缓存的方案就是调整一下对象存入单例池(一级缓存)的时机,把存入时机放到对象实例化后,初始化前

完整代码如下:

public class MyBeanFactory {

    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
    private final Map<String, ScannedGenericBeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

    public MyBeanFactory(String packageName) {
        // 利用Spring提供的工具,解析package下的BeanDefinition
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        MetadataReaderFactory metaReader = new CachingMetadataReaderFactory(resolver.getResourceLoader());
        String packagePath =
                ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + packageName.replace(".", "/") + "/**/*.class";
        try {
            Resource[] resources = resolver.getResources(packagePath);
            for (Resource r : resources) {
                MetadataReader reader = metaReader.getMetadataReader(r);
                ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(reader);
                // 获取beanName,类名首字母小写
                String beanName = this.getBeanName(sbd.getBeanClassName());
                sbd.setBeanClass(getClass().getClassLoader().loadClass(sbd.getBeanClassName()));
                // 保存到beanDefinitionMap
                beanDefinitionMap.put(beanName, sbd);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private String getBeanName(String beanClassName) {
        String shortClassName = ClassUtils.getShortName(beanClassName);
        return Introspector.decapitalize(shortClassName);
    }

    public Object getBean(String beanName) {
        // 从缓存中取对象
        Object bean = getSingleton(beanName);
        // 取到对象,直接返回
        if (bean != null) {
            return bean;
        }
        // 获取BeanDefinition
        ScannedGenericBeanDefinition definition = beanDefinitionMap.get(beanName);
        if (definition == null) {
            return null;
        }
        // 实例化对象
        bean = this.instanceBean(definition);
        // 加入缓存
        this.addSingleton(beanName, bean);
        // 依赖注入
        this.autowire(bean, definition);
        return bean;
    }

    private Object instanceBean(ScannedGenericBeanDefinition definition) {
        Class<?> beanClass = definition.getBeanClass();
        try {
            return beanClass.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private Object getSingleton(String beanName) {
        return singletonObjects.get(beanName);
    }

    private void addSingleton(String beanName, Object bean) {
        singletonObjects.put(beanName, bean);
    }

    private void autowire(Object bean, ScannedGenericBeanDefinition definition) {
        for (Field declaredField : definition.getBeanClass().getDeclaredFields()) {
            if (declaredField.isAnnotationPresent(Autowired.class)) {
                declaredField.setAccessible(true);
                try {
                    declaredField.set(bean, getBean(getBeanName(declaredField.getType().getName())));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

上面的代码实现了一个功能非常简单的BeanFactory,流程如下

  1. 构造方法先解析BeanDefinition
  2. getBean()方法获取对象
  3. 先从singletonObjects取对象,也就是我们说的一级缓存,有,直接返回,没有,实例化对象
  4. 实例完对象后存入singletonObjects
  5. 执行依赖注入,依赖注入再次调用getBean()方法获取对象
    我们把对象存入singletonObjects单例池的时机提前到了依赖注入之前

测试代码:

public static void main(String[] args) {
    MyBeanFactory beanFactory = new MyBeanFactory("com.dp.aop.cycle");
    A a = (A)beanFactory.getBean("a");
    B b = (B)beanFactory.getBean("b");
    // 打印b对象
    a.test();
    // 打印a对象
    b.test();
}

测试结果:

com.dp.aop.cycle.B@7e0ea639
com.dp.aop.cycle.A@3d24753a

可以看到结果符合预期,循环依赖的对象都有值,看似没有毛病,但这里还是存在一个问题

  1. 假设线程1调用getBean(“a”),对象创建完成后会存入singletonObjects 中
  2. 这时候如果另外一个线程2也来调用getBean(“a”),首先在singletonObjects 就找到了对象,直接返回,但是因为a对象实际还在创建中,这时它的成员变量b还是null,相当于线程2拿到了一个不完整的对象,成员变量都是null的,那肯定没法用

由于存在上述问题,所以Spring需要才有多级缓存来解决循环依赖问题

首先单例池singletonObjects 还是老老实实的在bean创建流程走完后再存入,这样,其他线程才能每次都拿到完整的对象,然后我们再加入一个二级缓存earlySingletonObjects,在实例化后,依赖注入前存入。

修改后代码如下

public class MyBeanFactory {

    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
    private final Map<String, ScannedGenericBeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>();

    // ......省略

    public Object getBean(String beanName) {
        // 从缓存中取对象
        Object bean = getSingleton(beanName);
        // 取到对象,直接返回
        if (bean != null) {
            return bean;
        }
        // 获取BeanDefinition
        ScannedGenericBeanDefinition definition = beanDefinitionMap.get(beanName);
        if (definition == null) {
            return null;
        }
        bean = this.createBean(beanName, definition);
        return bean;
    }

    private Object createBean(String beanName, ScannedGenericBeanDefinition definition) {
        Object bean;// 加锁,防止并发创建对象
        synchronized (this.singletonObjects) {
            bean = singletonObjects.get(beanName);
            if (bean == null) {
                // 实例化对象
                bean = this.instanceBean(definition);
                // 存入二级缓存
                earlySingletonObjects.put(beanName, bean);
                // 依赖注入
                this.autowire(bean, definition);
            }
            // 存入一级缓存
            this.addSingleton(beanName, bean);
        }
        return bean;
    }

    private Object getSingleton(String beanName) {
        Object bean = singletonObjects.get(beanName);
        if (bean == null) {
            // 加锁,防止并发时获取到earlySingletonObjects里不完整的对象
            synchronized (this.singletonObjects) {
                bean = this.singletonObjects.get(beanName);
                if (bean == null) {
                    bean = this.earlySingletonObjects.get(beanName);
                }
            }
        }
        return bean;
    }

    // ......

}

可以看到修改后的代码加入了二级缓存,创建bean和获取bean时都对singletonObjects加锁,解决了并发获取到不完整对象的问题

看似二级缓存就已经能很好的解决问题了,那为什么还需要三级缓存呢?
答案就是实例化后存入二级缓存的对象可能并不是我们想要的对象
考虑到AOP的情况,我们实际想要的其实是代理对象,而正常bean创建流程,会在依赖注入完成后,才会创建代理对象,所以还需要想办法让earlySingletonObjects保存我们需要的代理对象

Spring引入了三级缓存singletonFactories来解决这个问题

Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

具体在实例化后调用

// org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

这里存入的ObjectFactory是一个lamda表达式,在getBean方法进来时的getSingleton方法里调用

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		// Quick check for existing instance without full singleton lock
		Object singletonObject = this.singletonObjects.get(beanName);
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			singletonObject = this.earlySingletonObjects.get(beanName);
			if (singletonObject == null && allowEarlyReference) {
				synchronized (this.singletonObjects) {
					// Consistent creation of early reference within full singleton lock
					singletonObject = this.singletonObjects.get(beanName);
					if (singletonObject == null) {
						singletonObject = this.earlySingletonObjects.get(beanName);
						if (singletonObject == null) {
							// 从三级缓存拿ObjectFactory对象,就是() -> getEarlyBeanReference(beanName, mbd, bean)这个lamda表达式
							ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
							if (singletonFactory != null) {
								// 执行lamda表达式
								singletonObject = singletonFactory.getObject();
								// 把获取的对象存入二级缓存,方便下次获取
								this.earlySingletonObjects.put(beanName, singletonObject);
								this.singletonFactories.remove(beanName);
							}
						}
					}
				}
			}
		}
		return singletonObject;
	}

getEarlyBeanReference方法循环调用SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference

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

其中我们实现AOP功能的AbstractAutoProxyCreator类就实现了这个接口,循环依赖的时候就会调用它的getEarlyBeanReference方法提前创建出动态代理对象,这样就解决了获取不到代理对象的问题了

四.二级缓存真的解决不了循环依赖问题吗?

通过上面的流程我们其实可以发现,如果每次在实例化后,我们都去主动调用getEarlyBeanReference(beanName, mbd, bean)方法,并把得到的对象存入earlySingletonObjects中,这样感觉其实不用三级缓存也是可以的

但是经过仔细分析Spring源码后,我发现这个三级缓存在Spring里其实是设置的比较巧妙的,并不是可有可无

原因是:earlySingletonObjects不仅是作为二级缓存,同时还起到了标记的作用
这里的标记指的是标记是否存在循环依赖

我们在看看上面的getSingleton方法,它有一个allowEarlyReference参数,如果为false并且一二级缓存都查不到,并不会进入后面从三级缓存获取并存入二级缓存的逻辑

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		// Quick check for existing instance without full singleton lock
		Object singletonObject = this.singletonObjects.get(beanName);
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			singletonObject = this.earlySingletonObjects.get(beanName);
			if (singletonObject == null && allowEarlyReference) {
				synchronized (this.singletonObjects) {
					// ......
				}
			}
		}
		return singletonObject;
	}

同时,我发现,只有getBean开始那里才会传true去调用getSingleton方法
在这里插入图片描述
而从三级缓存获取对象的时候又是对singletonObjects加了锁的,同时对singletonObjects加锁的还有创建bean的那里,也就是说不同线程不可能同时进入到这两段代码中

那唯一会使earlySingletonObjects里有值的情况就是出现了循环依赖,同一个线程锁重入进入到了getSingleton的synchronized块里,调用三级缓存的getObject方法获取对象存入二级缓存

我们看看getSingleton(beanName, false)的调用
下面这段代码在bean对象依赖注入和初始化完成之后

// org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean

// ......
// exposedObject 为暴露对象
Object exposedObject = bean;
try {
	populateBean(beanName, mbd, instanceWrapper);
	// initializeBean后可能会改变exposedObject,比如变成代理对象
	exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
	if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
		throw (BeanCreationException) ex;
	}
	else {
		throw new BeanCreationException(
				mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
	}
}
if (earlySingletonExposure) {
	// 获取earlySingletonReference,存在循环依赖,才能在earlySingletonObjects中找到
	Object earlySingletonReference = getSingleton(beanName, false);
	if (earlySingletonReference != null) {
		
		if (exposedObject == bean) {
			exposedObject = earlySingletonReference;
		}
		// 如果exposedObject != bean会报错
		// allowRawInjectionDespiteWrapping默认false,hasDependentBean(beanName)判断是否被其他对象依赖
		else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
			String[] dependentBeans = getDependentBeans(beanName);
			Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
			for (String dependentBean : dependentBeans) {
				if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
					actualDependentBeans.add(dependentBean);
				}
			}
			if (!actualDependentBeans.isEmpty()) {
				throw new BeanCurrentlyInCreationException(beanName,
						"Bean with name '" + beanName + "' has been injected into other beans [" +
						StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
						"] in its raw version as part of a circular reference, but has eventually been " +
						"wrapped. This means that said other beans do not use the final version of the " +
						"bean. This is often the result of over-eager type matching - consider using " +
						"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
			}
		}
	}
}

首先earlySingletonReference == null就是不存在循环依赖的情况,这时没问题
exposedObject != bean什么时候会成立呢?主要取决于上面的initializeBean初始化方法

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
		if (System.getSecurityManager() != null) {
			AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
				invokeAwareMethods(beanName, bean);
				return null;
			}, getAccessControlContext());
		}
		else {
			invokeAwareMethods(beanName, bean);
		}

		Object wrappedBean = bean;
		if (mbd == null || !mbd.isSynthetic()) {
			wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
		}

		try {
			invokeInitMethods(beanName, wrappedBean, mbd);
		}
		catch (Throwable ex) {
			throw new BeanCreationException(
					(mbd != null ? mbd.getResourceDescription() : null),
					beanName, "Invocation of init method failed", ex);
		}
		if (mbd == null || !mbd.isSynthetic()) {
			wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
		}

		return wrappedBean;
	}

这里BeanPostProcessor的前后置方法可以改变exposedObject的值,AOP对这里做了兼容
如果存在循环依赖,不会再生成代理对象,并且返回原始对象,所以这里没问题
能够满足后面exposedObject == bean的判断

public Object getEarlyBeanReference(Object bean, String beanName) {
	Object cacheKey = getCacheKey(bean.getClass(), beanName);
	// 存入earlyProxyReferences原始对象
	this.earlyProxyReferences.put(cacheKey, bean);
	// 返回代理对象
	return wrapIfNecessary(bean, beanName, cacheKey);
}

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
	if (bean != null) {
		Object cacheKey = getCacheKey(bean.getClass(), beanName);
		// 如果从earlyProxyReferences拿到元素对象,不会再生成代理,直接返回原始对象
		if (this.earlyProxyReferences.remove(cacheKey) != bean) {
			return wrapIfNecessary(bean, beanName, cacheKey);
		}
	}
	return bean;
}

举一个会出问题的例子
修改一下代码,我们给test()方法加上了@Async注解,并开启异步调用@EnableAsync

@Component
public class A {
    @Autowired
    private B b;

    @Async
    public void test() {
        System.out.println(b);
    }
}

这时的测试结果

Exception in thread "main" org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Bean with name 'a' has been injected into other beans [b] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:649)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:953)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583)
	at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:93)
	at com.dp.Test.main(Test.java:20)

报错的位置就在前面那里
这里报错的原因是AsyncAnnotationBeanPostProcessor做后置处理的时候会生成一个新的代理对象并返回给exposedObject,导致exposedObject != bean

遇到这种情况可以给成员变量加@Lazy注解来解决

@Component
public class A {
    @Autowired
    @Lazy
    private B b;

    @Async
    public void test() {
        System.out.println(b);
    }
}

现在说回只有二级缓存的情况
如果我们在对象实例化后就调用getEarlyBeanReference(beanName, mbd, bean)方法,把代理对象存入earlySingletonObjects中
那初始化后的判断就会变成:

// ......
if (earlySingletonExposure) {
	// earlySingletonReference肯定不为空
	Object earlySingletonReference = getSingleton(beanName, false);
	if (earlySingletonReference != null) {
		if (exposedObject == bean) {
			exposedObject = earlySingletonReference;
		}
		// 即使没有循环依赖,也有可能进入这个判断里,并报错,比如加了@Async的情况
		else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
			String[] dependentBeans = getDependentBeans(beanName);
			Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
			for (String dependentBean : dependentBeans) {
				if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
					actualDependentBeans.add(dependentBean);
				}
			}
			if (!actualDependentBeans.isEmpty()) {
				throw new BeanCurrentlyInCreationException(beanName,
						"Bean with name '" + beanName + "' has been injected into other beans [" +
						StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
						"] in its raw version as part of a circular reference, but has eventually been " +
						"wrapped. This means that said other beans do not use the final version of the " +
						"bean. This is often the result of over-eager type matching - consider using " +
						"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
			}
		}
	}
}

因为没有了三级缓存,二级缓存里总是有值,Spring也不好判断到底存不存在循环依赖
当然也可以用其他方法来进行标记是否存在循环依赖,但都没有这么巧妙

另外用三级缓存还有一个原因:就是多数时候都不会有循环依赖,每次都提前把代理对象创建出来也不太合适,Spring还是选择正常情况下,代理对象在初始化完成后再生成

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值