《系列二》-- 8、单例bean的创建

系列,bean,创建 · 浏览次数 : 20

小编点评

## 获取单例bean的步骤解析 **1. 源码入口概述** 文章首先介绍了获取单例bean的流程,并强调该流程是流水账式的,涉及多个步骤。文章还指出,获取单例bean的具体操作取决于其作用域,主要分为单例作用域和多例作用域。 **2. `getSingleton(beanName, ObjectFactory)` 的行为总结** `getSingleton`方法主要有以下几个作用: * **获取单例对象:** 首先尝试从缓存中获取已经创建的单例对象。 * **创建新单例对象:** 如果缓存中没有,则调用`createObjectForBeanInstance`方法创建一个新的单例对象。 * **处理异常:** 当创建新单例对象过程中出现异常时,会将其记录并抛出。 * **缓存结果:** 如果创建成功,将创建的单例对象缓存到`singletonObjects`中。 * **返回最终结果:** 最后,返回从缓存中获取的单例对象。 **注意:** * `ObjectFactory` 是一个接口,`createObjectForBeanInstance`方法根据传入的参数创建单例对象。 * `singletonObjects` 是一个存放已经创建的单例对象的集合。 * `suppressedExceptions` 是一个存放异常的信息的集合。 **总结:** `getSingleton`方法通过缓存机制获取单例对象,并根据创建过程的异常处理逻辑确保其正常创建和缓存。

正文

阅读之前要注意的东西:本文就是主打流水账式的源码阅读,主导的是一个参考,主要内容需要看官自己去源码中验证。全系列文章基于 spring 源码 5.x 版本。

写在开始前的话:

阅读spring 源码实在是一件庞大的工作,不说全部内容,单就最基本核心部分包含的东西就需要很长时间去消化了:

  • beans
  • core
  • context

实际上我在博客里贴出来的还只是一部分内容,更多的内容,我放在了个人,fork自 spring 官方源码仓了; 而且对源码的学习,必须是要跟着实际代码层层递进的,不然只是干巴巴的文字味同嚼蜡。

https://gitee.com/bokerr/spring-framework-5.0.x-study

这个仓设置的公共仓,可以直接拉取。



Spring源码阅读系列--全局目录.md



1 源码入口概述

不管是何种作用域的bean 创建,最终都会指向: “如何从零开始创建bean”,而这个话题前文已经讲解过了。

本文重点介绍,单例作用域的bean 在这个过程中的动作有何异同。

img2222222.png

这里显然是定义了一个回调操作:

sharedInstance 声明的类型是 Object

Object sharedInstance;

看 sharedInstance 的定义,重点看它的第二个参数,经典匿名内部类写法。这里 createBean() 接收 了bean 相关配置信息;

当在 getSingleton(beanName, ObjectFactory) 方法内部调用 ObjectFactory 的 getObject() 方法时,这些参数将会真正登场。

sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
    public Object getObject() throws BeansException {
        try {
            // 准备完成后回调  由子类 AbstractAutowireCapableBeanFactory 实现方法
            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.
            // 出错,单例工厂销毁该 bean
            destroySingleton(beanName);
            throw ex;
        }
    }
});

接下来我们先看看 getSingleton(beanName, ObjectFactory) 干了什么:

2 getSingleton(beanName, ObjectFactory) 的行为

口头挨个介绍过于干巴巴了,下边直接贴出加了注释的代码:


/**
 * Return the (raw) singleton object registered under the given name,
 * creating and registering a new one if none registered yet.
 *
 * @param beanName         the name of the bean
 * @param singletonFactory the ObjectFactory to lazily create the singleton
 *                         with, if necessary
 * @return the registered singleton object
 */
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) { // 新创建bean,单例对象池中不存在,否则直接方法返回
            if (this.singletonsCurrentlyInDestruction) { // bean 已经被废弃,不能再加载它,抛异常退出
                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重复创建的校验,如果当前bean已经在创建流程中抛异常,众所周知单例bean必须唯一
            beforeSingletonCreation(beanName);
            boolean newSingleton = false;
            boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
            if (recordSuppressedExceptions) {
                this.suppressedExceptions = new LinkedHashSet<>();
            }
            try {
                // 校验通过后,调用 ObjectFactory 的 getObject() 进一步回调 createBean() 方法
                singletonObject = singletonFactory.getObject();
                // 有没有觉得这也太简单了?其实不然,bean 加载的重头戏还在后边: createBean() 
                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;
                }
                // bean 首次加载后再次校验,它是否是真的第一次被加载
                afterSingletonCreation(beanName);
            }
            if (newSingleton) {
                // 如果是一个新的单例bean,那么把它假如缓存中,从这里进去看到的一定会是:三级缓存
                addSingleton(beanName, singletonObject);
            }
        }
        return singletonObject;
    }
}

总结

针对单例作用域bean 的创建,这里会根据缓存进行判断,需要保证全局唯一。

  1. getSingleton() 返回 sharedInstance时,实际上最终得到的是回调: createBean()的返回结果

  2. sharedInstance 就只可能有两种类型: 普通bean 或者 FactoryBean

然后进入如下逻辑,根据 sharedInstance 提取实际的bean

bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);

与《系列二》-- 8、单例bean的创建相似的内容:

《系列二》-- 8、单例bean的创建

[TOC] > 阅读之前要注意的东西:本文就是主打流水账式的源码阅读,主导的是一个参考,主要内容需要看官自己去源码中验证。全系列文章基于 spring 源码 5.x 版本。 写在开始前的话: 阅读spring 源码实在是一件庞大的工作,不说全部内容,单就最基本核心部分包含的东西就需要很长时间去消化了

Rancher 系列文章-RHEL7.8 离线有代理条件下安装单节点 Rancher

一 基础信息 1.1 前提 本次安装的为 20220129 最新版:Rancher v2.6.3 VM 版本为 RHEL 7.8, 7.9 或 8.2, 8.3, 8.4(Rancher 官网要求) VM YUM 仓库:已配置对应版本的 RHEL 和 EPEL YUM 仓库 VM 提供 root 权

Redis 常用的数据结构简介与实例测试【Redis 系列二】

〇、都有哪些数据结构? Redis 提供了较为丰富的数据类型,常见的有五种:String(字符串),Hash(哈希),List(列表),Set(集合)、Zset(有序集合)。 随着 Redis 版本的更新,后面又支持了四种数据类型: BitMap(2.2 版新增)、HyperLogLog(2.8 版

[转帖]Django系列3-Django常用命令

文章目录 一. Django常用命令概述二. Django常用命令实例2.1 help命令2.2 version2.3 check2.4 startproject2.5 startapp2.6 runserver2.7 shell2.8 migrations2.8.1 makemigrations2

[转帖]Python基础之文件处理(二)

https://www.jianshu.com/p/7dd08066f499 Python基础文件处理 python系列文档都是基于python3 一、字符编码 在python2默认编码是ASCII, python3里默认是utf-8; unicode分为 utf-32(占4个字节),utf-16(

机器学习算法(一):1. numpy从零实现线性回归

系列文章目录 机器学习算法(一):1. numpy从零实现线性回归 机器学习算法(一):2. 线性回归之多项式回归(特征选取) @目录系列文章目录前言一、理论介绍二、代码实现1、导入库2、准备数据集3、定义预测函数(predict)4 代价(损失)函数5 计算参数梯度6 批量梯度下降7 训练8 可视

Cilium系列-8-绕过 IPTables 连接跟踪

## 系列文章 * [Cilium 系列文章](https://ewhisper.cn/tags/Cilium/) ## 前言 将 Kubernetes 的 CNI 从其他组件切换为 Cilium, 已经可以有效地提升网络的性能. 但是通过对 Cilium 不同模式的切换/功能的启用, 可以进一步提

Redis系列8:Bitmap实现亿万级数据计算

Redis系列1:深刻理解高性能Redis的本质 Redis系列2:数据持久化提高可用性 Redis系列3:高可用之主从架构 Redis系列4:高可用之Sentinel(哨兵模式) Redis系列5:深入分析Cluster 集群模式 追求性能极致:Redis6.0的多线程模型 追求性能极致:客户端缓

MQ系列8:数据存储,消息队列的高可用保障

MQ系列1:消息中间件执行原理 MQ系列2:消息中间件的技术选型 MQ系列3:RocketMQ 架构分析 MQ系列4:NameServer 原理解析 MQ系列5:RocketMQ消息的发送模式 MQ系列6:消息的消费 MQ系列7:消息通信,追求极致性能 1 介绍 在之前的章节中,我们介绍了消息的发送

< Python全景系列-8 > Python超薄感知,超强保护:异常处理的绝佳实践

欢迎来到系列第八篇,异常处理的深入探讨。本文将分五部分展开。首先,我们将学习Python异常处理的基础知识,理解`try/except`语句的用法。然后,我们将了解Python的常见异常类型并通过实例理解它们的作用。第三部分,我们将更深入地解析`try-except`块,理解其工作原理及更加复杂的用法。在第四部分,我们会介绍如何自定义异常,并讨论其应用场景。最后,我们将介绍上下文管理器在异常处理中