【闲聊杂谈】源码追踪Spring的三级缓存及循环依赖

导读:本篇文章讲解 【闲聊杂谈】源码追踪Spring的三级缓存及循环依赖,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

在上一篇中我们详细讨论了Spring中Bean的生命周期,对三级缓存和循环依赖的问题有过一些比较粗略的讲解。那么这篇结合自定义的代码和Spring的源码一起深入了解下Spring的三级缓存及循环依赖问题。

首先我们要知道什么是循环依赖问题?

【闲聊杂谈】源码追踪Spring的三级缓存及循环依赖

此时图中是一个闭环。如果想解决这个闭环问题,那么必须要保证不会出现第二次创建A对象这个步骤,也就是说从容器中必须能够获取到A这个对象。在Spring中,对象的创建分为实例化和初始化,实例化完成初始化尚未开始的时候,已实例化的对象是可以直接给其它对象引用的。所以可以通过把已实例化未初始化的对象提前暴露出去,让其它对象可以引用,这样就解决了这个闭环的问题,而已实例化未初始化的对象就放在缓存中。

Spring三级缓存分别是
一级缓存:
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

二级缓存:
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

三级缓存:
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

三级缓存的执行流程在DefaultSingletonBeanRegistry类中的getSingleton方法
先从一级缓存中取,一级缓存总没有再从二级缓存中取,二级缓存总没有再从三级缓存中取

如果发生循环依赖的对象,不需要代理的话,只需要二级缓存足矣;但是如果存在代理之后,必须要使用三级缓存才能解决。

好,接下来自定义类和xml文件进行Debug的跟踪:

【闲聊杂谈】源码追踪Spring的三级缓存及循环依赖【闲聊杂谈】源码追踪Spring的三级缓存及循环依赖【闲聊杂谈】源码追踪Spring的三级缓存及循环依赖

来到preInstantiateSingletons方法中

【闲聊杂谈】源码追踪Spring的三级缓存及循环依赖

// 将所有BeanDefinition的名字创建一个集合
List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

// 触发所有非延迟加载单例Bean的初始化,遍历集合的对象
for (String beanName : beanNames) {
    // 合并父类BeanDefinition
    RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
    // 需满足:抽象&&单例&&非懒加载
    if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
        // 判断是否实现了FactoryBean接口
        if (isFactoryBean(beanName)) {
            …..
        } else {
            // 如果beanName对应的bean不是FactoryBean,而只是普通的Bean
            // 通过beanName获取bean实例
            getBean(beanName);
        }
进入getBean方法中,再进入doGetBean方法中,这个方法中有一个很重要的判断方法getSingleton,这个就是从一级缓存中判断下有没有这个对象

【闲聊杂谈】源码追踪Spring的三级缓存及循环依赖

返回到doGetBean方法中
// 当对象都是单例的时候尝试解决循环依赖的问题
// 但是原型模式下如果存在循环依赖的情况
// 则直接抛出异常
if (isPrototypeCurrentlyInCreation(beanName)) {
    throw new BeanCurrentlyInCreationException(beanName);
}

代码继续往下走
// 创建Bean的实例对象
if (mbd.isSingleton()) {
    // 返回以beanName的原始单例对象,如果尚未注册
    // 则使用singletonFactory创建并注册一个对象
    sharedInstance = getSingleton(beanName, () -> {
        try {
            // 为给定的合并后BeanDefinition和参数创建一个Bean实例
            return createBean(beanName, mbd, args);
        } catch (BeansException ex) {
            // Explicitly remove instance from singleton cache: It might have been put there
            // eagerly by the creation process, to allow for circular reference resolution.
            // Also remove any beans that received a temporary reference to the bean.
            destroySingleton(beanName);
            throw ex;
        }
    });
    
    beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
当调用getSingleton这个方法的时候,第一个参数是beanName,第二个参数是一个匿名内部类,就是一个lambda表达式。进入方法中

【闲聊杂谈】源码追踪Spring的三级缓存及循环依赖

进入到singletonFactory.getObject()方法中,继续进入到createBean方法中,找到doCreateBean方法进入 

【闲聊杂谈】源码追踪Spring的三级缓存及循环依赖

// 根据执行bean使用对应的策略创建新的实例
// 比如:工厂方法、构造函数主注入、简单初始化等
instanceWrapper = createBeanInstance(beanName, mbd, args);

// 从包装类中获取原始Bean
Object bean = instanceWrapper.getWrappedInstance();

此时的A是只是一个完成了实例化,尚未初始化的半成品。

【闲聊杂谈】源码追踪Spring的三级缓存及循环依赖

代码继续往下走
// 判断当前Bean是否需要提前曝光:单例&&循环依赖&&当前bean正在创建中,检测循环依赖
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
    if (logger.isTraceEnabled()) {
        logger.trace(“Eagerly caching bean ‘” + beanName +
        “‘ to allow for resolving potential circular references”);
    }
    
    // 为避免后期循环依赖,可以在bean初始化完成前将创建实例的ObjectFactory加入工厂
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

【闲聊杂谈】源码追踪Spring的三级缓存及循环依赖

进入addSingletonFactory方法
synchronized (this.singletonObjects) {
    // 如果单例对象的高速缓存[beanName-bean实例]中没有beanName对应的这个对象
    if (!this.singletonObjects.containsKey(beanName)) {
        // 将beanName、singletonFactory放到单例工厂的缓存[beanName-ObjectFactory]中
        this.singletonFactories.put(beanName, singletonFactory);
        // 从早去单例对象的高速缓存[beanName-bean实例]中移除beanName的相关缓存对象
        this.earlySingletonObjects.remove(beanName);
        // 将beanName添加已注册的单例集合中
        this.registeredSingletons.add(beanName);
    }
}

【闲聊杂谈】源码追踪Spring的三级缓存及循环依赖

代码继续往下走回到doCreateBean方法中
// 对bean的属性进行填充,将各个属性值注入
// 如果存在依赖其它bean的属性,则会递归初始化依赖的bean
populateBean(beanName, mbd, instanceWrapper);

进入populateBean方法中
// 应用给定的属性值,解决任何在这个bean工厂运行时其它bean的引用
// 必须使用深拷贝,所以我们不会永久的修改这个属性
applyPropertyValues(beanName, mbd, bw, pvs);

进入到applyPropertyValues方法中
for (PropertyValue pv : original) {
    …..
} else {
    // 获取属性的名字
    String propertyName = pv.getName();
    // 获取未经类型转换的值
    Object originalValue = pv.getValue();
    // AutowiredPropertyMarker.INSTANCE:自动生成标记的规范实例
    if (originalValue == AutowiredPropertyMarker.INSTANCE) {
        …..
    }
    // 交由valueResolver根据pv解析出originalValue所封装的对象
    Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
    …..
}

进入到resolveValueIfNecessary方法中
if (value instanceof RuntimeBeanReference) {
    RuntimeBeanReference ref = (RuntimeBeanReference) value;
    // 解析出对应ref所封装的Bean元信息[beanName,bean类型]的Bean对象
    return resolveReference(argName, ref);
}

进入到resolveReference方法中
resolvedName = String.valueOf(doEvaluate(ref.getBeanName()));
// 获取resolvedName的Bean对象,也就是从容器中获取
bean = this.beanFactory.getBean(resolvedName);

进入到getBean方法中,继续进入到doGetBean方法中
// 提前检查单例缓存中是否有手动注册的单例对象
// 跟循环依赖有关联
Object sharedInstance = getSingleton(beanName);

进入到getSingleton方法中,继续进入getSingleton方法中
// 从单例对象缓存中获取beanName对应的单例对象
Object singletonObject = this.singletonObjects.get(beanName);
// 如果单例对象缓存中没有,且该beanName对应的单例bean正在创建中
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
    // 从earlySingletonObjects缓存中获取单例对象
    // 获取的这个对象是通过提前曝光的ObjectFactory创建出来的
    // 还未进行属性填充等操作
    singletonObject = this.earlySingletonObjects.get(beanName);
    // 如果earlySingletonObjects缓存中也没有
    // 并且也允许创建的话
    if (singletonObject == null && allowEarlyReference) {
        …..
    }
    
    
代码继续往下走,回到doGetBean方法中
// 如果不是做类型检查,那么表示要创建bean,此处在结合中做一个记录
if (!typeCheckOnly) {
    // 为beanName标记为已经创建,或将要创建
    markBeanAsCreated(beanName);
}

代码继续往下走,来到getSingleton方法,继续进入,会发现B也将创建一个半成品对象。然后对B进行属性填充,就需要用到A对象。A对象在之前已经创建出来,有在三级缓存中的任何一级缓存中吗?显然并没有。代码继续往下走,再次来到getSingleton方法中
singletonObject == null为true
allowEarlyReference也为true
刚刚跳过的这段代码开始进入执行
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
    // 从二级缓存中取
    singletonObject = this.earlySingletonObjects.get(beanName);
    if (singletonObject == null && allowEarlyReference) {
        synchronized (this.singletonObjects) {
        // Consistent creation of early reference within full singleton lock
        // 从一级缓存中取
        singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null) {
                    // 当某些方法需要提前初始化的时候则会调用addSingletonFactory方法
                    // 将对应的ObjectFactory初始化策略存储在singletonFactories
                    // 这便是从三级缓存中取
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        // 如果存在单例对象工厂,则通过工厂创建一个单例对象
                        singletonObject = singletonFactory.getObject();
                        // 记录在缓存中,二级缓存和三级缓存的对象不能同时存在
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        // 从三级缓存中移除
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
    }
}

【闲聊杂谈】源码追踪Spring的三级缓存及循环依赖

singletonObject = singletonFactory.getObject()点F7进入就会来到addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean))这个方法中的lambda表达式() -> getEarlyBeanReference(beanName, mbd, bean)。继续往下执行,回到getSingleton方法中,会发现将半成品的A返回了

【闲聊杂谈】源码追踪Spring的三级缓存及循环依赖

代码继续往下走,回到doCreateBean方法中,就会发现B中的a属性已经被A赋值了

【闲聊杂谈】源码追踪Spring的三级缓存及循环依赖

代码继续往下走,来到getSingleton方法中
// 这个单例对象是不是新对象
if (newSingleton) {
    // 将beanName和singletonObject的映射关系添加到该工厂的单例单例缓存中
    addSingleton(beanName, singletonObject);
}

进入addSingleton方法中
synchronized (this.singletonObjects) {
    // 放到一级缓存中
    this.singletonObjects.put(beanName, singletonObject);
    // 从二级缓存中移除
    this.singletonFactories.remove(beanName);
    // 从三级缓存中移除
    this.earlySingletonObjects.remove(beanName);
    // 将beanName添加到已注册的单例集中
    this.registeredSingletons.add(beanName);
}

此时B对象已经完成,回到A对象的赋值操作中,将B赋值给A对象的a属性。那么对A继续刚刚和B一样的操作,放到一级缓存中,从二级缓存中移除,从三级缓存中移除。这就是完整的一个创建过程。

那么,从上述的创建过程中,三级缓存是怎么流转的呢?
三级缓存中,并不是严格的从三级到二级到一级。有可能在三级和一级中有对象,也有可能再一二三级中都有对象。

如何改变源码中的三级缓存引用关系,将三级缓存改为二级缓存?
1、在doCreateBean中有添加lambda表示式到三级缓存的代码,要改成直接放到二级缓存;
2、直接修改getSingleton方法,将三级缓存去掉;

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/111914.html

(0)
Java光头强的头像Java光头强

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!