Skip to content

Latest commit

 

History

History
509 lines (402 loc) · 27.7 KB

2021-08-07-第17章:攻无不克,通过三级缓存解决循环依赖.md

File metadata and controls

509 lines (402 loc) · 27.7 KB
title lock
第17章:通过三级缓存解决循环依赖
need

第 17 章:攻无不克,通过三级缓存解决循环依赖

作者:小傅哥
博客:https://bugstack.cn
星球:https://articles.zsxq.com/id_w629m13v0hni.html

沉淀、分享、成长,让自己和他人都能有所收获!😄

零、优秀作业

一、前言

嘎哈呀,又不是不能用!

我经常说业务逻辑的代码实现,就像擦屁屁的纸,80%的面积都是保护手的。而那20%的核心流程也就仅仅是你说的能用就行,反正每次都洗手呗。

其实想把程序从能用实现到好用并不容易,这包括你对业务的理解、你对架构的把控、你对细节的实现等等,也包括你是否能做一些列的抽象实现,不至于整个程序随着开发的越多就变的越臃肿不堪。

那么对于编程上的写好程序的理解,我通常喜欢用生活中实际的例子来表达,因为有不少前辈的研发大牛都说:“你要面对对象编程”。所以嘞,我可能会用生活中的超市、展台、货架、官渡等来对我的程序开发中的类或者领域服务进行命名和实现,这样抽象化出来的代码逻辑更具有扩展性,也能让新接手的人快速理解并且不至于慌乱的开发。

二、目标

按照目前我们实现的 Spring 框架,是可以满足一个基本需求的,但如果你配置了A、B两个Bean对象互相依赖,那么立马会抛出 java.lang.StackOverflowError,为什么呢?因为A创建时需要依赖B创建,而B的创建又依赖于A创建,就这样死循环了。

而这个循环依赖基本也可以说是 Spring 中非常经典的实现了,所要解决的场景主要有以下三种情况:

  • 循环依赖主要分为这三种,自身依赖于自身、互相循环依赖、多组循环依赖。
  • 但无论循环依赖的数量有多少,循环依赖的本质是一样的。就是你的完整创建依赖于我,而我的完整创建也依赖于你,但我们互相没法解耦,最终导致依赖创建失败。
  • 所以需要 Spring 提供了除了构造函数注入和原型注入外的,setter 循环依赖注入解决方案。

三、设计

按照 Spring 框架的设计,用于解决循环依赖需要用到三个缓存,这三个缓存分别存放了成品对象半成品对象(未填充属性值)代理对象,分阶段存放对象内容,来解决循环依赖问题。

那么,这里我们需要知道一个核心的原理,就是用于解决循环依赖就必须是三级缓存呢,二级行吗?一级可以不?其实都能解决,只不过 Spring 框架的实现要保证几个事情,如只有一级缓存处理流程没法拆分,复杂度也会增加,同时半成品对象可能会有空指针异常。而将半成品与成品对象分开,处理起来也更加优雅、简单、易扩展。另外 Spring 的两大特性中不仅有 IOC 还有 AOP,也就是基于字节码增强后的方法,该存放到哪,而三级缓存最主要,要解决的循环依赖就是对 AOP 的处理,但如果把 AOP 代理对象的创建提前,那么二级缓存也一样可以解决。但是,这就违背了 Spring 创建对象的原则,Spring 更喜欢把所有的普通 Bean 都初始化完成,在处理代理对象的初始化。

不过,没关系我们可以先尝试仅适用一级缓存来解决循环依赖,通过这样的方式从中学习到处理循环依赖的最核心原来,也就是那20%的地方。

  • 如果仅以一级缓存解决循环依赖,那么在实现上可以通过在A对象 newInstance 创建且未填充属性后,直接放入缓存中。
  • A对象的属性填充B对象时,如果缓存中不能获取到B对象,则开始创建B对象,同样创建完成后,把B对象填充到缓存中去。
  • 接下来就开始对B对象的属性进行填充,恰好这会可以从缓存中拿到半成品的A对象,那么这个时候B对象的属性就填充完了。
  • 最后返回来继续完成A对象的属性填充,把实例化后并填充了属性的B对象赋值给A对象的b属性,这样就完成了一个循环依赖操作。

代码实现

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());
}

cn.bugstack.springframework.test.A@49476842
cn.bugstack.springframework.test.B@78308db1

Process finished with exit code 0

  • 从测试效果和截图依赖过程中可以看到,一级缓存也可以解决简单场景的循环依赖问题。
  • 其实 getBean,是整个解决循环依赖的核心内容,A 创建后填充属性时依赖 B,那么就去创建 B,在创建 B 开始填充时发现依赖于 A,但此时 A 这个半成品对象已经存放在缓存到singletonObjects 中了,所以 B 可以正常创建,在通过递归把 A 也创建完整了。

有了以上这部分关于循环依赖的处理内容,在理解循环依赖就没那么复杂了。接下来我们带着这个感觉去思考如果有对象不只是简单的对象,还有代理对象,还有AOP应用,要怎么处理这样的依赖问题。整体设计结构如下图:

  • 关于循环依赖在我们目前的 Spring 框架中扩展起来也并不会太复杂,主要就是对于创建对象的提前暴露,如果是工厂对象则会使用 getEarlyBeanReference 逻辑提前将工厂🏭对象存放到三级缓存中。等到后续获取对象的时候实际拿到的是工厂对象中 getObject,这个才是最终的实际对象。
  • 在创建对象的 AbstractAutowireCapableBeanFactory#doCreateBean 方法中,提前暴露对象以后,就可以通过接下来的流程,getSingleton 从三个缓存中以此寻找对象,一级、二级如果有则直接取走,如果对象是三级缓存中则会从三级缓存中获取后并删掉工厂对象,把实际对象放到二级缓存中。
  • 最后是关于单例的对象的注册操作,这个注册操作就是把真实的实际对象放到一级缓存中,因为此时它已经是一个成品对象了。

四、实现

1. 工程结构

small-spring-step-16
└── src
    ├── main
    │   └── java
    │       └── cn.bugstack.springframework
    │           ├── aop
    │           │   ├── aspectj
    │           │   │   └── AspectJExpressionPointcut.java
    │           │   │   └── AspectJExpressionPointcutAdvisor.java
    │           │   ├── framework 
    │           │   │   ├── adapter
    │           │   │   │   └── MethodBeforeAdviceInterceptor.java
    │           │   │   ├── autoproxy
    │           │   │   │   └── MethodBeforeAdviceInterceptor.java
    │           │   │   ├── AopProxy.java
    │           │   │   ├── Cglib2AopProxy.java
    │           │   │   ├── JdkDynamicAopProxy.java
    │           │   │   ├── ProxyFactory.java
    │           │   │   └── ReflectiveMethodInvocation.java
    │           │   ├── AdvisedSupport.java
    │           │   ├── Advisor.java
    │           │   ├── BeforeAdvice.java
    │           │   ├── ClassFilter.java
    │           │   ├── MethodBeforeAdvice.java
    │           │   ├── MethodMatcher.java
    │           │   ├── Pointcut.java
    │           │   ├── PointcutAdvisor.java
    │           │   └── TargetSource.java
    │           ├── beans
    │           │   ├── factory  
    │           │   │   ├── annotation
    │           │   │   │   ├── Autowired.java
    │           │   │   │   ├── AutowiredAnnotationBeanPostProcessor.java
    │           │   │   │   ├── Qualifier.java
    │           │   │   │   └── Value.java
    │           │   │   ├── config
    │           │   │   │   ├── AutowireCapableBeanFactory.java
    │           │   │   │   ├── BeanDefinition.java
    │           │   │   │   ├── BeanFactoryPostProcessor.java
    │           │   │   │   ├── BeanPostProcessor.java
    │           │   │   │   ├── BeanReference.java
    │           │   │   │   ├── ConfigurableBeanFactory.java
    │           │   │   │   ├── InstantiationAwareBeanPostProcessor.java
    │           │   │   │   └── SingletonBeanRegistry.java
    │           │   │   ├── support
    │           │   │   │   ├── AbstractAutowireCapableBeanFactory.java
    │           │   │   │   ├── AbstractBeanDefinitionReader.java
    │           │   │   │   ├── AbstractBeanFactory.java
    │           │   │   │   ├── BeanDefinitionReader.java
    │           │   │   │   ├── BeanDefinitionRegistry.java
    │           │   │   │   ├── CglibSubclassingInstantiationStrategy.java
    │           │   │   │   ├── DefaultListableBeanFactory.java
    │           │   │   │   ├── DefaultSingletonBeanRegistry.java
    │           │   │   │   ├── DisposableBeanAdapter.java
    │           │   │   │   ├── FactoryBeanRegistrySupport.java
    │           │   │   │   ├── InstantiationStrategy.java
    │           │   │   │   └── SimpleInstantiationStrategy.java  
    │           │   │   ├── support
    │           │   │   │   └── XmlBeanDefinitionReader.java
    │           │   │   ├── Aware.java
    │           │   │   ├── BeanClassLoaderAware.java
    │           │   │   ├── BeanFactory.java
    │           │   │   ├── BeanFactoryAware.java
    │           │   │   ├── BeanNameAware.java
    │           │   │   ├── ConfigurableListableBeanFactory.java
    │           │   │   ├── DisposableBean.java
    │           │   │   ├── FactoryBean.java
    │           │   │   ├── HierarchicalBeanFactory.java
    │           │   │   ├── InitializingBean.java
    │           │   │   ├── ListableBeanFactory.java
    │           │   │   ├── ObjectFactory.java
    │           │   │   └── PropertyPlaceholderConfigurer.java
    │           │   ├── BeansException.java
    │           │   ├── PropertyValue.java
    │           │   └── PropertyValues.java 
    │           ├── context
    │           │   ├── annotation
    │           │   │   ├── ClassPathBeanDefinitionScanner.java 
    │           │   │   ├── ClassPathScanningCandidateComponentProvider.java 
    │           │   │   └── Scope.java 
    │           │   ├── event
    │           │   │   ├── AbstractApplicationEventMulticaster.java 
    │           │   │   ├── ApplicationContextEvent.java 
    │           │   │   ├── ApplicationEventMulticaster.java 
    │           │   │   ├── ContextClosedEvent.java 
    │           │   │   ├── ContextRefreshedEvent.java 
    │           │   │   └── SimpleApplicationEventMulticaster.java 
    │           │   ├── support
    │           │   │   ├── AbstractApplicationContext.java 
    │           │   │   ├── AbstractRefreshableApplicationContext.java 
    │           │   │   ├── AbstractXmlApplicationContext.java 
    │           │   │   ├── ApplicationContextAwareProcessor.java 
    │           │   │   └── ClassPathXmlApplicationContext.java 
    │           │   ├── ApplicationContext.java 
    │           │   ├── ApplicationContextAware.java 
    │           │   ├── ApplicationEvent.java 
    │           │   ├── ApplicationEventPublisher.java 
    │           │   ├── ApplicationListener.java 
    │           │   └── ConfigurableApplicationContext.java
    │           ├── core.io
    │           │   ├── ClassPathResource.java 
    │           │   ├── DefaultResourceLoader.java 
    │           │   ├── FileSystemResource.java 
    │           │   ├── Resource.java 
    │           │   ├── ResourceLoader.java
    │           │   └── UrlResource.java
    │           ├── stereotype
    │           │   └── Component.java
    │           └── utils
    │               ├── ClassUtils.java
    │               └── StringValueResolver.java
    └── test
        └── java
            └── cn.bugstack.springframework.test
                ├── bean
                │   ├── Husband.java
                │   ├── HusbandMother.java
                │   ├── IMother.java
                │   ├── SpouseAdvice.java  
                │   └── Wife.java
                ├── ApiTest.java
                └── CircleTest.java  

工程源码公众号「bugstack虫洞栈」,回复:Spring 专栏,获取完整源码

处理循环依赖核心流程的类关系的操作过程包括:

  • 循环依赖的核心功能实现主要包括 DefaultSingletonBeanRegistry 提供三级缓存:singletonObjectsearlySingletonObjectssingletonFactories,分别存放成品对象、半成品对象和工厂对象。同时包装三个缓存提供方法:getSingleton、registerSingleton、addSingletonFactory,这样使用方就可以分别在不同时间段存放和获取对应的对象了。
  • 在 AbstractAutowireCapableBeanFactory 的 doCreateBean 方法中,提供了关于提前暴露对象的操作,addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, beanDefinition, finalBean));,以及后续获取对象和注册对象的操作, exposedObject = getSingleton(beanName);registerSingleton(beanName, exposedObject);,经过这样的处理就可以完成对复杂场景循环依赖的操作。
  • 另外在 DefaultAdvisorAutoProxyCreator 提供的切面服务中,也需要实现接口 InstantiationAwareBeanPostProcessor 新增的 getEarlyBeanReference 方法,便于把依赖的切面对象也能存放到三级缓存中,处理对应的循环依赖。

2. 设置三级缓存

cn.bugstack.springframework.beans.factory.support.DefaultSingletonBeanRegistry

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);
    }

}
  • 在用于提供单例对象注册的操作的 DefaultSingletonBeanRegistry 类中,共有三个缓存对象的属性;singletonObjects、earlySingletonObjects、singletonFactories,如它们的名字一样,用于存放不同类型的对象(单例对象、早期的半成品单例对象、单例工厂对象)。
  • 紧接着在这三个缓存对象下提供了获取、添加和注册不同对象的方法,包括:getSingleton、registerSingleton、addSingletonFactory,其实后面这两个方法都比较简单,主要是 getSingleton 的操作,它是在一层层处理不同时期的单例对象,直至拿到有效的对象。

3. 提前暴露对象

cn.bugstack.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {

    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;
    }

   // ...
}
  • 在 AbstractAutowireCapableBeanFactory#doCreateBean 的方法中主要是扩展了对象的提前暴露addSingletonFactory了,和单例对象的获取getSingleton以及注册操作registerSingleton
  • 这里提到一点 getEarlyBeanReference 就是定义在如 AOP 切面中这样的代理对象,可以参考源码中接口 InstantiationAwareBeanPostProcessor#getEarlyBeanReference 方法的实现。

五、测试

因为是要测试循环依赖,我们找一个比较贴近的场景来做测试,我说过我是一个喜欢从生活中发现面向对象编程的人 我们的案例场景人物包括:老公和媳妇互相依赖、婆婆是一个模拟成代理妈妈职责、在加上一个切面来关心家庭生活👪

1. 事先准备

老公,类

public class Husband {

    private Wife wife;

    public String queryWife(){
        return "Husband.wife";
    }

}

媳妇,类

public class Wife {

    private Husband husband;
    private IMother mother; // 婆婆

    public String queryHusband() {
        return "Wife.husband、Mother.callMother:" + mother.callMother();
    }

}

婆婆,代理了媳妇原来妈妈的职责的类

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());
    }

}

切面,类

public class SpouseAdvice implements MethodBeforeAdvice {

    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("关怀小两口(切面):" + method);
    }

}

2. 属性配置文件

spring.xml

<bean id="husband" class="cn.bugstack.springframework.test.bean.Husband">
    <property name="wife" ref="wife"/>
</bean>
  
<bean id="wife" class="cn.bugstack.springframework.test.bean.Wife">
    <property name="husband" ref="husband"/>
    <property name="mother" ref="husbandMother"/>
</bean>
  
<bean id="husbandMother" class="cn.bugstack.springframework.test.bean.HusbandMother"/>

<!-- AOP 配置验证三级缓存 -->
<bean class="cn.bugstack.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

<bean id="beforeAdvice" class="cn.bugstack.springframework.test.bean.SpouseAdvice"/>

<bean id="methodInterceptor" class="cn.bugstack.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor">
    <property name="advice" ref="beforeAdvice"/>
</bean>
  
<bean id="pointcutAdvisor" class="cn.bugstack.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor">
    <property name="expression" value="execution(* cn.bugstack.springframework.test.bean.Wife.*(..))"/>
    <property name="advice" ref="methodInterceptor"/>
</bean>
  • 这个里的配置就很简单了,配置husband依赖wife,配置wife依赖husband和mother,最后是关于AOP切面的依赖使用。

3. 单元测试

@Test
public void test_circular() {
    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());
}

测试结果

老公的媳妇Husband.wife
关怀小两口(切面):public java.lang.String cn.bugstack.springframework.test.bean.Wife.queryHusband()
媳妇的老公Wife.husbandMother.callMother婚后媳妇妈妈的职责被婆婆代理了callMother

Process finished with exit code 0
  • 从测试结果可以看到,无论是简单对象依赖 老公依赖媳妇、媳妇依赖老公,还是代理工程对象或者 AOP 切面对象都可以在三级缓存下解决循环依赖的问题了。
  • 此外从运行截图 DefaultSingletonBeanRegistry#getSingleton 中也可以看到凡事需要三级缓存存放工厂对象的类,都会使用到 getObject 获取真实对象,并随后存入半成品对象 earlySingletonObjects 中以及移除工厂对象。

六、总结

  • Spring 中所有的功能都是以解决 Java 编程中的特性而存在的,就像我们本章节处理的循环依赖,如果没有 Spring 框架的情况下,可能我们也会尽可能避免写出循环依赖的操作,因为在没有经过加工处理后,这样的依赖关系肯定会报错的。那么这也就是程序从能用到好用的升级
  • 在解决循环依赖的核心流程中,主要是提前暴露对象的设计,以及建立三级缓存的数据结构来存放不同时期的对象,如果说没有如切面和工厂中的代理对象,那么二级缓存也就可以解决了,哪怕是只有一级缓存。但为了设计上的合理和可扩展性,所以创建了三级缓存来放置不同时期的对象。
  • 通过这样的学习也可以思考🤔我们在做程序设计时,将要上线的功能是否能全面支撑起业务的拓展和频繁变化的特性,有时候这些设计思路是可以帮我们拓宽更多的技术设计视野。记得要多加练习!