SpringIOC之循环依赖问题思考

BeanFactory 是如何处理循环依赖?

前言,下面的“循环依赖”换成“循环依赖注入”比较合适,在 Spring 中通过 depends-on 配置的依赖对象如果出现循环依赖会抛出异常

说明:这里的循环依赖指的是单例模式下的 Bean 字段注入时出现的循环依赖。构造器注入对于 Spring 无法自动解决(应该考虑代码设计是否有问题),可通过延迟初始化来处理(lazy注解)。Spring 只解决单例模式下的循环依赖。

在 Spring 底层 IoC 容器 BeanFactory 中处理循环依赖的方法主要借助于以下 3 个 Map 集合:

  1. singletonObjects(一级 Map),里面保存了所有已经初始化好的单例 Bean,也就是会保存 Spring IoC 容器中所有单例的 Spring Bean;

  1. earlySingletonObjects(二级 Map),里面会保存从 三级 Map 获取到的正在初始化的 Bean

  1. singletonFactories(三级 Map),里面保存了正在初始化的 Bean 对应的 ObjectFactory 实现类,调用其 getObject() 方法返回正在初始化的 Bean 对象(仅实例化还没完全初始化好),如果存在则将获取到的 Bean 对象并保存至 二级 Map,同时从当前 三级 Map 移除该 ObjectFactory 实现类。

当通过 getBean 依赖查找时会首先依次从上面三个 Map 获取,存在则返回,不存在则进行初始化,这三个 Map 是处理循环依赖的关键。

例如两个 Bean 出现循环依赖,A 依赖 B,B 依赖 A;当我们去依赖查找 A,在实例化后初始化前会先生成一个 ObjectFactory 对象(可获取当前正在初始化 A)保存在上面的 singletonFactories 中,初始化的过程需注入 B;接下来去查找 B,初始 B 的时候又要去注入 A,又去查找 A ,由于可以通过 singletonFactories 直接拿到正在初始化的 A,那么就可以完成 B 的初始化,最后也完成 A 的初始化,这样就避免出现循环依赖。

问题一:为什么需要上面的 二级 Map
因为通过 三级 Map获取 Bean 会有相关 SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference(..) 的处理,避免重复处理,处理后返回的可能是一个代理对象
例如在循环依赖中一个 Bean 可能被多个 Bean 依赖, A -> B(也依赖 A) -> C -> A,当你获取 A 这个 Bean 时,后续 B 和 C 都要注入 A,没有上面的 二级 Map的话, 三级 Map 保存的 ObjectFactory 实现类会被调用两次,会重复处理,可能出现问题,这样做在性能上也有所提升
问题二:为什么不直接调用这个 ObjectFactory#getObject() 方法放入 二级Map 中,而需要上面的 三级 Map
对于不涉及到 AOP 的 Bean 确实可以不需要 singletonFactories(三级 Map),但是 Spring AOP 就是 Spring 体系中的一员,如果没有singletonFactories(三级 Map),意味着 Bean 在实例化后就要完成 AOP 代理,这样违背了 Spring 的设计原则。Spring 是通过 AnnotationAwareAspectJAutoProxyCreator 这个后置处理器在完全创建好 Bean 后来完成 AOP 代理,而不是在实例化后就立马进行 AOP 代理。如果出现了循环依赖,那没有办法,只有给 Bean 先创建代理对象,但是在没有出现循环依赖的情况下,设计之初就是让 Bean 在完全创建好后才完成 AOP 代理。
提示:AnnotationAwareAspectJAutoProxyCreator 是一个 SmartInstantiationAwareBeanPostProcessor 后置处理器,在它的 getEarlyBeanReference(..) 方法中可以创建代理对象。所以说对于上面的 问题二,如果出现了循环依赖,如果是一个 AOP 代理对象,那只能给 Bean 先创建代理对象,设计之初就是让 Bean 在完全创建好后才完成 AOP 代理。
为什么 Spring 的设计是让 Bean 在完全创建好后才完成 AOP 代理?
因为创建的代理对象需要关联目标对象,在拦截处理的过程中需要根据目标对象执行被拦截的方法,所以这个目标对象最好是一个“成熟态”,而不是仅实例化还未初始化的一个对象。

对应源码位置

<think>嗯,用户现在想了解Spring框架中IoC的原理。之前他们问过Dubbo协议对性能的影响,现在转向Spring的IoC,可能是在学习Spring框架的基础知识,或者是在构建分布式系统时需要整合Spring和Dubbo。 首先,我需要回顾用户提供的引用内容。引用1到5都是关于IoC和DI的解释,包括IoC的概念、容器类型(BeanFactory和ApplicationContext)、以及简单的代码示例。用户明确提到想了解IoC的实现原理和工作机制,所以需要深入这些方面。 用户可能已经知道IoC的基本概念,比如控制反转和依赖注入,但需要更详细的机制。他们可能想知道Spring是如何具体实现这些概念的,比如容器如何管理Bean的生命周期,依赖注入的方式,以及背后的设计模式。 接下来,我需要组织回答的结构。先解释IoC的核心概念,然后分步骤详细说明工作机制,比如配置元数据、容器初始化、Bean实例化、依赖注入等。结合用户提供的引用,特别是引用3提到的BeanFactory和ApplicationContext的区别,需要强调容器的作用。引用5中的代码示例展示了反射创建对象,可以用于解释Bean的实例化过程。 同时,用户可能对实际应用中的高级特性感兴趣,比如循环依赖的处理、AOP的整合,或者容器扩展点。需要确保回答不仅涵盖基础原理,还提到这些高级主题,帮助用户全面理解。 另外,用户可能希望了解IoC带来的好处,比如解耦、可测试性等,但问题主要聚焦在原理和机制,所以需要保持技术深度,避免偏离到优点部分。 最后,生成的相关问题需要引导用户进一步学习,比如Bean的生命周期、不同注入方式的对比,或者与AOP的关系。这些问题应该基于回答内容,帮助用户深化理解。 需要注意引用用户提供的资料,比如在解释容器管理时引用[^3],在实例化过程引用[^5],确保回答有据可依。同时,保持语言清晰,结构分明,使用代码示例和图表辅助说明,增强可读性。</think>#### Spring框架中IoC的实现原理与工作机制 --- ##### 一、IoC的核心设计思想 IoC(控制反转)的本质是将**对象创建**和**依赖管理**的控制权从程序员转移给容器。传统开发模式中,对象通过`new`关键字直接创建依赖对象,导致代码耦合度高;而IoC通过容器自动完成依赖注入,实现对象间的松耦合[^1][^2]。 --- ##### 二、IoC容器核心实现步骤 Spring通过以下关键步骤实现控制反转: 1. **配置元数据加载** - 支持XML/注解/JavaConfig三种方式定义Bean关系 - XML示例(引用[^3]): ```xml <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="url" value="jdbc:mysql://localhost:3306/test"/> </bean> ``` 2. **容器初始化** - **BeanFactory**:基础容器,延迟加载Bean(首次访问时创建) - **ApplicationContext**:扩展容器,预加载所有单例Bean,集成AOP/事件等特性[^3] ```java ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); ``` 3. **Bean实例化过程** - 通过反射机制创建对象(引用[^5]的简化版): ```java Class<?> clazz = Class.forName("com.example.ServiceImpl"); Object instance = clazz.newInstance(); ``` - 完整流程包含: ```mermaid graph TD A[加载Bean定义] --> B[解析依赖关系] B --> C[实例化Bean对象] C --> D[属性注入] D --> E[初始化回调] ``` 4. **依赖注入实现** - **构造器注入**:强制依赖关系的稳定性 ```java @Autowired public UserService(@Qualifier("mysqlDao") UserDao userDao) { this.userDao = userDao; } ``` - **Setter注入**:适用于可选依赖 - **字段注入**:通过反射直接赋值(需配合`@Autowired`) --- ##### 三、核心设计模式解析 1. **工厂模式** - `BeanFactory`接口定义对象创建规范 - 具体实现类(如`DefaultListableBeanFactory`)负责实例化逻辑 2. **反射机制** - 动态解析类信息创建对象(如引用[^5]的`Class.forName()`) - 支持接口与实现类的动态绑定 3. **策略模式** - 通过`InstantiationStrategy`接口提供多种实例化策略: - CGLIB动态代理(无构造函数的类) - 反射直接实例化 --- ##### 四、关键源码实现剖析 1. **Bean生命周期管理** ```java // AbstractAutowireCapableBeanFactory 核心方法 protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) { // 1. 实例化 BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args); // 2. 属性注入 populateBean(beanName, mbd, instanceWrapper); // 3. 初始化 exposedObject = initializeBean(beanName, exposedObject, mbd); return exposedObject; } ``` 2. **循环依赖解决** - 三级缓存机制: | 缓存级别 | 存储内容 | 作用 | |----------------|---------------------------|--------------------------------| | singletonObjects | 完整Bean对象 | 最终可用的Bean | | earlySingletonObjects | 早期引用 | 处理循环依赖的中间状态 | | singletonFactories | ObjectFactory工厂对象 | 生成代理对象的回调接口 | --- ##### 五、高级特性实现 1. **AOP集成** - 通过`BeanPostProcessor`接口在Bean初始化阶段植入代理 - 动态生成代理类实现切面逻辑 2. **条件化Bean注册** - 使用`@Conditional`注解实现环境适配: ```java @Bean @Conditional(ProdEnvCondition.class) public DataSource prodDataSource() { return new DruidDataSource(); } ``` 3. **容器扩展点** - `BeanFactoryPostProcessor`:修改Bean定义(如属性占位符替换) - `BeanPostProcessor`:干预Bean创建过程(如自动注入) --- ##### 六、性能优化实践 1. **懒加载优化** - 对非核心Bean设置`lazy-init="true"`延迟初始化 - 减少应用启动时的资源消耗 2. **作用域控制** | 作用域类型 | 特点 | 适用场景 | |-------------|---------------------------|-------------------------| | singleton | 容器内唯一实例(默认) | 无状态服务 | | prototype | 每次请求新实例 | 有状态对象 | | request | 单个HTTP请求生命周期 | Web层对象 | 3. **注解扫描优化** - 指定精确扫描路径避免全包扫描: ```java @ComponentScan(basePackages = "com.example.core") ``` --- ##### 七、设计思考 1. **与传统开发的本质区别** 通过比较`new`关键字直接创建与IoC容器管理的区别(如引用[^2]的"找女朋友"比喻),理解控制权反转带来的架构优势: - 对象关系可视化 - 配置与实现分离 - 更容易实现单元测试 2. **与DI的关系** DI(依赖注入)是IoC的具体实现手段,Spring通过以下方式实现: - 构造器注入(强依赖) - Setter注入(可选依赖) - 接口注入(较少使用) --- --相关问题--: 1. Spring如何处理Bean之间的循环依赖问题? 2. BeanFactory和ApplicationContext在实现上有哪些具体差异? 3. 如何通过自定义BeanPostProcessor实现扩展功能? 4. Spring的注解驱动开发是如何与IoC容器整合的?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Coder_Boy_

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

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

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

打赏作者

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

抵扣说明:

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

余额充值