《手写Spring渐进式源码实践》实践笔记(第十六章 三级缓存解决循环依赖)
文章目录
- 第十六章 通过三级缓存解决循环依赖
- 背景
- 技术背景
- Spring循环依赖
- 循环依赖类型
- 三级缓存解决循环依赖
- 业务背景
- 目标
- 设计
- 一级缓存实现方案
- 设计思路
- 代码实现
- 测试结果
- 三级缓存实现方案
- 实现
- 代码结构
- 类图
- 实现步骤
- 测试
- 事先准备
- 属性配置文件
- 测试用例
- 测试结果:
- 总结
第十六章 通过三级缓存解决循环依赖
背景
技术背景
Spring循环依赖
在Spring框架中,两个或多个Bean之间相互依赖,形成一个环状依赖
的情况, 就是循环依赖。例如,Bean A依赖Bean B,同时Bean B也依赖Bean A,这样就形成了一个循环依赖。循环依赖通常会导致Bean无法正确地被实例化,从而导致应用程序无法正常启动或者出现异常,因此是一种需要尽量避免的情况。
循环依赖类型
根据依赖注入的方式不同,循环依赖可以分为以下几种类型:
- 构造器循环依赖:这是指两个或多个Bean通过构造器参数相互依赖。例如,A的构造器需要B作为参数,而B的构造器又需要A作为参数,这就形成了一个闭环。Spring无法解决这种循环依赖,因为构造器注入会导致实例化对象的过程和对象属性赋值的过程没有分离开,必须在一起完成。
- 属性循环依赖:这是指两个或多个Bean通过属性相互依赖。例如,A有一个setter方法用于注入B,而B也有一个setter方法用于注入A。对于单例(singleton)作用域下的Bean,如果通过setter方式注入属性,Spring通常可以解决这种循环依赖。
三级缓存解决循环依赖
Spring解决循环依赖的主要机制是通过三级缓存。以下是三级缓存解决循环依赖的流程:
- 一级缓存:保存所有已经完成了实例化、属性注入、初始化等所有流程的Bean实例。
- 二级缓存:保存所有早期创建的Bean对象,这些Bean对象还没有完成依赖注入,但已经提前完成了AOP增强(如果有的话)。
- 三级缓存:保存的是singletonBean的生产工厂,即创建单例Bean的工厂。这个工厂用于在需要时生成Bean的实例,并将其放入二级缓存中。
当Spring尝试初始化一个Bean(例如Bean A)时,如果A依赖另一个Bean(例如Bean B),而B又依赖A,就会触发循环依赖的解决流程:
- 实例化A,并将A的对象工厂放入三级缓存。
- 注入A的依赖属性B,此时一级缓存和二级缓存中都没有B,因此开始初始化B。
- 实例化B,并将B的对象工厂放入三级缓存。
- 注入B的依赖属性A,此时一级缓存中没有A,二级缓存中也没有A,但在三级缓存中找到了A的对象工厂。
- 使用A的对象工厂创建A的实例(如果需要AOP增强,则创建增强后的实例),并将A放入二级缓存。
- 完成B的属性注入(此时B依赖的A是二级缓存中的实例),并将B放入一级缓存。
- 继续A的属性注入(此时A依赖的B是一级缓存中的实例),完成A的初始化,并将A放入一级缓存。
通过这种方式,Spring能够解决单例作用域下的循环依赖问题。
此外,还有其他一些方法可以帮助解决或避免Spring中的循环依赖问题:
- 使用@Lazy注解:对依赖的Bean使用@Lazy注解,可以将其标记为延迟加载。这样,当Bean首次被使用时才会被完全初始化,从而避免在初始化阶段就发生循环依赖。
- 重构代码:如果可能的话,通过重构代码来消除循环依赖。例如,可以将一些共享的功能提取到一个新的Bean中,并让A和B都依赖这个新的Bean。
- 避免使用构造器注入:对于可能发生循环依赖的Bean,可以考虑使用setter注入或字段注入代替构造器注入。因为构造器注入要求所有依赖在Bean实例化时就已经存在,而setter注入或字段注入则允许在Bean实例化后再设置依赖。
业务背景
按照目前我们实现的 Spring 框架,是可以满足一个基本需求的,但如果你配置了A、B两个Bean对象互相依赖,那么立马会抛出 java.lang.StackOverflowError
,为什么呢?因为A创建
时需要依赖B创建
,而B的创建又依赖于A创建,就这样死循环了。
而这个循环依赖基本也可以说是 Spring 中非常经典的实现了,所要解决的场景主要有以下三种情况:
- 循环依赖主要分为这三种,自身依赖于自身(
A->A
)、互相循环依赖(A->B, B->A
)、多组循环依赖(A->B, B->C , C->A
)。 - 但无论循环依赖的数量有多少,循环依赖的本质是一样的。就是你的完整创建依赖于我,而我的完整创建也依赖于你,但我们互相没法解耦,最终导致依赖创建失败。
- 所以除了构造函数注入和原型注入外, Spring还提供了setter 循环依赖注入解决方案。
目标
按照 Spring 框架的设计,实现三个缓存解决循环依赖,这三个缓存分别存放了成品对象
、半成品对象(未填充属性值)
、代理对象
,分阶段存放对象内容,来解决循环依赖问题。
设计
一级缓存实现方案
设计思路
首先,我们需要知道一个核心的原理,就是用于解决循环依赖就必须是三级缓存呢,二级行吗?一级可以不?其实都能解决
,只不过 Spring 框架的实现要保证几个事情,如只有一级缓存处理流程没法拆分,复杂度也会增加
,同时半成品对象可能会有空指针异常
。而将半成品与成品对象分开,处理起来也更加优雅、简单、易扩展。另外 Spring 的两大特性中不仅有 IOC 还有 AOP,也就是基于字节码增强后的方法,该存放到哪,而三级缓存最主要,要解决的循环依赖就是对 AOP 的处理
,但如果把 AOP 代理对象的创建提前,那么二级缓存也一样可以解决。但是,这就违背了 Spring 创建对象的原则,Spring 更喜欢把所有的普通 Bean 都初始化完成,再处理代理对象的初始化
。
不过,没关系我们可以先尝试仅适用一级缓存来解决循环依赖,通过这样的方式从中学习到处理循环依赖的最核心原理,整体设计结构如下图:
-
如果仅以一级缓存解决循环依赖,那么在实现上可以通过在A对象 newInstance 创建且未填充属性后,直接放入缓存中。
-
在
A对象
的属性填充B对象
时,如果缓存中不能获取到B对象
,则开始创建B对象
,同样创建完成后,把B对象
填充到缓存中去。 -
接下来就开始对
B对象
的属性进行填充,恰好这会可以从缓存中拿到半成品的A对象
,那么这个时候B对象
的属性就填充完了。 -
最后返回来继续完成
A对象
的属性填充,把实例化后并填充了属性的B对象
赋值给A对象的b属性
,这样就完成了一个循环依赖操作。
代码实现
- 使用一级缓存存放对象的方式,就是这样简单的实现过程,只要是创建完对象,立马塞到缓存里去。这样就可以在其他对象创建时候获取到属性需要填充的对象了。
public class CircleTest {
/**
* 一级缓存,存对象.
*/
private final static Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
private static <T> T getBean(Class<T> beanClass) throws Exception {
String beanName = beanClass.getSimpleName().toLowerCase();
if (singletonObjects.containsKey(beanName)) {
return (T) singletonObjects.get(beanName);
}
// 实例化对象入缓存
Object obj = beanClass.newInstance();
singletonObjects.put(beanName, obj);
// 属性填充补全对象
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
Class<?> fieldClass = field.getType();
String fieldBeanName = fieldClass.getSimpleName().toLowerCase();
field.set(obj, singletonObjects.containsKey(fieldBeanName) ? singletonObjects.get(fieldBeanName) : getBean(fieldClass));
field.setAccessible(false);
}
return (T) obj;
}
public static void main(String[] args) throws Exception {
System.out.println(getBean(B.class).getA());
System.out.println(getBean(A.class).getB());
}
}
class A {
private B b;
public B getB() {
return b;
}
public void setB(B b) {
this.b = b;
}
}
class B {
private A a;
public A getA() {
return a;
}
public void setA(A a) {
this.a = a;
}
}
测试结果
public static void main(String[] args) throws Exception {
System.out.println(getBean(B.class).getA());
System.out.println(getBean(A.class).getB());
}
}
执行结果:
cn.suwg.springframework.test.A@6e0be858
cn.suwg.springframework.test.B@61bbe9ba
Process finished with exit code 0
- 从测试效果和截图依赖过程中可以看到,一级缓存也可以解决简单场景的循环依赖问题。
- 其实 getBean,是整个解决循环依赖的核心内容,A 创建后填充属性时依赖 B,那么就去创建 B,在创建 B 开始填充时发现依赖于 A,但此时 A 这个半成品对象已经存放在缓存到singletonObjects 中了,所以 B 可以正常创建,再通过递归把 A 也创建完整了。
三级缓存实现方案
有了上述一级缓存方案,解决循环依赖的处理内容,再理解循环依赖就没那么复杂了。接下来我们带着这个感觉去思考如果有对象不只是简单的对象,还有代理对象,还有AOP应用,要怎么处理这样的依赖问题。整体设计结构如下图:
- 关于循环依赖在我们目前的 Spring 框架中扩展起来也并不会太复杂,主要就是对于创建对象的提前暴露,如果是工厂对象则会使用 getEarlyBeanReference 逻辑提前将工厂🏭对象存放到三级缓存中。等到后续获取对象的时候实际拿到的是工厂对象中 getObject,这个才是最终的实际对象。
- 在创建对象的 AbstractAutowireCapableBeanFactory#doCreateBean 方法中,提前暴露对象以后,就可以通过接下来的流程,getSingleton 从三个缓存中以此寻找对象,一级、二级如果有则直接取走,如果对象是三级缓存中则会从三级缓存中获取后并删掉工厂对象,把实际对象放到二级缓存中。
- 最后是关于单例的对象的注册操作,这个注册操作就是把真实的实际对象放到一级缓存中,因为此时它已经是一个成品对象了。
实现
代码结构
源码实现:https://github.com/swg209/spring-study/tree/main/step16-bean-circle-dependency
类图
- 在整个类图中,循环依赖的核心功能实现主要包括 DefaultSingletonBeanRegistry 提供三级缓存:singletonObjects、earlySingletonObjects、singletonFactories,分别存放成品对象、半成品对象和工厂对象。同时包装三个缓存提供方法:getSingleton、registerSingleton、addSingletonFactory,这样使用方就可以分别在不同时间段存放和获取对应的对象了。
- 在 AbstractAutowireCapableBeanFactory 的 doCreateBean 方法中,提供了关于提前暴露对象的操作,addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, beanDefinition, finalBean));,以及后续获取对象和注册对象的操作, exposedObject = getSingleton(beanName);、registerSingleton(beanName, exposedObject);,经过这样的处理就可以完成对复杂场景循环依赖的操作。
- 另外在 DefaultAdvisorAutoProxyCreator 提供的切面服务中,也需要实现接口 InstantiationAwareBeanPostProcessor 新增的 getEarlyBeanReference 方法,便于把依赖的切面对象也能存放到三级缓存中,处理对应的循环依赖。
实现步骤
-
创建对象工厂 ObjectFactory
定义工厂对象,方便获取工厂代理。
public interface ObjectFactory<T> { T getObject() throws BeansException; }
-
调整实例化策略
AbstractAutoWireCapableBeanFactory
- 调整实例化策略为 SimpleInstantiationStrategy 因为代理的代理类,这里没有再做过多逻辑的判断。
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory { private InstantiationStrategy instantiationStrategy = new SimpleInstantiationStrategy(); ...... }
DefaultAdvisorAutoProxyCreator
- AOP advisor自动代理创建时,advisedSupport的proxyTargetClass属性是否开启Cglib代理设置为true。
- 不开启 CGLIB 代理会报错的原因是,如果目标类没有实现任何接口,JDK 动态代理将无法创建代理对象。
JDK 动态代理只能代理实现了接口的类
,而CGLIB 代理可以代理没有实现接口的类
。 在你的代码中,如果 advisedSupport.setProxyTargetClass(false) 则会使用 JDK 动态代理。如果目标类没有实现任何接口,代理对象将无法创建,从而导致报错。 - 通过将 advisedSupport.setProxyTargetClass(true), 启用了 CGLIB 代理,这样即使目标类没有实现任何接口,代理对象也能正确创建,从而避免报错。
public class DefaultAdvisorAutoProxyCreator implements InstantiationAwareBeanPostProcessor, BeanFactoryAware { ...... protected Object wrapIfNecessary(Object bean, String beanName) { if (isInfrastructureClass(bean.getClass())) { return bean; } Collection<AspectJExpressionPointcutAdvisor> advisors = beanFactory.getBeansOfType(AspectJExpressionPointcutAdvisor.class).values(); for (AspectJExpressionPointcutAdvisor advisor : advisors) { ClassFilter classFilter = advisor.getPointcut().getClassFilter(); //过滤匹配类 if (!classFilter.matches(bean.getClass())) { continue; } AdvisedSupport advisedSupport = new AdvisedSupport(); TargetSource targetSource = new TargetSource(bean); advisedSupport.setTargetSource(targetSource); advisedSupport.setMethodInterceptor((MethodInterceptor) advisor.getAdvice()); advisedSupport.setMethodMatcher(advisor.getPointcut().getMethodMatcher()); //这里需要修改为true,使用cglib代理. advisedSupport.setProxyTargetClass(true); //返回代理对象. return new ProxyFactory(advisedSupport).getProxy(); } return bean; } }
-
设置三级缓存
DefaultSingletonBeanRegistry
- 在用于提供单例对象注册的操作的 DefaultSingletonBeanRegistry 类中,共有三个缓存对象的属性;singletonObjects、earlySingletonObjects、singletonFactories,如它们的名字一样,用于存放不同类型的对象(单例对象、早期的半成品单例对象、单例工厂对象)。
- 紧接着在这三个缓存对象下提供了获取、添加和注册不同对象的方法,包括:getSingleton、registerSingleton、addSingletonFactory,其实后面这两个方法都比较简单,主要是 getSingleton 的操作,它是在一层层处理不同时期的单例对象,直至拿到有效的对象
public class DefaultSingletonBeanRegistry implements SingletonBeanRegistry { // 一级缓存,普通对象 private Map<String, Object> singletonObjects = new ConcurrentHashMap<>(); // 二级缓存,提前暴漏对象,没有完全实例化的对象 protected final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(); // 三级缓存,存放代理对象 private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(); private final Map<String, DisposableBean> disposableBeans = new LinkedHashMap<>(); @Override public Object getSingleton(String beanName) { Object singletonObject = singletonObjects.get(beanName); if (null == singletonObject) { singletonObject = earlySingletonObjects.get(beanName); // 判断二级缓存中是否有对象,这个对象就是代理对象,因为只有代理对象才会放到三级缓存中 if (null == singletonObject) { ObjectFactory<?> singletonFactory = singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); // 把三级缓存中的代理对象中的真实对象获取出来,放入二级缓存中 earlySingletonObjects.put(beanName, singletonObject); singletonFactories.remove(beanName); } } } return singletonObject; } public void registerSingleton(String beanName, Object singletonObject) { singletonObjects.put(beanName, singletonObject); earlySingletonObjects.remove(beanName); singletonFactories.remove(beanName); } protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory){ if (!this.singletonObjects.containsKey(beanName)) { this.singletonFactories.put(beanName, singletonFactory); this.earlySingletonObjects.remove(beanName); } } public void registerDisposableBean(String beanName, DisposableBean bean) { disposableBeans.put(beanName, bean); } }
-
提前暴露对象
AbstractAutowireCapableBeanFactory
- 调整实例化策略为 SimpleInstantiationStrategy 因为代理的代理类,这里没有再做过多逻辑的判断。
- 在 AbstractAutowireCapableBeanFactory#doCreateBean 的方法中主要是扩展了对象的提前暴露addSingletonFactory了,和单例对象的获取getSingleton以及注册操作registerSingleton。
- 这里提到一点 getEarlyBeanReference 就是定义在如 AOP 切面中这样的代理对象,可以参考源码中接口 InstantiationAwareBeanPostProcessor#getEarlyBeanReference 方法的实现。
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory { private InstantiationStrategy instantiationStrategy = new SimpleInstantiationStrategy(); protected Object doCreateBean(String beanName, BeanDefinition beanDefinition, Object[] args) { Object bean = null; try { // 实例化 Bean bean = createBeanInstance(beanDefinition, beanName, args); // 处理循环依赖,将实例化后的Bean对象提前放入缓存中暴露出来 if (beanDefinition.isSingleton()) { Object finalBean = bean; addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, beanDefinition, finalBean)); } // 实例化后判断 boolean continueWithPropertyPopulation = applyBeanPostProcessorsAfterInstantiation(beanName, bean); if (!continueWithPropertyPopulation) { return bean; } // 在设置 Bean 属性之前,允许 BeanPostProcessor 修改属性值 applyBeanPostProcessorsBeforeApplyingPropertyValues(beanName, bean, beanDefinition); // 给 Bean 填充属性 applyPropertyValues(beanName, bean, beanDefinition); // 执行 Bean 的初始化方法和 BeanPostProcessor 的前置和后置处理方法 bean = initializeBean(beanName, bean, beanDefinition); } catch (Exception e) { throw new BeansException("Instantiation of bean failed", e); } // 注册实现了 DisposableBean 接口的 Bean 对象 registerDisposableBeanIfNecessary(beanName, bean, beanDefinition); // 判断 SCOPE_SINGLETON、SCOPE_PROTOTYPE Object exposedObject = bean; if (beanDefinition.isSingleton()) { // 获取代理对象 exposedObject = getSingleton(beanName); registerSingleton(beanName, exposedObject); } return exposedObject; } protected Object getEarlyBeanReference(String beanName, BeanDefinition beanDefinition, Object bean) { Object exposedObject = bean; for (BeanPostProcessor beanPostProcessor : getBeanPostProcessors()) { if (beanPostProcessor instanceof InstantiationAwareBeanPostProcessor) { exposedObject = ((InstantiationAwareBeanPostProcessor) beanPostProcessor).getEarlyBeanReference(exposedObject, beanName); if (null == exposedObject) return exposedObject; } } return exposedObject; } // ... }
测试
事先准备
相互依赖的类 Husband、Wife、 代理类HusbandMother、AOP切面SpouseAdvice
Husband
public class Husband {
private Wife wife;
public String queryWife() {
return "Husband.wife";
}
public Wife getWife() {
return wife;
}
public void setWife(Wife wife) {
this.wife = wife;
}
}
Wife
public class Wife {
private Husband husband;
private IMother mother; // 婆婆
public String queryHusband() {
return "Wife.husband.Mother.takeCare:" + mother.takeCare();
}
public Husband getHusband() {
return husband;
}
public void setHusband(Husband husband) {
this.husband = husband;
}
public IMother getMother() {
return mother;
}
public void setMother(IMother mother) {
this.mother = mother;
}
}
HusbandMother (代理类)
public class HusbandMother implements FactoryBean<IMother> {
@Override
public IMother getObject() throws Exception {
return (IMother) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[]{IMother.class},
(proxy, method, args) -> "婚后媳妇妈妈的职责被婆婆代理了!" + method.getName());
}
@Override
public Class<?> getObjectType() {
return IMother.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
SpouseAdvice(切面类)
public class SpouseAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("关怀小两口(切面):" + method);
}
}
属性配置文件
spring.xml
- 配置husband依赖wife,配置wife依赖husband和mother,最后是关于AOP切面的依赖使用。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="husband" class="cn.suwg.springframework.test.bean.Husband">
<property name="wife" ref="wife"/>
</bean>
<bean id="husbandMother" class="cn.suwg.springframework.test.bean.HusbandMother"/>
<bean id="wife" class="cn.suwg.springframework.test.bean.Wife">
<property name="husband" ref="husband"/>
<property name="mother" ref="husbandMother"/>
</bean>
<!-- AOP配置,可以验证三级缓存.-->
<bean class="cn.suwg.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
<bean id="beforeAdvice" class="cn.suwg.springframework.test.bean.SpouseAdvice"/>
<bean id="methodInterceptor"
class="cn.suwg.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor">
<property name="advice" ref="beforeAdvice"/>
</bean>
<bean id="pointcutAdvisor" class="cn.suwg.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor">
<property name="expression" value="execution(* cn.suwg.springframework.test.bean.Wife.*(..))"/>
<property name="advice" ref="methodInterceptor"/>
</bean>
</beans>
测试用例
public class ApiTest {
@Test
public void testCircular() {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml");
Husband husband = applicationContext.getBean("husband", Husband.class);
Wife wife = applicationContext.getBean("wife", Wife.class);
System.out.println("老公的媳妇:" + husband.queryWife());
System.out.println("媳妇的老公:" + wife.queryHusband());
}
}
测试结果:
- 从测试结果可以看到,无论是简单对象依赖 还是代理工程对象或者 AOP 切面对象都可以在三级缓存下解决循环依赖的问题了。
- 此外从运行截图 DefaultSingletonBeanRegistry#getSingleton 中也可以看到凡事需要三级缓存存放工厂对象的类,都会使用到 getObject 获取真实对象,并随后存入半成品对象 earlySingletonObjects 中以及移除工厂对象。
总结
- Spring 中所有的功能都是以解决 Java 编程中的特性而存在的,就像我们本章节处理的循环依赖,如果没有 Spring 框架的情况下,可能我们也会尽可能避免写出循环依赖的操作,因为在没有经过加工处理后,这样的依赖关系肯定会报错的。那么这也就是程序从能用到好用的升级
- 在解决循环依赖的核心流程中,主要是提前暴露对象的设计,以及建立三级缓存的数据结构来存放不同时期的对象,如果说没有如切面和工厂中的代理对象,那么二级缓存也就可以解决了,哪怕是只有一级缓存。但为了设计上的合理和可扩展性,所以创建了三级缓存来放置不同时期的对象。
- 排查问题时,可以先自己debug看看情况,对于自己难以排查到问题,可以适当借助大模型工具,提供一些排查思路,可以更快定位到问题。
参考书籍:《手写Spring渐进式源码实践》
书籍源代码:https://github.com/fuzhengwei/small-spring