Spring框架的依赖注入(DI)是其核心功能之一,为了解决循环依赖的问题,Spring提供了所谓的“三级缓存”。这个机制确保了即使是彼此依赖的bean也能被正确地创建和管理。
循环依赖是指两个或多个bean相互依赖,形成一个闭环。例如,A
bean依赖B
bean,而B
bean同时依赖A
bean。在单例模式下,Spring容器通过三级缓存来解决这一问题。
Spring容器使用以下三个缓存来存储bean:
一级缓存(Singleton Objects):
存储完全初始化好的bean,即依赖注入完全完成,可以直接使用的bean。
二级缓存(Early Singleton Objects):
存储原始的bean(尚未完成依赖注入),通常是在bean实例化之后,但在依赖注入开始之前。
三级缓存(Singleton Factories):
存储bean工厂对象,用于生成对应的bean,通常会包含一个引用到ObjectFactory
,通过这个工厂对象可以获取早期的bean引用。
当容器实例化一个bean时,它会经历以下步骤:
创建bean实例:
通过构造器或工厂方法创建bean实例。
填充属性:
注入属性值和其他bean的引用。
Bean后处理器:
进行BeanPostProcessor处理,比如@Autowired
注解的处理。
处理循环依赖的关键在于第三步,使用BeanPostProcessor
时,如果发现依赖的bean尚未创建,则会通过三级缓存来提早曝光一个bean的引用。
在Spring的DefaultSingletonBeanRegistry
类中,你会看到如下的三个Map,作为缓存的数据结构:
/** 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);
当通过getBean
方法请求一个bean时,Spring容器会尝试从一级缓存获取。如果没有找到,它会尝试通过工厂对象(三级缓存)创建一个bean。创建过程中如果需要依赖注入,而所依赖的bean也在创建过程中,就会形成一个循环依赖。
在创建bean的过程中,一旦实例化后,Spring会将其包装成一个工厂对象存入三级缓存,然后继续bean的创建。如果另一个bean需要依赖当前创建中的bean,就可以通过三级缓存的工厂获取到早期引用。
这里以一个简化的代码示例来说明Spring如何处理循环依赖:
public class CircularDependencyDemo {
@Component
public static class BeanA {
@Autowired
private BeanB beanB;
}
@Component
public static class BeanB {
@Autowired
private BeanA beanA;
}
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(CircularDependencyDemo.class);
BeanA beanA = context.getBean(BeanA.class);
BeanB beanB = context.getBean(BeanB.class);
}
}
在上面的例子中,BeanA
和BeanB
形成了循环依赖。Spring容器会通过三级缓存确保两个bean都能被创建。
关键的源码在AbstractAutowireCapableBeanFactory
的doCreateBean
方法中。这个方法会在bean实例化之后、依赖注入之前将其包装为ObjectFactory,并放入三级缓存中。然后在依赖注入的过程中,如果需要引用其他bean,容器会首先检查一级缓存,然后是二、三级缓存,以解决循环依赖。如果当前bean的依赖存在于缓存中,就会使用缓存中的对象来完成注入。
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
// Instantiate the bean...
BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
// Allow post-processors to modify the merged bean definition...
// Initialize the bean instance...
Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (...) {
// Handle initialization failure...
}
return exposedObject;
}
在populateBean
方法中,Spring会处理依赖注入(例如通过@Autowired
注解)。如果在这个过程中需要依赖其他bean,Spring就会尝试从缓存中加载这个bean,以满足当前bean的依赖。
需要注意的是,正是这种三级缓存的设计让Spring能够解决单例bean的循环依赖问题。不过,对于原型(Prototype)作用域的bean,Spring无法解决循环依赖问题,因为每次请求原型bean都会创建一个新的实例。