Spring依赖注入之循环依赖问题详解

目录

什么是循环依赖

Java原生创建对象循环依赖不会有问题

为什么Spring循环依赖会有问题?

Spring解决循环依赖问题的思路

设置二级缓存对象池

方案一直接将实例化对象放入早期对象池

方案一缺点

方案二-将实例化对象处理AOP后放入早期对象池

方案二-缺点

设置三级缓存对象池

Spring是否哪些场景的循环依赖都能解决呢

原型bean和原型bean的循环依赖

属性作为构造函数的参数

其他后置处理器初始化后生成了新的对象

Spring解决循环依赖源码解析


什么是循环依赖

对象A依赖对象B,对象B依赖对象A,这是一种常见的依赖关系。

Java原生创建对象循环依赖不会有问题

通过Java直接创建对象,并不会产生问题;因为Java创建的只需要实例化后就是一个完整的对象,循环依赖时,只需将所有的对象都先实例化,再处理依赖关系。

publc class A{
    public B b;
}
publc class B{
    public A a;
}
public class Test{
    public static void main(String[] args){
          A a = new A();
          B b = new B();
          a.b = b;
          b.a = a;
    }
}

为什么Spring循环依赖会有问题?

spring创建的bean对象,不只是简单的实例化,还要经历属性注入,初始化前,初始化,初始化后等过程,只有经历完这个过程,才是一个完整的bean。要了解spring生命周期,可以参考这篇博文:

一句话解释:处理依赖关系时,由于bean没有经历完完整的生命周期,还不是一个完整的bean,导致有循环依赖关系的bean都陷入等待对方完成状态,而无法继续向后执行。请参考下面的流程图感受下:

ABean创建-->依赖了B属性-->触发BBean创建--->B依赖了A属性--->需要ABean(但ABean还在创建过程中)

Spring解决循环依赖问题的思路

设置二级缓存对象池

方案一直接将实例化对象放入早期对象池

最简单的思路就是在类似于普通对象创建的方式,在实例化后有一个原始的对象池,在判断单例池中是否有bean对象时,若无则从原始对象池中获取,可以解决上诉问题


方案一缺点

如果A的原始对象注入给B的属性之后,在初始化后阶段A的原始对象进行了AOP产生了一个代理对象,对于A而言,它的Bean对象其实应该是AOP之后的代理对象,而B的a属性对应的并不是AOP之后的代理对象,这就产生了冲突

方案二-将实例化对象处理AOP后放入早期对象池

放入早期对象池前,对实例化后对象A处理,若有AOP则放入代理对象A,若无AOP,则放入原始对象,如下图所示

方案二-缺点

但是在实例化后,直接处理对象A将代理对象或原始对象放入对象原始池这种方式,对所有对象的创建过程都起作用,而没有循环依赖的对象创建过程,无需将AOP过程前置,所以这个时机不合适

设置三级缓存对象池

Spring为了解决非循环依赖的对象不进行AOP前置,在实例化后,先缓存一个对象工厂函数,此时不执行内部逻辑,待去获取bean找不对bean时,才真正获取到对象工厂去调用函数获取早期对象,存放到二级缓存对象池中即早期对象池

三级缓存对象池的作用

1.单例对象池(singletonObjects中):缓存的是经历过完整生命周期后对象

2.早期对象池(earlySingletonObjects):缓存的是没有经历过完整生命周期的对象,实例化后原始对象或者实例化后的代理对象

3.对象工厂(singletonFactories):缓存的是对象工厂,对象工厂中定义了创建早期对象的函数

三者的创建时机,获取时机如下流程图:

Spring是否哪些场景的循环依赖都能解决呢

答案肯定是不能,spring无法解决下面三种情况,需要程序员自己在开发过程中注意!

原型bean和原型bean的循环依赖

上面spring循环依赖解决思路主要针对bean类型是单例bean。若是原型bean类型,因为无法缓存,每次产生的对象都不一样,对象A和对象B会陷入等待对方创建完成的过程

@Component
@Scope("prototype")
public class A {
	@Autowired
	private B b;
}

@Component
@Scope("prototype")
public class B {
	@Autowired
	private A a;
}

public class Test {
    public static void main(String[] args) {
		// 创建一个Spring容器
		AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
		A A = (A) applicationContext.getBean("a");
		B B = (B) applicationContext.getBean("b");
	}
}

而spring会抛出相关异常

Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'a': Unsatisfied dependency expressed through field 'b'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'b': Unsatisfied dependency expressed through field 'a'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

属性作为构造函数的参数

下面这种情况无法解决,对象A在实例化时就需要对象B,而此时对象A由于还没有实例化完,没有早期对象,所以在创建B时,对象B属性又需要注入A,也没有单例对象,也没有早期对象,就又会重新走到创建A的过程,从而导致死循环。

@Component
public class A {
	private B b;

	public A(B b) {
		this.b = b;
	}
}

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

而spring统一对这种循环依赖,抛出异常

Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

其他后置处理器初始化后生成了新的对象

主要原因还是因为属性注入后B注入的属性a对象和最终A初始化完后生成生成的对象不一样,有依赖注入属性时,要注意初始化后

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

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

@Component
public class CylBeanPostprocessor implements BeanPostProcessor {
	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		if (beanName.equals("a")){
			return new A();
		}
		return bean;
	}
}

spring识别到这种异常,抛出相关异常

警告: Exception encountered during context initialization - cancelling refresh attempt: 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.

Spring解决循环依赖源码解析

源码中getSingleton是解决循环依赖的核心代码:获取对象时先从单例池中获取对象,获取不到从早期对象池中获取对象,若仍获取不到则从对象工厂中获取对象bean的对象工厂,创建早期对象,存入早期对象池中,并且该对象工厂从对象工厂池中删除。

@Nullable
	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<?> singletonFactory = this.singletonFactories.get(beanName);
							if (singletonFactory != null) {
								singletonObject = singletonFactory.getObject();
								this.earlySingletonObjects.put(beanName, singletonObject);
								this.singletonFactories.remove(beanName);
							}
						}
					}
				}
			}
		}
		return singletonObject;
	}

对象工厂调用getObject()时,实际调用的就是

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#getEarlyBeanReference

会遍历所有实现了SmartInstantiationAwareBeanPostProcessor的类,调用getEarlyBeanReference获取bean

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;
	}

SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference会直接获取到原始bean

public interface SmartInstantiationAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessor {

	/**
	 * Obtain a reference for early access to the specified bean,
	 * typically for the purpose of resolving a circular reference.
	 * <p>This callback gives post-processors a chance to expose a wrapper
	 * early - that is, before the target bean instance is fully initialized.
	 * The exposed object should be equivalent to the what
	 * {@link #postProcessBeforeInitialization} / {@link #postProcessAfterInitialization}
	 * would expose otherwise. Note that the object returned by this method will
	 * be used as bean reference unless the post-processor returns a different
	 * wrapper from said post-process callbacks. In other words: Those post-process
	 * callbacks may either eventually expose the same reference or alternatively
	 * return the raw bean instance from those subsequent callbacks (if the wrapper
	 * for the affected bean has been built for a call to this method already,
	 * it will be exposes as final bean reference by default).
	 * <p>The default implementation returns the given {@code bean} as-is.
	 * @param bean the raw bean instance
	 * @param beanName the name of the bean
	 * @return the object to expose as bean reference
	 * (typically with the passed-in bean instance as default)
	 * @throws org.springframework.beans.BeansException in case of errors
	 */
	default Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
		return bean;
	}

}

但是spring提供了AbstractAutoProxyCreator实现了SmartInstantiationAwareBeanPostProcessor,重写了getEarlyBeanReference方法,若开启了代理,那会直接执行AbstractAutoProxyCreator的getEarlyBeanReference获取代理后的对象

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
		implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware{
// 提前进行AOP
	@Override
	public Object getEarlyBeanReference(Object bean, String beanName) {
		Object cacheKey = getCacheKey(bean.getClass(), beanName);
		this.earlyProxyReferences.put(cacheKey, bean);
		return wrapIfNecessary(bean, beanName, cacheKey);
    
	}
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值