Spring Bean循环依赖问题是如何解决的?

什么是循环依赖?

循环依赖就是循环引用的意思,也就是两个或两个以上的 bean 互相持有对方,最终形成闭环。比如 A 依赖于 B ,B 又依赖于 A 。如下:
在这里插入图片描述
Spring 注入bean 的方式有 构造器注入、field 属性注入等。

Spring 通过特殊的 bean 生成机制解决了 field 属性注入方式产生的循环依赖问题,使得循环链的所有 bean 对象都能正确创建。

而构造器注入方式阐释的循环依赖则会抛出异常,如下代码所示:

@Service
public class A {

    private B b;

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

@Service
public class B {

    private A a;

    @Autowired
    public B(A a) {
        this.a = a;
    }

}

上边这段代码,在程序启动时,会抛出如下异常:

org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ‘a’: Requested bean is currently in creation: Is there an unresolvable circular reference?

知道循环依赖问题的现象了,那接下来我们就看看 Spring 是如何来解决这些循环依赖问题的。

Spring 是如何解决 field 属性注入引发的循环依赖问题的?

我们先来看个 filed 属性注入的循环依赖代码:

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

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

上边这段代码,在程序启动时并不会报错,这说明 Bean A 和 Bean B 都被正确注入了,Spring 默认帮我们解决了循环依赖的问题,那 Spring 是通过什么方式解决的呢?

Spring 解决这个问题是通过 Java 的引用传递,以及提前暴露对象到三级缓存中和延后设置 field 属性来实现的。

如果把 bean 的创建过程简述为 2 个步骤的话:第一步是创建一个对象(通过反射);第二步是给对象填充属性。

Spring 在创建 bean 的时候并不是等它完全完成,而是将创建中的 bean 提前曝光(即加入到 singletonFactories 三级缓存中),当下一个 bean 创建的时候需要依赖此 bean ,则从三级缓存中获取。

我们来描述一下 Spring 解决循环依赖的过程:

  1. 先创建 A 对象,并将创建出来的 A 对象放到 Spring 的三级缓存中;
  2. 此时 A 对象的 b 属性为空,需要填充 b 属性,到缓存中查询 B 对象,没有查到,触发 B 对象的创建流程;
  3. 创建 B 对象,并将创建出来的 B 对象放到 Spring 的三级缓存中;
  4. B 对象的 a 属性为空,需要填充 a 属性,随后在缓存中找到 A 对象,完成填充注入;
  5. 最后再对 A 对象的 b 属性进行填充,从缓存中顺利拿到 B 对象,完成属性注入,循环依赖到此解决。

上边提到了三级缓存,那 Spring 的 一、二、三级缓存的作用是什么呢?

  • singletonObjects:一级缓存,用于存放完整的 bean,从该缓存中取出的 bean 可以直接使用;
  • earlySingletonObjects:二级缓存,存放提前暴露的 bean,bean 是不完整的,未完成属性注入和执行 init 方法,用于解决循环依赖;
  • singletonFactories:三级缓存,对初始化后的 bean 完成 AOP 代理操作,bean 初始化完成之后才生成代理,而不是实例化之后就生成代理,保证了bean的生命周期。

一级缓存和二级缓存比较容易理解,因为只有一级缓存的话,里边既有完整的 bean,也会有尚未初始化的 bean,这样在获取到尚未初始化的 bean 进行调用的时候,就会出现问题。

如果注入的都是普通对象的话,一级缓存和二级缓存已经能解决循环依赖问题。

但是如果是代理对象的话,如果只有一级缓存和二级缓存,那在对象实例化之后就必须要马上创建一个代理对象,这样就违背了 Spring 结合 AOP 跟 Bean 的生命周期的设计,Spring 结合 AOP 跟 Bean 的生命周期本身就是通过 AnnotationAwareAspectJAutoProxyCreator 这个后置处理器来完成的,所以 Spring 才会通过三级缓存来保证创建普通对象和创建代理对象时 bean 的生命周期流程统一。

怎么解决构造器注入引发的循环依赖问题?

我们可以通过两种方式来解决构造器注入引发的循环依赖问题:第一种是通过 @Scope 的 proxyMode 属性来设置类的代理模式;第二种是使用 @Lazy 注解。

通过 @Scope 的 proxyMode 属性来设置类的代理模式代码示例:

@Service
public class A {

    private B b;

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

}

@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS) // 使用 CGLIB 动态代理
@Service
public class B {

    private A a;

    @Autowired
    public B(A a) {
        this.a = a;
    }

}

@Scope 注解是 Spring IOC 容器中的一个作用域,默认为 singleton(单例)。通过 Scope 中的 proxyMode 属性可以设置类的代理模式,

  • DEFAULT 不使用代理
  • NO 不使用代理
  • INTERFACES 使用 JDK 动态代理
  • TARGET_CLASS 使用 CGLIB 动态代理

这里我们不对 @Scope 做过多介绍,上边代码中因为 B 类没有实现接口,不能使用 JDK 动态代理,所以这里使用的是 CGLIB 动态代理。

使用 @Lazy 注解的代码示例:

@Service
public class A {

    private B b;

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

}

@Service
public class B {

    private A a;

    @Autowired
    public B(A a) {
        this.a = a;
    }

}

@Lazy 注解用于标识 bean 是否需要延迟加载。

当我们通过上述两种方式进行代码改动后,再次启动程序就不会抛出异常了。

那,这两种方式又是如何解决循环依赖问题的呢?

其实它们 都是通过动态代理来避免了循环依赖

我们再来描述一下 解决循环依赖的过程:

  1. 创建 A 时需要 B ,发现 B 是可以延迟加载或者是指定了代理模式的;
  2. 创建一个 B 类的代理类 Bproxy ;
  3. 通过 Bproxy 创建 A(此时 A 的依赖关系已经变成了 A 依赖 Bproxy);
  4. 再创建 B 时,A 已经存在,所以 B 也成功创建。

这样一来,A 跟 B 就不是相互依赖了,变成了 A 依赖代理类 Bproxy,B 依赖 A :
在这里插入图片描述
原理我们已经明白了,那我们怎么才能去验证一下呢?

我们拿通过 @Lazy 注解的方式来解决循环依赖为例(注:这里虽然两种方式都是通过代理模式来解决的循环依赖,但是过程还是有差别,不过原理是一样的

我们先看下如下代码:

DefaultListableBeanFactory#resolveDependency

@Override
public Object resolveDependency(DependencyDescriptor descriptor, String requestingBeanName,
		Set<String> autowiredBeanNames, TypeConverter typeConverter) throws BeansException {

	// ...  省略一些内容
	else {
		// 尝试获取一个懒加载代理
		Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
				descriptor, requestingBeanName);
		if (result == null) {
			// 如果没获取到懒加载代理,就直接去获取bean实例了,这里最终会调用getBean
			result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
		}
		return result;
	}
}

而 getLazyResolutionProxyIfNecessary 方法内部又调用了 isLazy 和 buildLazyResolutionProxy 方法:

@Override
public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, String beanName) {
	// 判断如果 isLazy 方法返回 true,则继续调用 buildLazyResolutionProxy 方法
	return (isLazy(descriptor) ? buildLazyResolutionProxy(descriptor, beanName) : null);
}

protected boolean isLazy(DependencyDescriptor descriptor) {
	for (Annotation ann : descriptor.getAnnotations()) {
		Lazy lazy = AnnotationUtils.getAnnotation(ann, Lazy.class);
		if (lazy != null && lazy.value()) {
			// 判断如果有 @Lazy 注解,则返回true
			return true;
		}
	}
	// ... 省略一些代码
	return false;
}

protected Object buildLazyResolutionProxy(final DependencyDescriptor descriptor, final String beanName) {
	
	final DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) getBeanFactory();
	
	TargetSource ts = new TargetSource() {
		@Override
		public Class<?> getTargetClass() {
			return descriptor.getDependencyType();
		}
		@Override
		public boolean isStatic() {
			return false;
		}
		@Override
		public Object getTarget() {
			
			// 在这里才会去正真加载依赖,进而调用getBean方法 获得原始 bean 对象
			Object target = beanFactory.doResolveDependency(descriptor, beanName, null, null);
			if (target == null) {
				Class<?> type = getTargetClass();
				if (Map.class == type) {
					return Collections.EMPTY_MAP;
				}
				else if (List.class == type) {
					return Collections.EMPTY_LIST;
				}
				else if (Set.class == type || Collection.class == type) {
					return Collections.EMPTY_SET;
				}
				throw new NoSuchBeanDefinitionException(descriptor.getResolvableType(),
						"Optional dependency not present for lazy injection point");
			}
			return target;
		}
		@Override
		public void releaseTarget(Object target) {
		}
	};
	// 创建代理工厂ProxyFactory
	ProxyFactory pf = new ProxyFactory();
	pf.setTargetSource(ts);
	Class<?> dependencyType = descriptor.getDependencyType();
	if (dependencyType.isInterface()) {
		pf.addInterface(dependencyType);
	}
	// 返回代理类
	return pf.getProxy(beanFactory.getBeanClassLoader());
}

上边代码表示了,如果属性前边加上了 @Lazy 注解的话,会创建一个代理类的实例对象。

当 Spring 容器启动完成后,我们在 Spring 容器的一级缓存里,可以看到生成的 a 对象和 b 对象如下:
在这里插入图片描述
通过上图我们就能清晰的看到,循环依赖已经被代理对象打断了,这样就解决了循环依赖问题!

  • 18
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晓呆同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值