3.3 Spring5源码---循环依赖过程中spring读取不完整bean的最终解决方案 3.1 spring5源码系列--循环依赖 之 手写代码模拟spring循环依赖3.2spring源码系列----循环依赖源码分析

根据之前解析的循环依赖的源码,分析了一级缓存,二级缓存,三级缓存的作用以及如何解决循环依赖的. 然而在多线程的情况下,Spring在创建bean的过程中,可能会读取到不完整的bean. 下面,我们就来研究两点:

1. 为什么会读取到不完整的bean.

2. 如何解决读取到不完整bean的问题.

 

和本文相关的spring循环依赖的前两篇博文如下: 

3.1 spring5源码系列--循环依赖 之 手写代码模拟spring循环依赖

3.2spring源码系列----循环依赖源码分析

一. 为什么会读取到不完整的bean.

我们知道,如果spring容器已经加载完了,那么肯定所有bean都是完整的了,但如果,spring没有加载完,在加载的过程中,构建bean就有可能出现不完整bean的情况

如下所示: 

首先,有一个线程要去创建A类,调用getBean(A),他会怎么做呢?

第一步: 调用getSingleton()方法,去缓存中取数据,我们发现缓存中啥都没有,肯定返回null. 

第二步: 将其放入到正在创建集合中,标记当前bean A正在创建

第三步: 实例化bean

第四步: 将bean放到三级缓存中. 定义一个函数接口,方便后面调用

addSingletonFactory(beanName,() -> getEarlyBeanReference(beanName,mbd,bean));

第四步: 属性赋值. 在属性赋值的时候,返现要加载类B,就在这个时候,另一个线程也进来了,要创建Bean A.

第五步: 线程2 创建bean,也是先去调用getSinglton()从缓存中取,一二级换粗中都没有,但是三级缓存中却是有的. 于是就调用动态代理,去创建bean,很显然这时候创建的bean是不完整的. 然后将其放入到二级缓存中,二级缓存里的bean也是不完整的. 这就导致了后面是用的bean可能都是不完整的. 详细的分析上图

 

二. 如何解决读取到不完整bean的问题.

其实,之所以出现这样的问题,原因就在于,第一个bean还没有被创建完,第二个bean就开始了. 这是典型的并发问题. 

针对这个问题,其实,我们加锁就可以了.  

 用自己手写的代码为例

第一: 将整个创建过程加一把锁

/**
     * 获取bean,根据beanName获取
     */
    public static Object getBean(String beanName) throws Exception {

        // 增加一个出口. 判断实体类是否已经被加载过了
        Object singleton = getSingleton(beanName);
        if (singleton != null) {
            return singleton;
        }
        Object instanceBean;
        synchronized (singletonObjects) {

             标记bean正在创建
            if (!singletonsCurrectlyInCreation.contains(beanName)) {
                singletonsCurrectlyInCreation.add(beanName);
            }

            *
             * 第一步: 实例化
             * 我们这里是模拟,采用反射的方式进行实例化. 调用的也是最简单的无参构造函数
             */
            RootBeanDefinition beanDefinition = (RootBeanDefinition) beanDefinitionMap.get(beanName);
            Class<?> beanClass = beanDefinition.getBeanClass();
             调用无参的构造函数进行实例化
            instanceBean = beanClass.newInstance();


            *
             * 第二步: 放入到三级缓存
             * 每一次createBean都会将其放入到三级缓存中. getObject是一个钩子方法. 在这里不会被调用.
             * 什么时候被调用呢?
             * 在getSingleton()从三级缓存中取数据,调用创建动态代理的时候
             
            singletonFactories.put(beanName,new ObjectFactory() {
                @Override
                public Object getObject() throws BeansException {
                    return new JdkProxyBeanPostProcessor().getEarlyBeanReference(earlySingletonObjects.(beanName),beanName);
                }
            });
            earlySingletonObjects.put(beanName,instanceBean);

            *
             *  第三步: 属性赋值
             *  instanceA这类类里面有一个属性,InstanceB. 所以,先拿到 instanceB,然后在判断属性头上有没有Autowired注解.
             *  注意: 这里我们只是判断有没有Autowired注解. spring中还会判断有没有@Resource注解. @Resource注解还有两种方式,一种是name,一种是type
             
            Field[] declaredFields = beanClass.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                 判断每一个属性是否有@Autowired注解
                Autowired annotation = declaredField.getAnnotation(Autowired.class);
                if (annotation != ) {
                     设置这个属性是可访问的
                    declaredField.setAccessible(true);
                     那么这个时候还要构建这个属性的bean.
                    
                     * 获取属性的名字
                     * 真实情况,spring这里会判断,是根据名字,还是类型,还是构造函数来获取类.
                     * 我们这里模拟,所以简单一些,直接根据名字获取.
                     
                    String name = declaredField.getName();

                    *
                     * 这样,在这里我们就拿到了 instanceB 的 bean
                     
                    Object fileObject = getBean(name);

                     为属性设置类型
                    declaredField.set(instanceBean,fileObject);
                }
            }


            *
             * 第四步: 初始化
             * 初始化就是设置类的init-method.这个可以设置也可以不设置. 我们这里就不设置了
             */


            *
             * 第五步: 放入到一级缓存
             *
             * 在这里二级缓存存的是动态代理,那么一级缓存肯定也要存动态代理的实例.
             * 从二级缓存中取出实例,放入到一级缓存中
             */
            if (earlySingletonObjects.containsKey(beanName)) {
                instanceBean = earlySingletonObjects.(beanName);
            }
            singletonObjects.put(beanName,instanceBean);

             删除二级缓存

             删除三级缓存
        }
         instanceBean;
    }

 

然后在从缓存取数据的getSingleton()上也加一把锁

private  Object getSingleton(String beanName) {
        先去一级缓存里拿,
        Object bean = singletonObjects.(beanName);
         一级缓存中没有,但是正在创建的bean标识中有,说明是循环依赖
        if (bean == null && singletonsCurrectlyInCreation.contains(beanName)) {
            synchronized (singletonObjects) {
                bean = earlySingletonObjects.(beanName);
                 如果二级缓存中没有,就从三级缓存中拿
                 从三级缓存中取
                    ObjectFactory objectFactory = singletonFactories.(beanName);
                    if (objectFactory != ) {
                         这里是真正创建动态代理的地方.
                        bean = objectFactory.getObject();
                         然后将其放入到二级缓存中. 因为如果有多次依赖,就去二级缓存中判断. 已经有了就不在再次创建了
                        earlySingletonObjects.put(beanName,bean);
                    }
                }
            }
        }
         bean;
    }

加了两把锁.

 

这样,在分析一下

 

如上图,线程B执行到getSingleton()的时候,从一级缓存中取没有,到二级缓存的时候就加锁了,他要等待直到线程A完成执行完才能进入. 这样就避免出现不完整bean的情况. 

 

三. 源码解决

在创建实例bean的时候,加了一把锁,锁是一级缓存.

 1 public Object getSingleton(String beanName,ObjectFactory<?> singletonFactory) {
 2         Assert.notNull(beanName,"Bean name must not be null");
 3         synchronized (this.singletonObjects) {
 4              第一步: 从一级缓存中获取单例对象
 5             Object singletonObject = this.singletonObjects.(beanName);
 6             if (singletonObject == ) {
 7                 if (this.singletonsCurrentlyInDestruction) {
 8                     throw  BeanCreationNotAllowedException(beanName,9                             Singleton bean creation not allowed while singletons of this factory are in destruction " +
10                             (Do not request a bean from a BeanFactory in a destroy method implementation!));
11                 }
12                  (logger.isDebugEnabled()) {
13                     logger.debug(Creating shared instance of singleton bean '" + beanName + '1415                  第二步: 将bean添加到singletonsCurrentlyInCreation中,表示bean正在创建
16                 beforeSingletonCreation(beanName);
17                 boolean newSingleton = false;
18                 boolean recordSuppressedExceptions = (this.suppressedExceptions == 19                  (recordSuppressedExceptions) {
20                     this.suppressedExceptions = new LinkedHashSet<>();
2122                 try {
23                      第三步: 这里调用getObject()钩子方法,就会回调匿名函数,调用singletonFactory的createBean()
24                     singletonObject = singletonFactory.getObject();
25                     newSingleton = 2627                 catch (IllegalStateException ex) {
28                      Has the singleton object implicitly appeared in the meantime ->
29                      if yes,proceed with it since the exception indicates that state.
30                     singletonObject = (beanName);
31                     ) {
32                         throw ex;
33                     }
3435                  (BeanCreationException ex) {
36                     37                         for (Exception suppressedException : .suppressedExceptions) {
38                             ex.addRelatedCause(suppressedException);
39                         }
4041                     4243                 finally44                     45                         4647                     afterSingletonCreation(beanName);
4849                  (newSingleton) {
50                     addSingleton(beanName,singletonObject);
5152             }
53              singletonObject;
54         }
55     }

 

再从缓存中取数据的时候,也加了一把锁,和我们的demo逻辑是一样的. 锁也是一级缓存.

protected Object getSingleton(String beanName,boolean allowEarlyReference) {
         从一级缓存中获取bean实例对象
        Object singletonObject = *
         * 如果在第一级的缓存中没有获取到对象,并且singletonsCurrentlyIncreation为true,也就是这个类正在创建.
         * 标明当前是一个循环依赖.
         *
         * 这里有处理循环依赖的问题.-- 我们使用三级缓存解决循环依赖
         */
         isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
                *
                 * 从二级缓存中拿bean,二级缓存中的对象是一个早期对象
                 * 什么是早期对象?就是bean刚刚调用了构造方法,还没有给bean的属性进行赋值,和初始化,这就是早期对象
                  

                singletonObject = this.earlySingletonObjects. allowEarlyReference) {
                    *
                     * 从三级缓存拿bean,singletonFactories是用来解决循环依赖的关键所在.
                     * 在ios后期的过程中,当bean调用了构造方法的时候,把早期对象包装成一个ObjectFactory对象,暴露在三级缓存中
                      
                    ObjectFactory<?> singletonFactory = this.singletonFactories.if (singletonFactory != *
                         * 在这里通过暴露的ObjectFactory包装对象. 通过调用他的getObject()方法来获取对象
                         * 在这个环节中会调用getEarlyBeanReference()来进行后置处理
                         
                        singletonObject = singletonFactory.getObject();
                         把早期对象放置在二级缓存中
                        .earlySingletonObjects.put(beanName,singletonObject);
                         删除三级缓存
                        .singletonFactories.remove(beanName);
                    }
                }
            }
         singletonObject;
    }

 

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

相关推荐


这篇文章主要介绍了spring的事务传播属性REQUIRED_NESTED的原理介绍,具有一定借鉴价值,需要的朋友可以参考下。下面就和我一起来看看吧。传统事务中回滚点的使...
今天小编给大家分享的是一文解析spring中事务的传播机制,相信很多人都不太了解,为了让大家更加了解,所以给大家总结了以下内容,一起往下看吧。一定会有所收获...
这篇文章主要介绍了SpringCloudAlibaba和SpringCloud有什么区别,具有一定借鉴价值,需要的朋友可以参考下。下面就和我一起来看看吧。Spring Cloud Netfli...
本篇文章和大家了解一下SpringCloud整合XXL-Job的几个步骤。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。第一步:整合pom文件,在S...
本篇文章和大家了解一下Spring延迟初始化会遇到什么问题。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。List 坑列表 = new ArrayList(2);...
这篇文章主要介绍了怎么使用Spring提供的不同缓存注解实现缓存的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇...
本篇内容主要讲解“Spring中的@Autowired和@Resource注解怎么使用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学...
今天小编给大家分享一下SpringSecurity怎么定义多个过滤器链的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家
这篇文章主要介绍“Spring的@Conditional注解怎么使用”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Spring的@Con...
这篇文章主要介绍了SpringCloudGateway的熔断限流怎么配置的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇SpringCloud&nb...
今天小编给大家分享一下怎么使用Spring解决循环依赖问题的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考
这篇文章主要介绍“Spring事务及传播机制的原理及应用方法是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Sp...
这篇“SpringCloudAlibaba框架实例应用分析”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价
本篇内容主要讲解“SpringBoot中怎么使用SpringMVC”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习...
这篇文章主要介绍“SpringMVC适配器模式作用范围是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“SpringMVC
这篇“导入SpringCloud依赖失败如何解决”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家...
这篇文章主要讲解了“SpringMVC核心DispatcherServlet处理流程是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来
今天小编给大家分享一下SpringMVCHttpMessageConverter消息转换器怎么使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以...
这篇文章主要介绍“Spring框架实现依赖注入的原理是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Spring框架...
本篇内容介绍了“Spring单元测试控制Bean注入的方法是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下