程序员社区

Spring 框架源码解读7


title: Spring 框架源码解读7
date: 2020/04/17 16:09


本节内容 & 思考题

我们之前的依赖注入的版本是不支持循环依赖的,我们本节就将其改造成支持的。

Spring 中是否支持循环依赖?

支持

为什么 2个对象都是原型的就不支持了呢?

因为 A 中引用 B 就需要创建 B,B 引用了 A 就要创建 A1 ...

怎样关闭 Spring 的循环依赖?

打破下面三个条件的随便哪个

// 满足单例 + 开启了循环依赖(默认为true) + bean在singletonsCurrentlyInCreation集合中时
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
            isSingletonCurrentlyInCreation(beanName));

谈谈 Spring 的循环依赖具体实现?

三级缓存

修改 AbstractBeanFactory#getBean()

public abstract class AbstractBeanFactory implements BeanFactory {

    ...

    // 二级缓存:维护早期暴露的Bean(只进行了实例化,并未进行属性注入)
    private final Map<String, Object> earlySingletonObjects = new HashMap<>();

    /**
     * 根据名称获取对应的 bean (工厂方法模式)
     * <p>
     * 注:没有对原型做处理,如果2个对象都是原型,则会进入死循环从而堆栈溢出
     */
    @Override
    public Object getBean(String name) {

        // 先从一级缓存中获取
        if (sharedInstanceCache.containsKey(name)) {
            return sharedInstanceCache.get(name);
        }

        // 如果一级缓存中没有,则从二级缓存中获取(防止循环引用)
        if (earlySingletonObjects.containsKey(name)) {
            return earlySingletonObjects.get(name);
        }

        // 如果二级缓存中没有,则获取 bd 创建
        BeanDefinition bd = this.getBeanDefinition(name);
        if (ObjectUtil.isNotNull(bd)) {
            Object bean = this.createBean(bd);
            // 判断是否是单例,如果是则加入一级缓存中
            if (bd.getScope() == BeanDefinition.ScopeEnum.SINGLETON) {
                sharedInstanceCache.put(bd.getName(), bean);
            }
            return bean;
        }

        // 如果 bd 为空,而且没有父 bf,则抛出异常
        if (ObjectUtil.isNull(parentBeanFactory)) {
            throw new RuntimeException("获取的bean不存在!");
        }

        // 从父 bf 中获取
        return parentBeanFactory.getBean(name);
    }

    /**
     * 根据 bd 创建对象
     * <p>
     * 1)创建bean 日后需要对有参构造进行扩展
     * 2)注入属性(Spring 源码中 2 是在 3456 的后面)
     * 3)调用部分 Aware 的方法
     * 4)后置处理器的前置方法
     * 5)执行初始化操作
     * 6)后置处理器的后置方法
     * 7)注册销毁的处理
     */
    private Object createBean(BeanDefinition beanDefinition) {

        // 1、创建 bean
        BeanWrapper beanWrapper = this.createBeanInstance(beanDefinition);

        // 1.1 将还没有注入属性的 bean 放入二级缓存中
        if (beanDefinition.getScope() == BeanDefinition.ScopeEnum.SINGLETON) {
            earlySingletonObjects.put(beanDefinition.getName(), beanWrapper.getWrappedInstance());
        }

        ...

Spring 0.9

Spring 0.9 中和我们实现的方式基本相同

Spring 5.0 中通过三级缓存实现

本部分参考:https://juejin.im/post/5dbb9fdef265da4d4c202483

当获取 bean 的时候,Spring 会先调用 getSingleton() 方法

Spring 框架源码解读7插图
// 它在Bean开始创建时放入 beanName,创建完成时会将其移出
private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));

/** Cache of singleton objects: bean name --> bean instance */
一级缓存:维护着所有创建完成的Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);

/** Cache of early singleton objects: bean name --> bean instance */
二级缓存:维护早期暴露的Bean(只进行了实例化,并未进行属性注入)
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);

/** Cache of singleton factories: bean name --> ObjectFactory */
三级缓存:维护创建中Bean的ObjectFactory(解决循环依赖的关键)
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);

分析: Spring首先从singletonObjects(一级缓存)中尝试获取,如果获取不到并且对象在创建中,则尝试从earlySingletonObjects(二级缓存)中获取,如果还是获取不到并且允许从singletonFactories通过getObject获取,则通过三级缓存获取,即通过singletonFactory.getObject()。如果获取到了,将其存入二级缓存,并清除三级缓存。

Spring 框架源码解读7插图1

如果缓存中没有bean对象,那么Spring会创建Bean对象,将实例化的bean提前曝光,并且加入缓存中。

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
            throws BeanCreationException {

    // Instantiate the bean.
    BeanWrapper instanceWrapper = null;
    ......
    if (instanceWrapper == null) {
        //这个是实例化Bean的方法,会调用构造方法,生成一个原始类型的Bean
        instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
    
    // 提前曝光这个实例化的Bean,方便其他Bean使用
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
            isSingletonCurrentlyInCreation(beanName));
            
    // 满足单例 + 开启了循环依赖(默认为true) + bean在singletonsCurrentlyInCreation集合中时
    if (earlySingletonExposure) {
        if (logger.isDebugEnabled()) {
            logger.debug("Eagerly caching bean '" + beanName +
                    "' to allow for resolving potential circular references");
        }
        // 将bean加入三级缓存中
        addSingletonFactory(beanName, new ObjectFactory<Object>() {
            @Override
            public Object getObject() throws BeansException {
                return getEarlyBeanReference(beanName, mbd, bean);
            }
        });
    }
    
    // Initialize the bean instance.
    Object exposedObject = bean;
    try {
        // 属性注入,这里可能发生循环依赖
        populateBean(beanName, mbd, instanceWrapper);
        if (exposedObject != null) {
            // 初始化bean 
            exposedObject = initializeBean(beanName, exposedObject, mbd);
        }
    }
    
    // 由于AService提前暴露,会走这段代码
    if (earlySingletonExposure) {
        // 从二级缓存中拿出AService(这个对象其实ObjectFactory.getObject()得来的,可能是个包装类,而exposedObject可能依然是实例化的那个bean,这时为保证最终BService中的AService属性与AService本身持有的引用一直,故再次进行exposedObject的赋值操作,保证beanName对应实例唯一性。)
        Object earlySingletonReference = getSingleton(beanName, false);
        if (earlySingletonReference != null) {
        if (exposedObject == bean) {
            exposedObject = earlySingletonReference;
        }
    }
    // ...........
    return exposedObject;
}

分析:当通过无参构造,获得一个实例化bean时,Spring会将其提前曝光,即在实例化后注入属性前将其加入三级缓存中。下面以AService和BService相互依赖为例,说明依赖排除过程。

  1. AService实例化后,在注入属性前提前曝光,将其加入三级缓存singletonFactories中,供其他bean使用;
  2. AService通过populateBean注入BService,从缓存中获取BService,发现缓存中没有,开始创建BService实例;
  3. BService实例也会在属性注入前提前曝光,加入三级缓存中,此时三级缓存中有AService和BService;
  4. BService在进行属性注入时,发现有AService引用,此时,创建AService时,会先从缓存中获取AService(先从一级缓存中取,没有取到后,从二级缓存中取,也没有取到,这时,从三级缓存中取出),这时会清除三级缓存中的AService,将其将其加入二级缓存earlySingletonObjects中,并返回给BService供其使用;
  5. BService在完成属性注入,进行初始化,这时会加入一级缓存,这时会清除三级缓存中的BService,此时,三级缓存为空,二级缓存中有AService,一级缓存中有BService;
  6. BService初始化后注入AService中,AService进行初始化,然后通过getSingleton方法获取二级缓存,赋值给exposedObject,最后将其加入一级缓存,清除二级缓存AService;

从上述分析可知,singletonFactories即三级缓存才是解决循环依赖的关键,它是一个桥梁。当AService初始化后,会从二级缓存中获取提前暴露的对象,并且赋值给exposedObject。这主要是二级缓存的对象earlySingletonReference可能是包装类,BService持有的引用就是这个earlySingletonReference,赋值后保证beanName对应实例唯一性,这点回味无穷。

Spring 框架源码解读7插图2

你是不是和我一样有疑问,为啥要有第 3 级缓存,不是已经有了 bean 对象了吗,直接放进二级缓存里不行吗?

原因在 getEarlyBeanReference(beanName, mbd, bean) 方法中:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (bean != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
                if (exposedObject == null) {
                    return null;
                }
            }
        }
    }
    return exposedObject;
}

实际上,它是对 bean 做了一步操作,如果没有 SmartInstantiationAwareBeanPostProcessor (默认好像就没有),就相当于直接返回了 bean。

那么 SmartInstantiationAwareBeanPostProcessor 是啥,他要干啥呢:

public interface SmartInstantiationAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessor {

    // 预测Bean的类型,返回第一个预测成功的Class类型,如果不能预测返回null
    Class<?> predictBeanType(Class<?> beanClass, String beanName) throws BeansException;
   
    // 选择合适的构造器,比如目标对象有多个构造器,在这里可以进行一些定制化,选择合适的构造器
    // beanClass参数表示目标实例的类型,beanName是目标实例在Spring容器中的name
    // 返回值是个构造器数组,如果返回null,会执行下一个PostProcessor的determineCandidateConstructors方法;否则选取该PostProcessor选择的构造器
    Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, String beanName) throws BeansException;
    
    // 获得提前暴露的bean引用。主要用于**解决循环引用的问题**
    // 只有单例对象才会调用此方法
    Object getEarlyBeanReference(Object bean, String beanName) throws BeansException;
}

扩展:Spring 中对弱依赖的支持

Spring 框架源码解读7插图3
赞(0) 打赏
未经允许不得转载:IDEA激活码 » Spring 框架源码解读7

一个分享Java & Python知识的社区