循环依赖就是循环引用的意思,也就是两个或两个以上的 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 是如何来解决这些循环依赖问题的。
我们先来看个 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 解决循环依赖的过程:
上边提到了三级缓存,那 Spring 的 一、二、三级缓存的作用是什么呢?
一级缓存和二级缓存比较容易理解,因为只有一级缓存的话,里边既有完整的 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 是否需要延迟加载。
当我们通过上述两种方式进行代码改动后,再次启动程序就不会抛出异常了。
那,这两种方式又是如何解决循环依赖问题的呢?
其实它们 都是通过动态代理来避免了循环依赖。
我们再来描述一下 解决循环依赖的过程:
这样一来,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 对象如下:
通过上图我们就能清晰的看到,循环依赖已经被代理对象打断了,这样就解决了循环依赖问题!