Spring系列-4 循环依赖与三级缓存

背景

本文承接Spring系列-3 Bean实例化与依赖注入,继续介绍依赖注入相关内容,内容包括循环依赖和三级缓存。

注意:在SpringBoot 2.6.0之后Spring官方已经不建议循环依赖了,因此尽量在编码层面避免循环依赖

1.循环依赖问题

当两个或多个类之间存在相互依赖时,就可能会出现循环依赖问题,如下图所示:
在这里插入图片描述

两个类的循环依赖与多个类的循环依赖原理相同,以下以ComponentA和ComponentB为例进行介绍:

@Component
@Data
public class ComponentA {
    @Autowired
    private ComponentB componentB;
}

@Component
@Data
public class ComponentB {
    @Autowired
    private ComponentA componentA;
}

Spring容器启动过程中会依次构造Bean对象并注入到IOC容器中,而Bean对象的生命周期存在如下流程:准备阶段->实例化->属性设置->初始化->使用->销毁。

下图为ComponentA和ComponentB的构建流程:
在这里插入图片描述

如上图所示,在没有设置三级缓存情况下,Spring创建Bean对象时会陷入无限循环。

下面以ComponentA和ComponentB为例构造一个循环依赖场景:
[1] 添加配置类:

@Configuration
@ComponentScan(basePackages = "com.caltta.ldsdebug.lazy", lazyInit = true)
 public class LazyConfiguration {
}

通过lazyInit属性将ComponentA和ComponentB设置为懒加载,这样容器启动时就不会将ComponentA和ComponentB注入到IOC容器中。

[2] 引入测试用例:

public class LazyTest {
    @Test
    public void test() {
        System.out.println("test begin...");
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(LazyConfiguration.class);
        //通过IOC容器的setAllowCircularReferences方法关闭IOC容器的循环依赖能力,
        ((DefaultListableBeanFactory)applicationContext.getBeanFactory()).setAllowCircularReferences(false);
        ComponentA componentA = (ComponentA)applicationContext.getBean("componentA");
        System.out.println("test end!");
    }
}

通过applicationContext对象获取到IOC工厂,并设置禁止循环依赖。
运行得到如下结果:

org.springframework.beans.factory.UnsatisfiedDependencyException: 
Error creating bean with name 'componentA': Unsatisfied dependency expressed through field 'componentB'; 
    
nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException:Error creating bean with name 'componentB': Unsatisfied dependency expressed through field 'componentA'; 

nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException:Error creating bean with name 'componentA': Requested bean is currently in creation: Is there an unresolvable circular reference?

(ComponentA)applicationContext.getBean("componentA")执行后,ComponentA会进行完整的Bean生命周期:实例化、属性设置、初始化等流程;在ComponentA对象的属性设置阶段会因需构造ComponentB对象;在构造ComponentB对象过程的属性注入阶段需要构造ComponentA对象(关闭循环依赖能力时-不会存储半成本的A对象),因此陷入循环。

2.Spring的三级缓存

2.1 三级缓存介绍

DefaultListableBeanFactory的父类DefaultSingletonBeanRegistry中定义了三个属性,作为Spring的三级缓存:

/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);


singletonObjects作为一级缓存, 也是IOC容器的单例池,存放创建好的成品对象(完成了实例化、属性设置和初始化等流程,可直接对外使用);earlySingletonObjects作为二级缓存,存放的是半成品的Bean对象, 用于提前暴露对象(该对象来源于三级缓存);singletonFactories作为三级缓存: 存放用于构造Bean对象的lambda表达式,本质是Bean对象创建工厂。

当从IOC容器查找Bean对象时,查找顺序为:一级->二级->三级, 代码如下:

public Object getSingleton(String beanName) {
    return getSingleton(beanName, true);
}

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 1.从一级缓存中根据beanName获取Bean对象
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            // 2.从二级缓存中根据beanName获取Bean对象
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                // 3.先从三级缓存中根据beanName获取ObjectFactory并调用getObject获取Bean对象
                // 对应一个lambda表达式
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

以下给出一个Spring可以正常处理的循环依赖场景:

public class LazyTest {
    @Test
    public void testNormal() {
        System.out.println("test begin...");
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(LazyConfiguration.class);
        ComponentA componentA = (ComponentA)applicationContext.getBean("componentA");
        System.out.println("test end!");
    }
}

运行发现本测试用例可以正常执行🥷不会抛出循环依赖异常。因为Spring通过引入三级缓存解决了该类循环依赖问题。案例中Bean对象的实例化和属性设置流程可以表示为:
![在这里插入图片

Bean生命周期相关的逻辑在本文中不再赘述,请参考 Spring系列-2 Bean的生命周期

如上图所示:
(1) 当从IOC容器获取ComponentA对象时,进入对象ComponentA的Bean生命周期;首先实例化ComponentA对象,此时ComponentA对象未经过属性注入,是一个半成品;然后将包裹ComponentA对象的lambda表达式存入三级缓存,相关源码如下:

// 省略无关逻辑
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
    // ...
    // ⚠️:1.实例化对象
    // Instantiate the bean.
    BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
    Object bean = instanceWrapper.getWrappedInstance();


    // Eagerly cache singletons to be able to resolve circular references
    // even when triggered by lifecycle interfaces like BeanFactoryAware.
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
            isSingletonCurrentlyInCreation(beanName));
    // ⚠️:2.将半成品对象加入三级缓存
    if (earlySingletonExposure) {
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }

    // ⚠️:3.依赖注入
    populateBean(beanName, mbd, instanceWrapper);

    // ⚠️:4.返回对象
    return exposedObject;
}

重点部分的代码是将半成品的ComponentA对象存入包装后存入三级缓存:

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized(this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

ObjectFactory是一个函数式接口(仅有一个接口),传入的lambda表达式() -> getEarlyBeanReference(beanName, mbd, bean)会以匿名类的形式传参;因此从singletonFactories三级缓存中获取到ObjectFactory对象并调用其getObject()方法时,会执行该lambda表达式以获取Bean对象(如需代理,为代理后的对象)。

(2) 进入ComponentA对象的属性注入阶段,对b属性的注入, 堆栈的调用链进入从容器中获取ComponentB对象;
属性注入部分内容可以参考:Spring系列-3 Bean实例化与依赖注入

(3-4) 获取ComponentB对象时进入ComponentB的Bean生命周期;完成ComponentB对象的实例化并存入三级缓存,过程同步骤(1)。
(5) 进入ComponentB对象的属性注入阶段,进行a属性的注入,尝试从IOC容器获取ComponentA对象;
(6) 从容器中获取ComponentA对象: 调用三级缓存中的lambda表达式,构建出ComponentA对象(可能是代理后的对象),然后将该Bean对象存入二级缓存,并从三级缓存中删除。

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 1.从一级和二级缓存中根据beanName获取不到Bean对象...
    Object singletonObject;

    // 2.先从三级缓存中根据beanName获取ObjectFactory并调用getObject获取Bean对象
    // 对应一个lambda表达式
    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
    
    // 3.调用getObject()时,执行lambda表达式,获取Bean对象
    singletonObject = singletonFactory.getObject();

	// 4.将得到的Bean对象存入二级缓存,并删除三级缓存的记录
    this.earlySingletonObjects.put(beanName, singletonObject);
    this.singletonFactories.remove(beanName);
    return singletonObject;
}

此时,Spring三级缓存中存在beanName为componentA对应的记录。去除该记录(lambda表达式)执行后,得到一个半成品对象,如果该Bean对象需要被代理,则返回的是代理后的Bean对象。并将该记录放入到二级缓存中,然后删除三级缓存中的记录。

(7) 当对象B完成依赖注入及后续的初始化操作后,将B对象放入到一级缓存中。

//在doGetBean方法通过creatBean创建完Bean对象后,会调用addSingleton方法
protected void addSingleton(String beanName, Object singletonObject) {
	synchronized (this.singletonObjects) {
		// 放入一级缓存
		this.singletonObjects.put(beanName, singletonObject);
		// 删除二级和三级缓存
		this.singletonFactories.remove(beanName);
		this.earlySingletonObjects.remove(beanName);
		
		this.registeredSingletons.add(beanName);
	}
}

(8) 调用链返回至对象A的属性注入阶段,将从单例池中获取的ComponentB对象注入到b属性中;A完成属性注入和后续的初始化等流程后,将A对象存入一级缓存,并删除二级缓存中的A对象;过程同(7).

2.2 三级缓存的原因:

spring解决循环依赖的核心思想在于提前暴露引用,因此需要引入缓存;由于一级缓存是作为单例池存在,因此提前暴露的对象不能放在一级缓存而需要另外开辟缓存。如2.1中例子,本质上如果没有代理情况发生,只需要两级缓存就能解决循环依赖问题。

2.2.1 第三级缓存的作用:

当A对象需要被代理时,bean的生命周期如下所示:
在这里插入图片描述

按照Bean的生命周期,代理操作在初始化阶段的BPP(BeanPostProcessor)执行;因此,上述ComponentB在属性注入阶段得到的ComponentA对象应是原始Bean对象(ComponentA对象的生命周期暂停在了属性设置阶段—未进入初始化阶段)。
为了解决上述问题,Spring引入了第三级缓存,该缓存中存放lambda表达式;当执行该表达式时,如果ComponentA需要代理,则可以将ComponentA对象在初始化阶段的AOP操作提前至ComponentB属性设置阶段;因此ComponentB的a属性注入的对象是代理后的对象。

既然将AOP操作提前了,因此需要引入一个标志(this.earlyProxyReferences)使得ComponentA的Bean对象在初始化阶段的AOP过程可以得到已被代理的信息:

public Object getEarlyBeanReference(Object bean, String beanName) {
    Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
    this.earlyProxyReferences.put(cacheKey, bean);
    // 如果需要代理,执行代理操作
    return this.wrapIfNecessary(bean, beanName, cacheKey);
}


public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
        Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
        if (this.earlyProxyReferences.remove(cacheKey) != bean) {
            return this.wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}

2.2.2 第二级缓存的作用:

2.2.1 第三级缓存的作用章节可知,只需要第一级缓存和第三级缓存就可以解决ComponentA和ComponentB的循环依赖,即使ComponentA或ComponentB需要被代理。
在这里插入图片描述
如上所示:ComponentA对象同时与ComponentB和ComponentC对象存在循环依赖问题;ComponentB和ComponentC对象在属性设置阶段获取ComponentA对象时,两次都会从第三级缓存中得到相同的lambda表达式并执行获取代理对象;但两次获取到的代理对象不同,违背了ComponentA单例的原则。
因此,Spring引入第二级缓存并与第三级缓存配合,共同解决上述问题:第一次通过lambda表达式得到代理对象,然后将代理对象存入二级缓存并删除三级缓存;第二次直接从二级缓存中查询,而不需要再次执行lambda表达式。

3.遗留问题

本章节介绍Spring系列-3 Bean实例化与依赖注入一文中遗留的两个方法。

跟进element.inject(target, beanName, pvs)方法(省略try-catch异常)protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
	Field field = (Field) this.member;
	Object value;
	if (this.cached) {
		value = resolvedCachedArgument(beanName, this.cachedFieldValue);
	} else {
		value = resolveFieldValue(field, bean, beanName);
	}
	
	if (value != null) {
		ReflectionUtils.makeAccessible(field);
		field.set(bean, value);
	}
}

inject方法的主体逻辑可以分为两步:(1) 根据beanName等信息获取待注入的值; (2)通过反射完成属性注入。
第一步的主体逻辑可以简化为:

if (this.cached) {
	value = resolvedCachedArgument(beanName, this.cachedFieldValue);
} else {
	value = resolveFieldValue(field, bean, beanName);
	//...
	cached = true;
}

刨去cached的优化,主体逻辑应为value = resolveFieldValue(field, bean, beanName).
根进resolveFieldValue(field, bean, beanName)

private Object resolveFieldValue(Field field, Object bean, @Nullable String beanName) {
	// ⚠️1:根据字段Field、是否必须required、bean的class对象构造DependencyDescriptor对象
    DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
    desc.setContainingClass(bean.getClass());

    Object value;
    try {
        // ⚠️2:调用beanFactory.resolveDependency方法获取注入的值value【 ⚠️主线】
        value = beanFactory.resolveDependency(desc, beanName, new LinkedHashSet<>(1), beanFactory.getTypeConverter());
    }
    catch (BeansException ex) {
        throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
    }
    // ⚠️3:返回value
    return value;
}

跟进beanFactory.resolveDependency方法:

public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
		@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
	// ...
	// 主体逻辑如下:
	//【1】如果待注入字段被@Lazy注解,则生成一个代理对象返回, 否则返回null;
	// @Lazy也是一个解决循环依赖的方案
	Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
			descriptor, requestingBeanName);
	if (result == null) {
		// ⚠️ 主线逻辑
		result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
	}
	return result;
}

跟进doResolveDependency方法:

public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
        @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
    //...
    // 主线逻辑如下:
    // ⚠️1: 按类型以及注解信息(@Qualifier等)获取Bean集合
    Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
    
     // ⚠️2: matchingBeans为空场景
    if (matchingBeans.isEmpty()) {
        if (isRequired(descriptor)) {
            raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
        }
        return null;
    }
    
	// ⚠️3:如果matchingBeans数量大于1个场景
    if (matchingBeans.size() > 1) {
        autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
        if (autowiredBeanName == null) {
            if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {
                return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);
            }
            else {
                return null;
            }
        }
        instanceCandidate = matchingBeans.get(autowiredBeanName);
    } else {
    	// ⚠️4:如果matchingBeans数量等于1场景
        Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
        autowiredBeanName = entry.getKey();
        instanceCandidate = entry.getValue();
    }
}

doResolveDependency方法首先调用findAutowireCandidates(beanName, type, descriptor)方法获取条件匹配的bean对象,形成集合matchingBeans:通过@Qualifier注解指定了name时,需要根据该name进行过滤;否则返回根据类型匹配的所有的Bean对象。

然后对得到的matchingBeans分场景进行处理:
场景1:matchingBeans为空
表示没有找到匹配的Bean对象,如果required(@Autowired注解时指定的属性)为false, 则返回空;否则抛出NoSuchBeanDefinitionException异常。

场景2: matchingBeans只有一个元素
此时,直接使用该元素进行依赖注入。

场景3: matchingBeans多于一个元素
按照@Primary注解和@Priority注解根据优先级进行筛选:

protected String determineAutowireCandidate(Map<String, Object> candidates, DependencyDescriptor descriptor) {
	Class<?> requiredType = descriptor.getDependencyType();
	// 如果有@Primary注解,则使用该Bean对象
	String primaryCandidate = determinePrimaryCandidate(candidates, requiredType);
	if (primaryCandidate != null) {
		return primaryCandidate;
	}
	// 得到@Priority优先级最高的Bean对象(value值越小优先级越高)
	String priorityCandidate = determineHighestPriorityCandidate(candidates, requiredType);
	if (priorityCandidate != null) {
		return priorityCandidate;
	}
	//...
	//抛出NoUniqueBeanDefinitionException异常
}

首先根据@Primary进行过滤,标注了@Primary注解的Bean对象作为优先级最高的对象被返回;否则,找出候选Bean集合的标注了@Priority注解且属性值最小的Bean对象返回;否则,抛出NoUniqueBeanDefinitionException异常,表示容器无法确定使用哪个候选的Bean对象进行依赖注入。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值