spring解决循环依赖的流程
时间: 2023-12-02 18:34:07 浏览: 113
循环依赖是指两个或多个Bean之间相互依赖的情况,而导致Spring在加载Bean时无法解析这些依赖关系的情况。Spring解决循环依赖的流程主要分为三个步骤:
1. 实例化Bean:首先,Spring会创建一个Bean的半成品,也就是一个空对象。并将该对象缓存到Early Singleton Objects cache中。
2. 填充属性:Spring会开始填充该对象的属性,但是,如果该对象的属性中有引用到其他的Bean,则Spring会暂时将该属性赋值为null,并将该半成品对象缓存到Mid Singleton Objects cache中。
3. 注入其他Bean:Spring会递归注入该半成品对象中的其他属性所引用的Bean。如果在递归注入的过程中,发现有某个Bean需要引用到之前创建的Bean,那么Spring就会直接从Mid Singleton Objects cache中获取该Bean,并将其注入到当前对象中,以完成循环依赖的解决。
最后,当所有的Bean都被填充完毕后,Spring会将这些半成品对象转换为真正的Bean,并将它们缓存到Singleton Objects cache中,供其他的Bean去引用。
相关问题
spring解决循环依赖三级缓存
### Spring 框架中的三级缓存机制
Spring框架采用`singletonObjects`, `earlySingletonObjects`, 和 `singletonFactories`这三级缓存来处理循环依赖问题[^1]。
#### 单例模式下的Bean管理
当一个单例模式的Bean A正在创建过程中,另一个Bean B尝试注入它时,如果此时A还未完全初始化完成,则会触发循环依赖检测逻辑。为了应对这种情况,Spring设计了一套复杂的缓存策略:
- **一级缓存 (`singletonObjects`)**
这是一个标准的缓存层,用来存储已经完全初始化完毕并可随时使用的单例Beans。一旦某个Bean完成了所有的初始化工作(包括属性设置、生命周期回调等),就会被放置到这里供其他组件使用[^4]。
- **二级缓存 (`earlySingletonObjects`)**
当前正在创建过程中的Bean,在其部分初始化状态下会被提前暴露给其他需要它的Beans。具体来说就是在这个阶段,即使Bean还没有完成全部配置项的赋值,也允许将其作为“早期版本”的形式提供出去。这种做法使得即便存在相互之间的引用关系,也能顺利完成各自的构建流程[^5]。
- **三级缓存 (`singletonFactories`)**
主要负责保存那些尚未准备好直接交付使用的Beans对应的工厂实例(`ObjectFactory`)。每当请求访问某特定名称下的Bean时,先检查是否有一级缓存命中;如果没有找到对应记录,则进一步查询是否有可用的对象工厂存在于此级别内。如果有匹配的结果,那么就通过调用该工厂的方法即时生产所需的对象,并立即将新产生的实体转移到更高级别的缓存中以便于后续重复利用[^3]。
#### 构造器引发的特殊情况
值得注意的是,对于由构造函数引起的循环依赖情况,上述方案并不适用。原因在于此类情形发生得非常早——甚至在任何类型的预设或代理之前就已经开始了。因此,在这个时候根本无法将任何一个不完整的对象加入到任一层次级别的缓存当中去[^2]。
```java
// 示例代码展示如何从三级缓存获取Bean
public Object getBean(String beanName){
// 尝试从一级缓存获取
Object sharedInstance = singletonObjects.get(beanName);
if (sharedInstance != null) {
return getObjectForBeanInstance(sharedInstance, name);
}
// 如果不在一级缓存中,查看是否可以在二级缓存中找到
Object early SingletonObject = earlySingletonObjects.get(beanName);
if (earlySingletonObject != null) {
return earlySingletonObject;
}
// 否则考虑使用三级缓存里的factory生成目标bean
ObjectFactory<?> objectFactory = singletonFactories.get(beanName);
if(objectFactory!=null){
beforeSingletonCreation(beanName);
try{
earlySingletonObjects.put(beanName,objectFactory.getObject());
singletonFactories.remove(beanName);
afterSingletonCreation(beanName);
return earlySingletonObjects.get(beanName);
}catch(Throwable ex){
throw new BeansException("...");
}
}
}
```
spring解决循环依赖,还需要@Lazy
### Spring框架中通过@Lazy注解解决循环依赖问题
在Spring框架中,`@Lazy`注解提供了一种机制来解决某些类型的循环依赖问题。当两个或多个bean之间存在相互依赖关系时,可能会导致容器初始化失败。然而,在特定情况下,可以通过`@Lazy`注解延迟加载bean的方式绕过这种问题。
#### 循环依赖的背景
Spring中的循环依赖通常发生在以下两种场景之一:
1. 构造器注入方式下的单例模式bean之间的循环依赖。
2. Setter方法或其他自定义注入方式下的循环依赖。
对于第一种情况,Spring默认支持基于构造器注入的单例bean间的循环依赖解决方案;而对于第二种情况,则可能需要借助于`@Lazy`注解来解决问题[^2]。
#### `@Lazy`的工作原理
`@Lazy`注解的核心作用在于延迟bean的实例化过程。具体来说,它会在实际需要使用某个bean的时候才去创建其实例,而不是在应用上下文启动阶段就立即完成实例化操作。以下是其主要应用场景:
- **标记在普通bean类上**
被标注为`@Lazy`的bean会被推迟至第一次访问时才会触发实例化流程。
- **标记在@Configuration配置类上**
如果在一个带有`@Configuration`注解的类上加上`@Lazy`,则整个配置类以及其中声明的所有`@Bean`都会受到懒加载的影响。
- **用于@Autowired字段或者参数处**
当`@Lazy`应用于`@Autowired`修饰的成员变量或方法参数时,表示这些地方所引用的对象也应采用懒加载策略。需要注意的是,这仅影响注入时机而不会改变目标bean本身的生命周期特性。
#### 解决循环依赖的具体实现细节
为了理解`@Lazy`是如何帮助缓解循环依赖难题的,我们需要深入探讨一下Spring内部处理依赖解析的过程。假设A和B构成了典型的双向依赖结构——即A持有对B的引用的同时,B又反过来指向了A。如果两者都未设置任何特殊的加载选项,默认情况下就会引发异常因为无法决定谁先被完全构建出来供另一方消费。
但是假如我们给其中一个方向加上了`@Lazy`标签呢?比如让B成为懒载入型组件的话,那么事情便有了转机:虽然此时仍然要经历一轮初步装配动作(也就是把尚未成熟的半成品版本交给对方),但由于涉及到了后者部分的关键环节已被延缓执行的缘故,所以整体链条得以顺利贯通下去直到最终形成稳定状态为止[^3]。
下面是简化版伪代码展示这一交互过程的一部分片段:
```java
@Override
@Nullable
public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, @Nullable String beanName) {
return isLazy(descriptor) ? buildLazyResolutionProxy(descriptor, beanName) : null;
}
```
上述方法来自DefaultListableBeanFactory类内部定义,负责判断当前描述符是否满足懒加载条件并据此返回相应的代理对象而非真实主体本身。这样做的好处就在于能够暂时规避掉那些可能导致死锁风险的操作序列从而保障系统的正常运转。
另外值得一提的小插曲就是关于代理生成工具的选择方面。在这里提到过的ProxyFactory实际上正是承担此项重任的主要角色之一,并且由于是非线程安全的设计因而建议避免共享同一个实例跨多线程环境重复利用的情况发生[^5]。
最后再补充一句有关自动装配处理器注册时间点的信息。我们知道像AutowiredAnnotationBeanPostProcessor这样的特殊组件往往早早就已经加入到全局管理池当中等待后续发挥作用的机会到来。它们的存在意义就在于能够在恰当的时间介入业务逻辑之中按照预定规则完成必要的绑定任务等等[^4]。
---
阅读全文
相关推荐














