Spring5源码---循环依赖过程中spring读取不完整bean的最终解决方案

简介: Spring5源码---循环依赖过程中spring读取不完整bean的最终解决方案

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


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

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


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



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

如下所示:

1187916-20201113100420567-1075604066.png


首先, 有一个线程要去创建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.get(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 != null) {
                    // 设置这个属性是可访问的
                    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.get(beanName);
            }
            singletonObjects.put(beanName, instanceBean);
            // 删除二级缓存
            // 删除三级缓存
        }
        return instanceBean;
    }

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

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

加了两把锁.

 

这样, 在分析一下

1187916-20201114080805566-1685126495.png

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

 

三. 源码解决


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

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(beanName, "Bean name must not be null");
        synchronized (this.singletonObjects) {
            // 第一步: 从一级缓存中获取单例对象
            Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
                if (this.singletonsCurrentlyInDestruction) {
                    throw new BeanCreationNotAllowedException(beanName,
                            "Singleton bean creation not allowed while singletons of this factory are in destruction " +
                            "(Do not request a bean from a BeanFactory in a destroy method implementation!)");
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
                }
                // 第二步: 将bean添加到singletonsCurrentlyInCreation中, 表示bean正在创建
                beforeSingletonCreation(beanName);
                boolean newSingleton = false;
                boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
                if (recordSuppressedExceptions) {
                    this.suppressedExceptions = new LinkedHashSet<>();
                }
                try {
                    // 第三步: 这里调用getObject()钩子方法, 就会回调匿名函数, 调用singletonFactory的createBean()
                    singletonObject = singletonFactory.getObject();
                    newSingleton = true;
                }
                catch (IllegalStateException ex) {
                    // Has the singleton object implicitly appeared in the meantime ->
                    // if yes, proceed with it since the exception indicates that state.
                    singletonObject = this.singletonObjects.get(beanName);
                    if (singletonObject == null) {
                        throw ex;
                    }
                }
                catch (BeanCreationException ex) {
                    if (recordSuppressedExceptions) {
                        for (Exception suppressedException : this.suppressedExceptions) {
                            ex.addRelatedCause(suppressedException);
                        }
                    }
                    throw ex;
                }
                finally {
                    if (recordSuppressedExceptions) {
                        this.suppressedExceptions = null;
                    }
                    afterSingletonCreation(beanName);
                }
                if (newSingleton) {
                    addSingleton(beanName, singletonObject);
                }
            }
            return singletonObject;
        }
    }

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

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        // 从一级缓存中获取bean实例对象
        Object singletonObject = this.singletonObjects.get(beanName);
        /**
         * 如果在第一级的缓存中没有获取到对象, 并且singletonsCurrentlyIncreation为true,也就是这个类正在创建.
         * 标明当前是一个循环依赖.
         *
         * 这里有处理循环依赖的问题.-- 我们使用三级缓存解决循环依赖
         */
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
                /**
                 * 从二级缓存中拿bean, 二级缓存中的对象是一个早期对象
                 * 什么是早期对象?就是bean刚刚调用了构造方法, 还没有给bean的属性进行赋值, 和初始化, 这就是早期对象
                  */
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null && allowEarlyReference) {
                    /**
                     * 从三级缓存拿bean, singletonFactories是用来解决循环依赖的关键所在.
                     * 在ios后期的过程中, 当bean调用了构造方法的时候, 把早期对象包装成一个ObjectFactory对象,暴露在三级缓存中
                      */
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        /**
                         * 在这里通过暴露的ObjectFactory包装对象. 通过调用他的getObject()方法来获取对象
                         * 在这个环节中会调用getEarlyBeanReference()来进行后置处理
                         */
                        singletonObject = singletonFactory.getObject();
                        // 把早期对象放置在二级缓存中
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        // 删除三级缓存
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }


相关文章
|
22天前
|
人工智能 监控 安全
智慧工地解决方案,Spring Cloud智慧工地源代码
智慧工地平台针对建筑工地人员管理难、机械设备繁多、用电安全及施工环境复杂等问题,通过集成应用和硬件设备,实现数据互联互通与集中展示。基于微服务架构(Java+Spring Cloud+UniApp+MySql),平台支持PC端、手机端、平板端、大屏端管理,涵盖人员实名制、工资考勤、视频AI监控、绿色施工、危大工程监测、物料管理和安全质量管理等功能,助力施工现场的数字化、智能化综合管理,提升效率与安全性。
53 15
|
3月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
128 2
|
2天前
|
网络协议 Java Shell
java spring 项目若依框架启动失败,启动不了服务提示端口8080占用escription: Web server failed to start. Port 8080 was already in use. Action: Identify and stop the process that’s listening on port 8080 or configure this application to listen on another port-优雅草卓伊凡解决方案
java spring 项目若依框架启动失败,启动不了服务提示端口8080占用escription: Web server failed to start. Port 8080 was already in use. Action: Identify and stop the process that’s listening on port 8080 or configure this application to listen on another port-优雅草卓伊凡解决方案
21 7
|
1月前
|
监控 JavaScript 数据可视化
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
|
2月前
|
Java Nacos Sentinel
Spring Cloud Alibaba:一站式微服务解决方案
Spring Cloud Alibaba(简称SCA) 是一个基于 Spring Cloud 构建的开源微服务框架,专为解决分布式系统中的服务治理、配置管理、服务发现、消息总线等问题而设计。
403 13
Spring Cloud Alibaba:一站式微服务解决方案
|
2月前
|
运维 监控 Java
为何内存不够用?微服务改造启动多个Spring Boot的陷阱与解决方案
本文记录并复盘了生产环境中Spring Boot应用内存占用过高的问题及解决过程。系统上线初期运行正常,但随着业务量上升,多个Spring Boot应用共占用了64G内存中的大部分,导致应用假死。通过jps和jmap工具排查发现,原因是运维人员未设置JVM参数,导致默认配置下每个应用占用近12G内存。最终通过调整JVM参数、优化堆内存大小等措施解决了问题。建议在生产环境中合理设置JVM参数,避免资源浪费和性能问题。
123 3
|
2月前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
72 2
|
3月前
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
90 9
|
2月前
|
安全 Java API
实现跨域请求:Spring Boot后端的解决方案
本文介绍了在Spring Boot中处理跨域请求的三种方法:使用`@CrossOrigin`注解、全局配置以及自定义过滤器。每种方法都适用于不同的场景和需求,帮助开发者灵活地解决跨域问题,确保前后端交互顺畅与安全。
112 0
|
22天前
|
XML Java 应用服务中间件
Spring Boot 两种部署到服务器的方式
本文介绍了Spring Boot项目的两种部署方式:jar包和war包。Jar包方式使用内置Tomcat,只需配置JDK 1.8及以上环境,通过`nohup java -jar`命令后台运行,并开放服务器端口即可访问。War包则需将项目打包后放入外部Tomcat的webapps目录,修改启动类继承`SpringBootServletInitializer`并调整pom.xml中的打包类型为war,最后启动Tomcat访问应用。两者各有优劣,jar包更简单便捷,而war包适合传统部署场景。需要注意的是,war包部署时,内置Tomcat的端口配置不会生效。
181 17
Spring Boot 两种部署到服务器的方式