这篇文章探讨一下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之所以要解决循环依赖问题,是因为其特殊的依赖注入流程
创建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)的流程大致如下
到这里就可以看出问题了
因为只有在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,流程如下
测试代码:
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
可以看到结果符合预期,循环依赖的对象都有值,看似没有毛病,但这里还是存在一个问题
由于存在上述问题,所以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还是选择正常情况下,代理对象在初始化完成后再生成