实战讲解及分析Spring新建Bean的几种方式以及创建过程(图+文+源码)

news2024/11/14 21:52:38

1 缘起

作为一个应用开发人员而言,会使用某一个工具分为两个层次(个人观点):
第一个层次,知道工具,会使用这个工具解决问题;
第二个层次,理解工具的实现原理。
关于Spring的学习,还在第一个层次转悠,缺少原理的研究,
随着学习的深入,开始研究些Spring源码,配合IDEA调试,
逐渐理解一些Spring原理,先从创建Bean开始,
分享如下。

2 新建Bean

对于Spring学习、使用和研究人员而言,
Bean必修课,Bean从何而来,又如何获取,
弄清楚这些,会加深对Spring的理解。
首先从创建Bean开始,常见的创建方式有3种:

  • 注解@Bean方式
  • Xml方式
  • BeanDefinitionBuilder方式

Spring提供了多种方式创建Bean,这里的创建Bean是创建自定义的Bean,
不涉及Spring启动时需要创建的系统Bean,
但是殊途同归,最终都是通过BeanDefinition构建Bean,
创建自定义的Bean会经历两个核心步骤:

  • 注册BeanDefinition
    填充beanDefinitionMap和beanDefiinitionNames,为填充singletonObjects准备
  • 创建单例Bean
    填充singletonObjects,供后续获取Bean使用

2.1 @Bean方式

通过@Bean方式创建自定义Bean是最明显的方式,
直接在对应的方法上添加@Bean注解,表明这是Bean,
结合@Configuration,Spring会自动创建Bean,
测试样例及注释如下:

package com.monkey.springboottemplate.modules.bean_definition;

import com.monkey.springboottemplate.common.entity.UserEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 注解新建Bean.
 *
 * @author xindaqi
 * @since 2022-12-14 16:01
 */
@Configuration
public class BeanDefinitionByAnnotation {
    private static final Logger logger = LoggerFactory.getLogger(BeanDefinitionByAnnotation.class);
    @Bean
    public UserEntity myUserBean() {
        return new UserEntity("xiaohua", "欧洲");
    }
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        // 注册BeanDefinition
        applicationContext.register(BeanDefinitionByAnnotation.class);
        // 加载或刷新配置(Java基础配置/XML/properties等)的持久化描述,
        // 这里关注填充singletonObjects
        applicationContext.refresh();
        logger.info(">>>>>>>>>>应用程序上下文启动");
        UserEntity user = applicationContext.getBean(UserEntity.class);
        logger.info(">>>>>>>>Bean:{}", user);
        applicationContext.close();
        logger.info(">>>>>>>>关闭应用程序上下文");
    }
}

2.2 XML方式

通过XML创建Bean,首先要构建对应的XML文件,
配置样例及注释如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 在Spring framework中启用@Resource注解 -->
    <context:annotation-config />
    <!-- 构建User Bean实例:Bean id为user1 -->
    <bean id="userA" class="com.monkey.springboottemplate.common.entity.UserEntity">
        <property name="nickname" value="xiaoxml" />
        <property name="address" value="欧洲" />
    </bean>
</beans>

XML文件只是存储Bean的持久化配置文件,
想要使该Bean加载到Spring容器,仍需要通过相关类加载该XML文件,
测试样例以及注释如下:

package com.monkey.springboottemplate.modules.bean_definition;

import com.monkey.springboottemplate.common.entity.UserEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * XML新建Bean.
 *
 * @author xindaqi
 * @since 2022-12-14 15:59
 */
public class BeanDefinitionByXml {
    private static final Logger logger = LoggerFactory.getLogger(BeanDefinitionByXml.class);
    public static void main(String[] args) {
        String beanConfig = "bean-creation.xml";
        // 通过XML配置文件构建Bean
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(beanConfig);
        UserEntity user = applicationContext.getBean("userA", UserEntity.class);
        logger.info(">>>>>>>>Creation Bean using XML, User Bean :{}", user);
    }
}

2.3 BeanDefinition

上面两种创建Bean的方式对于应用开发者而言是显式的,
而,更贴近Spring底层的方式是通过BeanDefinition创建Bean,
Spring创建Bean的第一个过程即注册BeanDefinition,就是下面的样例,
通过显式的方式注册BeanDefinition,
然后再创建单例的Bean,测试样例及注释如下:

package com.monkey.springboottemplate.modules.bean_definition;

import com.monkey.springboottemplate.common.entity.UserEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * BeanDefinition新建Bean.
 *
 * @author xindaqi
 * @since 2022-12-14 16:01
 */
public class BeanDefinitionByBuilder {
    private static final Logger logger = LoggerFactory.getLogger(BeanDefinitionByBuilder.class);
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        // 构建BeanDefinition,填充Bean属性
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(UserEntity.class);
        beanDefinitionBuilder.addPropertyValue("nickname", "xiaoxiao");
        AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
        // 注册BeanDefinition
        applicationContext.registerBeanDefinition("myBean", beanDefinition);
        // 加载或刷新配置(Java基础配置/XML/properties等)的持久化描述,
        // 这里关注填充singletonObjects
        applicationContext.refresh();
        logger.info(">>>>>>>>>>应用程序上下文启动");
        UserEntity user1 = applicationContext.getBean(UserEntity.class);
        UserEntity user2 = (UserEntity) applicationContext.getBean("myBean");
        logger.info(">>>>>>>>>>User:{}, user2:{}", user1, user2);
        applicationContext.close();
        logger.info(">>>>>>>>关闭应用程序上下文");
    }
}

3 源码分析

3.1 创建Bean流程

Spring中创建自定义Bean的核心流程如下图所示,
由图可知,创建自定义Bean分成两个部分:

  • 注册BeanDefition
  • 注册Bean

核心都是填充对应的数据结构,
最终把Bean添加到Spring构建的Bean容器中,供后续取用。
虽然干巴巴流程太流于表面,没有质感,但是,这些使用源码中抠出来的
虽然散,但是,易于理解和记忆,
后面会详细给出流程的源码及源码分析。
在这里插入图片描述

3.2 BeanDefinition相关

先从注册BeanDefinition开始分析,
BeanDefiniton,从命名即可知,是Bean的定义,用于描述Bean,
Spring将BeanDefinition设计为一个interface,面向接口,
BeanDefinition源码长这样,如下图所示,
这里仅需要知道BeanDefinition是用于描述Bean的即可,
会在其他文章详细讲解BeanDefinition。
在这里插入图片描述
为什么要注册BeanDefinition?
因为BeanDefinition用于描述Bean实例,因此,创建Bean的时候需要先注册BeanDefition,描述Bean,然后再创建Bean,两者是关联的。
注册BeanDefinition的核心是填充beanDefinitionMap和beanDefinitionNames。

  • beanDefinitionMap
    位置:org.springframework.beans.factory.support.DefaultListableBeanFactory#beanDefinitionMap
    源码如下图所示,由注释可知,beanDefinitionMap用于存储bean名称和beanDefintion,所谓的关联关系就在这里,(key, value)->(beanName, beanDefinition),为后续通过beanName获取beanDefinition做准备。
    在这里插入图片描述

  • beanDefinitionNames
    位置:org.springframework.beans.factory.support.DefaultListableBeanFactory#beanDefinitionNames
    源码如下图所示,由注释可知,beanDefintionNames存储Bean名称,为后续填充singletonObjects做准备。
    在这里插入图片描述

3.2.1 流程

前置知识已介绍,下面进入正题,讲解流程。
源码调试流程如下图所示,感兴趣的可以参照流程图中打断点调试。
在这里插入图片描述

(1)AnnotationConfigApplicationContext中注册BeanDefinition:registerBeanDefinition
(2)GenericApplicationContext中注册BeanDefinition:registerBeanDefinition
(3)DefaultListableBeanFactory中注册BeanDefinition:registerBeanDefinition
(4)DefaultListableBeanFactory中填充beanDefinitionMap:beanDefinitionMap.put
(5)DefaultListableBeanFactory中填充beanDefinitionNames:beanDefinitionNames.add

3.2.2 源码调试过程

3.2.2.1 AnnotationConfigApplicationContext中注册BeanDefinition:registerBeanDefinition

在这里插入图片描述

3.2.2.2 GenericApplicationContext中注册BeanDefinition:registerBeanDefinition

在这里插入图片描述

3.2.2.3 DefaultListableBeanFactory中注册BeanDefinition:registerBeanDefinition

在这里插入图片描述

3.2.2.4 DefaultListableBeanFactory中填充beanDefinitionMap:beanDefinitionMap.put &

3.2.2.5 DefaultListableBeanFactory中填充beanDefinitionNames:beanDefinitionNames.add

合并步骤4和5,填充对应的数据。
在这里插入图片描述

3.3 Bean相关

完成上面的准备工作,接下来就要创建Bean,
这个Bean实际单例对象,在singletonObjects中存储,为后面获取Bean做准备。
singletonObjects:
位置:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#singletonObjects
源码如下图所示,由注释可知,singletonObjects缓存单例Bean实例。
在这里插入图片描述

3.3.1 流程

下面开始讲解如何填充这个Map,
源码调试流程如下图所示,感兴趣的可以参照流程图中打断点调试。
在这里插入图片描述

(1)AnnotationConfigApplicationContext中刷新相关的Bean:refresh
(2)同步锁,AbstractApplicationContext中刷新相关Bean:refresh
(3)结束BeanFactory初始化,AbstractApplicationContext初始化的最后一步,完成准备工作之后,需要固化相关的操作:finishBeanFactoryInitialization
(4)预处理,DefaultListableBeanFactory实例化单体Bean:preInstantiateSingletons
(5)获取Bean,AbstractBeanFactory根据Bean名称获取Bean:getBean(java.lang.String)
(6)获取Bean,AbstractBeanFactory获取Bean:doGetBean
(7)获取单例Bean,DefaultSingletonBeanRegistry根据Bean名称获取单例Bean:getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory<?>)
(8)添加单例Bean,DefaultSingletonBeanRegistry添加单例Bean:addSingleton
(9)添加Bean,DefaultSingletonBeanRegistry:singletonObjects.put

3.3.2 源码调试过程

3.3.2.1 AnnotationConfigApplicationContext中刷新相关的Bean:refresh

在这里插入图片描述

3.3.2.2 同步锁,AbstractApplicationContext中刷新相关Bean:refresh

在这里插入图片描述

3.3.2.3 结束BeanFactory初始化,AbstractApplicationContext初始化的最后一步,完成准备工作之后,需要固化相关的操作:finishBeanFactoryInitialization

在这里插入图片描述

3.3.2.4 预处理,DefaultListableBeanFactory实例化单体Bean:preInstantiateSingletons

在这里插入图片描述
在这里插入图片描述

3.3.2.5 获取Bean,AbstractBeanFactory根据Bean名称获取Bean:getBean(java.lang.String)

在这里插入图片描述

在这里插入图片描述

3.3.2.6 获取Bean,AbstractBeanFactory获取Bean:doGetBean

在这里插入图片描述

在这里插入图片描述

3.3.2.7 获取单例Bean,DefaultSingletonBeanRegistry根据Bean名称获取单例Bean:getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory<?>)

在这里插入图片描述

在这里插入图片描述

3.3.2.8 添加单例Bean,DefaultSingletonBeanRegistry添加单例Bean:addSingleton

在这里插入图片描述

在这里插入图片描述

3.3.2.9 添加Bean,DefaultSingletonBeanRegistry:singletonObjects.put

在这里插入图片描述
在这里插入图片描述

3.4 获取Bean

通过上面的准备工作,填充了相关的数据,
下面可以通过应用上下文获取Bean。

3.4.1 流程

源码调试流程如下图所示,感兴趣的可以参照流程图中打断点调试。
在这里插入图片描述

(1)获取Bean,AnnotationConfigApplicationContext:getBean
(2)获取Bean,AbstractApplicationContext:getBean(java.lang.String)
(3)获取Bean,AbstractBeanFactory:getBean(java.lang.String)
(4)处理逻辑,获取Bean,AbstractBeanFactory:doGetBean
(5)从填充的singletonObjects获取Bean,DefaultSingletonBeanRegistry:getSingleton(java.lang.String)
(6)获取Bean,得到查询的Bean,DefaultSingletonBeanRegistry:getSingleton(java.lang.String, boolean)

3.4.2 源码调试

3.4.2.1 获取Bean,AnnotationConfigApplicationContext:getBean

在这里插入图片描述

3.4.2.2 获取Bean,AbstractApplicationContext:getBean(java.lang.String)

在这里插入图片描述

3.4.2.3 获取Bean,AbstractBeanFactory:getBean(java.lang.String)

在这里插入图片描述

3.4.2.4 处理逻辑,获取Bean,AbstractBeanFactory:doGetBean

在这里插入图片描述

3.4.2.5 从填充的singletonObjects获取Bean,DefaultSingletonBeanRegistry:getSingleton(java.lang.String)

在这里插入图片描述

3.4.2.6 获取Bean,得到查询的Bean,DefaultSingletonBeanRegistry:getSingleton(java.lang.String, boolean)

在这里插入图片描述
最终返回查到的Bean。
在这里插入图片描述

4 小结

(1)新建Bean的三种方式:XML、@Bean和BeanDefinition;
(2)创建Bean的核心过程:
(2.1)注册BeanDefinition,填充beanDefinitionMap和beanDefinitionNames;
(2.2)注册Bean,填充singletonObjects;
(3)通过应用上下文获取Bean,是通过singletonObjects查询获取。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/98704.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux Centos7 磁盘的分区、挂载

1、前言 注&#xff1a;看不懂的同学可以直接跟着后面的步骤操作 一块新的磁盘放到电脑上&#xff0c;要经过分区-->给分区设置文件系统--->挂载才能用。 也就是说要想将磁盘挂载&#xff0c;必须完成给磁盘分区和给分区设置文件系统这两步。 分区的时候先分成主分区和扩…

【DBN分类】基于matlab深度置信网络DBN变压器故障诊断【含Matlab源码 2284期】

一、深度置信网络DBN变压器故障诊断简介 1 DBN模型 DBN是深度学习中最关键的一个多层网络架构&#xff0c;如图2所示&#xff0c;由多层RBM堆叠而成&#xff0c;前一层RBM的输出为后一层RBM的输入&#xff0c;最顶层采用Softmax分类器作为标签层&#xff0c;输出分类识别的结果…

AD-DA转换(PCF8591)

AD转换目录一、AD转换&#xff08;PCF8591&#xff09;①初始化函数②读取ADC值的函数二、DA转换&#xff08;PCF8591&#xff09;三、STC15系列单片机用户手册.pdf—第10章一、AD转换&#xff08;PCF8591&#xff09; 思路&#xff1a;&#xff08;66&#xff0c;两个地址0x90…

RNA-seq——上游分析练习2(数据下载+trim-galore+hisat2+samtools+featureCounts)

目录软件安装新建文件夹一、下载数据二、质控过滤1.数据质量检测2.数据质量控制3.对处理后的数据再次QC三、序列比对1.hisat2比对2.flagstat检查一下结果四、featureCounts定量写在前面——本文是转录组上游分析的实战练习。主要包含四个步骤&#xff1a; 数据下载&#xff08…

DockerCompose编排Redis6.2.6以及遇到的那些坑

场景 Docker中使用Dockerfile的方式部署SpringBootVue前后端分离的项目(若依前后端分离框架为例): Docker中使用Dockerfile的方式部署SpringBootVue前后端分离的项目(若依前后端分离框架为例)_霸道流氓气质的博客-CSDN博客_若依 dockerfile 在上面使用Dockerfile分别构建每个…

Heron‘s formula

In geometry, Heron’s formula (or Hero’s formula) gives the area A of a triangle in terms of the three side lengths a, b, c. If {\textstyle s{\tfrac {1}{2}}(abc)}{\textstyle s{\tfrac {1}{2}}(abc)} is the semiperimeter of the triangle, the area is,[1] {\d…

影视中学职场套路——《如懿传》中职场生存法则

目录 一、老板决定的事&#xff0c;赞成不赞成都要执行 二、居人之下&#xff0c;聪明劲儿别往外露 三、切忌大庭广众直接与上级冲突 四、取悦所有人&#xff0c;不如取悦最大的boss 五、再强的人&#xff0c;也需要团队作战 六、人善被人欺&#xff08;首先要自保&#…

第三十一章 linux-模块的加载过程一

第三十一章 linux-模块的加载过程一 文章目录第三十一章 linux-模块的加载过程一sys_init_modulestruct moduleload_module模块ELF静态的内存视图字符串表&#xff08;string Table)HDR视图的第一次改写find_sec函数ps:kernel symbol内核符号表&#xff0c;就是在内核的内部函数…

opencv图像去畸变

图像去畸变的思路 对于目标图像(无畸变图像)上的每个像素点&#xff0c;转换到normalize平面&#xff0c;再进行畸变变换&#xff0c;进行投影&#xff0c;得到这个像素点畸变后的位置&#xff0c;然后将这个位置的源图像&#xff08;畸变图像&#xff09;的像素值作为目标图像…

Visual Studio 2022安装与编译简单c语言以及C#语言(番外)

文章目录1 软件下载网站2 下载与安装3 创建并学习C语言4 创建并学习C#语言1 软件下载网站 Visual Studio官网 2 下载与安装 1、下载社区版即可。 2、下载得到安装文件&#xff0c;右键以管理员方式运行安装文件。 3、点击继续。 4、等待下载完成。 5、这里学习C选择使用…

SpringBoot文件上传同时,接收复杂参数

目录 环境信息 问题描述 错误分析 解决方法 简单参数 总结 环境信息 Spring Boot&#xff1a;2.0.8.RELEASE Spring Boot内置的tomcat&#xff1a;tomcat-embed-core 8.5.37 问题描述 收到文件上传的开发工作&#xff0c;要求能适配各种场景&#xff0c;并且各场景的请求…

C语言——操作符详解(上)

C语言——操作符详解&#xff08;上&#xff09; 操作符的分类 C语言中的操作符主要分为算术操作符、移位操作符、位操作符、赋值操作符、单目操作符、关系操作符、逻辑操作符、条件操作符、逗号表达式、下标引用、函数调用和结构成员。我将分成三篇文章为大家详细介绍以上所…

[附源码]Python计算机毕业设计Django网约车智能接单规划小程序

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

[附源码]Python计算机毕业设计华夏商场红酒管理系统Django(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等…

AI绘画火爆,以昆仑万维AIGC为例,揭秘AI绘画背后的模型算法

AI绘画火爆&#xff0c;以昆仑万维AIGC为例&#xff0c;揭秘AI绘画背后的模型算法 一、前言 最近AI绘画让人工智能再次走进大众视野。在人工智能发展早起&#xff0c;一直认为人工智能能实现的功能非常有限。通常都是些死板的东西&#xff0c;像是下棋、问答之类的&#xff0…

mysql锁范围(一)表级锁变行级锁

文章目录行级锁1. 用两个连接connection登陆mysql2. 测试无索引情况1&#xff09;机器1开启事务&#xff0c;执行更新北京仓数据sql&#xff0c;不提交事务2&#xff09;机器2开启事务&#xff0c;先查询北京仓3&#xff09;机器2开始更新上海仓数据4&#xff09;机器1事务回滚…

【Spring Cloud】Nacos服务分级存储模型与负载均衡原理与实战

本期目录1. 服务分级模型介绍2. 服务分级模型的必要性3. 配置集群属性4. NacosRule负载均衡4.1 背景描述4.2 配置Nacos负载均衡策略4.3 根据权重负载均衡1. 服务分级模型介绍 为了提升整个系统的容灾性&#xff0c;Nacos 引入了地域 (Zone) 的概念&#xff0c;如上图中的北京、…

Reactor 和 Proactor 区别

Reactor 和 Proactor 区别 同步异步、阻塞非阻塞组合 同步 以read()函数为例&#xff0c;int n read(fd, buf. sz) 当采用同步的方式和阻塞io的方式时&#xff0c;buf就是从内核拷贝的数据&#xff0c;函数返回则可以马上知道 buf 中的数据。当采用同步的方式和非阻塞io的方式…

关于rabbitmq消息推送的小demo

目录 一.前言 1.1场景 1.2消息交换机三种形式 二.建设demo工程 2.1 依赖 2.2yml文件指定rabbitmq连接信息 2.3直连型消息链接 一.前言 1.1场景 在我们实际开发中到一个特定的时候是比如工作流到某个状态时, 我们会向某某单位发送消息, 这时就会用到我们的消息推送---ra…

javaee之Mybatis2

一、保存操作 在做这个方法之前&#xff0c;我们先把之前做的那个MybatisTest里面的每一个方法做成一个Test方法&#xff0c;也就是标注Test这个注解 这样便于我们测试接下来的每一个方法。仔细分析一下上面的代码&#xff0c;会发现&#xff0c;可重复性的地方太多。比如我们…