首页 > 其他分享 >Spring循环依赖

Spring循环依赖

时间:2024-12-06 14:44:05浏览次数:6  
标签:依赖 name Spring beanName bean 循环 AOP

Spring中循环依赖

1. 什么是循环依赖

两个类之间相互引用,或者多个类之间依次引用对方,最终构成一个环,例如下图所示,ServiceA类中定义了一个ServiceB类型的属性,ServiceB类中定义了一个ServiceC类型的属性,ServiceC类中定义了一个ServiceA类型的属性,三个类型之间引用构成了一个环。

2. 循环依赖如何解决

2.1 spring容器中bean的创建流程

在描述循环依赖如何解决之前,下面用一张图来回忆下spring容器中bean的创建流程。spring中BeanFacgtory根据getBean方法获取bean,下图描述了bean创建的几个重要步骤,其中和循环依赖相关的部分用绿色标记。

public Object getBean(String name) throws BeansException {
	return doGetBean(name, null, null, false);
}

2.2 处理循环依赖

了解了上面bean创建的流程之后,再去理解循环依赖的处理逻辑会更容易些。Spring在singleton scope下解决了setter注入方式的循环依赖。具体流程可以见下图所示:

这其中主要用到三个Map,描述如图

/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
	
/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

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

下面描述下上图中比较重要的几个地方。

2.2.1 提前暴露

在bean完成实例化步骤之后,spring容器判断是否需要提前暴露刚完成实例化的bean,如果需要则创建一个ObjectFactory放到singletonFactories中。

  • (1) 创建bean中关于是否需要提前暴露的判断逻辑
// 判断是否需要提前暴露
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
			isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
	if (logger.isDebugEnabled()) {
		logger.debug("Eagerly caching bean '" + beanName +
				"' to allow for resolving potential circular references");
	}
	addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

  • (2) addSingletonFactory 方法

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
	Assert.notNull(singletonFactory, "Singleton factory must not be null");
	synchronized (this.singletonObjects) {
		// 判断该bean还没被创建好
		if (!this.singletonObjects.containsKey(beanName)) {
			// 添加到singletonFactories中
			this.singletonFactories.put(beanName, singletonFactory);
			// earlySingletonObjects移除
			this.earlySingletonObjects.remove(beanName);
			this.registeredSingletons.add(beanName);
		}
	}
}

2.2.2 从缓存中取

后续循环引用的时候从缓存中获取提前暴露的bean

2.2.3 循环依赖校验

(1) 在bean创建完并初始化之后,会校验提前暴露出去的bean对象和最终生成的bean对象是否相同,如果不同,则会继续查看是否有其他已经被创建过至少一次的bean依赖了该bean,如果有则会抛出异常,认为被依赖的bean对象注入的该bean对象不是最终版本的对象。

(2) 该校验主要是用在bean被AOP增强的场景中。我们知道AOP增强是在初始化流程中进行的,被AOP增强过的bean和初始阶段被实例化的bean已经不是同一个对象了。如果被依赖的bean注入了初始阶段实例化的bean就会出现问题。

(3) 校验具体实现:

  • 前置依赖,主要使用到一个map - dependentBeanMap和一个set - alreadyCreated
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry

/** Map between dependent bean names: bean name --> Set of dependent bean names */
private final Map<String /*依赖bean name*/, Set<String> /*被依赖bean集合(依赖了name为该key的bean集合)*/> dependentBeanMap = new ConcurrentHashMap(64);

org.springframework.beans.factory.support.AbstractBeanFactory

/** Names of beans that have already been created at least once */
private final Set<String> alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256));
  • 添加阶段

    • dependentBeanMap - 在populateBean方法中注入依赖属性之后,会调用org.springframework.beans.factory.config.ConfigurableBeanFactory#registerDependentBean(String beanName, String dependentBeanName)方法将两个bean之间的依赖关系添加到dependentBeanMap中。

        1. 如果是通过@Resource注解修饰的属性,调用逻辑在类org.springframework.context.annotation.CommonAnnotationBeanPostProcessor里面
        1. 如果是通过@Autowired注解修饰的属性,调用逻辑在类org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor里面
    • alreadyCreated - 在doGetBean方法中尝试从缓存中获取之后实例化之前会将要创建的bean name添加到alreadyCreated

protected void markBeanAsCreated(String beanName) {
	if (!this.alreadyCreated.contains(beanName)) {
		synchronized (this.mergedBeanDefinitions) {
			if (!this.alreadyCreated.contains(beanName)) {
				// Let the bean definition get re-merged now that we're actually creating
				// the bean... just in case some of its metadata changed in the meantime.
				clearMergedBeanDefinition(beanName);
				this.alreadyCreated.add(beanName);
			}
		}
	}
}
  • 具体实现逻辑
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory
------------------------------------------------------------------------------

if (earlySingletonExposure) {
	Object earlySingletonReference = getSingleton(beanName, false);
	// 只有被循环依赖过该返回才不为null
	if (earlySingletonReference != null) {
		// 没有被AOP增强或者被AOP增强的逻辑也在实现了接口org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法中被实现过
		
		if (exposedObject == bean) {
			exposedObject = earlySingletonReference;
		}
		else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
			// 存在依赖当前bean的其他bean
			String[] dependentBeans = getDependentBeans(beanName);
			Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
			for (String dependentBean : dependentBeans) {
				// 判断被依赖的bean是否已被创建好
				if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
					actualDependentBeans.add(dependentBean);
				}
			}
			// 存在其他bean依赖了该bean的初始版本
			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 " +
						"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
			}
		}
	}
}

上面代码逻辑如下图所示:

(4) 通常AOP增强(比如@Aspect注解)是通过org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator实现的。该类实现了SmartInstantiationAwareBeanPostProcessor接口重写了其中的getEarlyBeanReference方法,所以提前暴露出去的bean已经是被增强过的实例了。

@Override
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
	Object cacheKey = getCacheKey(bean.getClass(), beanName);
	if (!this.earlyProxyReferences.contains(cacheKey)) {
		this.earlyProxyReferences.add(cacheKey);
	}
	return wrapIfNecessary(bean, beanName, cacheKey);
}

但是@Async注解的实现类没有实现SmartInstantiationAwareBeanPostProcessor接口,也就没有重写getEarlyBeanReference方法,所以被@Async注解修饰的类(或者其中有被@Async注解修饰的方法)如果出现循环依赖引用可能会导致循环依赖检验不过最终导致服务启动失败。

(5) 因为存在AOP增强的功能,所以这也是为什么Spring中处理循环依赖需要使用三个map的原因。

3. 不能解决的场景

  • (1) prototype作用域的bean

  • (2) 构造器注入方式

  • (3) 针对某些AOP场景中,spring容器提前暴露生成的bean和最终构建的bean不相同

4. 总结

  1. Spring容器中解决了singleton作用域下的setter注入方式的循环依赖,是因为在bean实例化之后在属性被注入之前会提前暴露ObjectFactory。但是需要注意AOP场景(使用了@Async注解的类)。
  2. 不能解决prototype作用域等一些特殊场景下的循环依赖。

标签:依赖,name,Spring,beanName,bean,循环,AOP
From: https://www.cnblogs.com/sv00/p/18279054

相关文章

  • SpringCloud与Dubbo的区别
    在构建分布式系统时,SpringCloud和Dubbo是两个常用的框架。虽然它们都能帮助开发者实现服务之间的通信和治理,但在设计理念、使用场景和技术实现上,两者存在明显的区别。本文将详细探讨SpringCloud与Dubbo的不同之处,以帮助开发者更好地选择适合自己的框架。初始定位与架构设计Sp......
  • SpringCloud提供的多维度解决方案:构建高效微服务生态系统
    在微服务架构日益盛行的今天,SpringCloud凭借其强大的功能和灵活的扩展性,成为了构建微服务生态系统的首选框架之一。本文将从多个维度探讨SpringCloud如何为微服务架构提供全面、高效的解决方案。一、服务注册与发现服务注册与发现是微服务架构中的核心功能之一。SpringCloud......
  • 基于springboot+vue实现的剧本杀服务平台 (源码+L文+ppt)4-112
      摘 要本设计旨在开发一款便捷、有趣的剧本杀服务平台,为剧本杀爱好者提供一个线上游戏体验。平台将提供用户、优质店铺、剧本拼团、剧本分类、系统管理、订单管理和个人中心等功能模块。用户可以浏览和购买各种类型的剧本,参与拼团活动以享受优惠价格。优质店铺将展示各......
  • 基于springboot+vue实现的办公管理系统 (源码+L文+ppt)4-116
      摘 要办公管理系统是一款集成了部门信息、员工管理、考勤签到、请假审批、工作日程安排、会议信息记录及签到、文件资料存储、通知公告发布和留言建议收集等多项功能的综合性软件。它旨在简化日常办公流程,提高工作效率,通过系统化的信息管理,确保数据的准确性和安全性。......
  • 基于springboot+vue实现的剧本杀服务平台 (源码+L文+ppt)4-112
      摘 要本设计旨在开发一款便捷、有趣的剧本杀服务平台,为剧本杀爱好者提供一个线上游戏体验。平台将提供用户、优质店铺、剧本拼团、剧本分类、系统管理、订单管理和个人中心等功能模块。用户可以浏览和购买各种类型的剧本,参与拼团活动以享受优惠价格。优质店铺将展示各......
  • 基于springboot+vue实现的剧本杀服务平台 (源码+L文+ppt)4-112
      摘 要本设计旨在开发一款便捷、有趣的剧本杀服务平台,为剧本杀爱好者提供一个线上游戏体验。平台将提供用户、优质店铺、剧本拼团、剧本分类、系统管理、订单管理和个人中心等功能模块。用户可以浏览和购买各种类型的剧本,参与拼团活动以享受优惠价格。优质店铺将展示各......
  • 基于springboot+vue实现的办公管理系统 (源码+L文+ppt)4-116
      摘 要办公管理系统是一款集成了部门信息、员工管理、考勤签到、请假审批、工作日程安排、会议信息记录及签到、文件资料存储、通知公告发布和留言建议收集等多项功能的综合性软件。它旨在简化日常办公流程,提高工作效率,通过系统化的信息管理,确保数据的准确性和安全性。......
  • SpringCloudStream极简教程
    简介SpringCloudStream是一个轻量级消息驱动微服务框架,旨在简化与消息中间件(如Kafka、RabbitMQ等)的集成,支持消息的发布和订阅模式。它提供了一种基于Spring编程模型的方式(即自动依赖注入和强调通过注解来完成功能的封装),使得构建可扩展和灵活的消息驱动应用变得更加简单。......
  • 【Spring】logback-spring.xml 日志配置
     写项目没关注过这个东西,项目工程文件也是同事从别的项目上搬过来的测试跑环境发现不打印日志了,叫我看看怎么个事情我们找到 logback-spring.xml 文件<?xmlversion="1.0"encoding="UTF-8"?><configurationscan="true"scanPeriod="10seconds"><!--start:控制......
  • Springboot定时任务详解
    文章目录Springboot定时任务详解一、引言二、cron表达式三、使用`@Scheduled`注解1、开启定时任务2、添加定时任务四、使用`TaskScheduler`接口1、注入`TaskScheduler`实例五、集成Quartz框架1、集成Quartz六、实际使用示例七、总结Springboot定时任务详解一、引......