Spring容器启动的过程(main)

大体流程如下
在这里插入图片描述

1、初始化

首先,Spring会通过用户提供的配置信息(例如XML文件或者注解)来初始化一个BeanFactory,这个BeanFactory是Spring容器的核心,它负责创建和管理所有的Bean。

2、读取配置生成并注册BeanDefinition

Spring读取散落于程序代码各处的注解、及保存在磁盘上的xml或者其他文件的配置元信息,在内存中总要以一种对象的形式表示,而Spring选择在内存中表示这些配置元信息的方式就是BeanDefination,也就是配置元信息被加载到内存之后是以BeanDefination的形存在的。然后BeanDefination被注册到BeanDefinitionRegistry中,这个过程通常是通过BeanDefinitionReader实现的。BeanDefinition描述了一个bean实例,该实例它具有属性值, 构造函数参数值,以及提供的进一步信息的具体的实现,Spring是根据beanDefinition来创建Spring bean的,结构如下所示。
在这里插入图片描述
上图就是beanDefinition所包含的主要内容

  • lazy:是否是懒加载
  • scope注解来标识这个bean其作用域,常用的是singleton和prototype
  • beanClass,类的class对象
  • isPrimary,是否被优先加载的
  • factoryBeanName:工厂名
  • factoryMethodName:工厂方法
  • initMethodName,初始化方法
  • destoryMethodName,销毁化方法

BeanDefinationReader

Spring是如何看懂这些配置元信息的呢?这个就要靠我们的BeanDefinationReader了。不同的BeanDefinationReader拥有不同的功能

  • 读取xml配置元信息,那么可以使用XmlBeanDefinationReader。
  • 要读取properties配置文件,那么可以使用PropertiesBeanDefinitionReader。
  • 读取注解配置元信息,那么可以使用 AnnotatedBeanDefinitionReader加载。

我们也可以很方便的自定义BeanDefinationReader来自己控制配置元信息的加载。总的来说,BeanDefinationReader的作用就是加载配置元信息,并将其转化为内存形式的BeanDefination,存在某一个地方,这个地方就是BeanDefinationRegistry

BeanDefinationRegistry

执行到这里,Spring已经将存在于各处的配置元信息加载到内存,并转化为BeanDefination的形式,这样我们需要创建某一个对象实例的时候,找到相应的BeanDefination然后创建对象即可。那么我们需要某一个对象的时候,去哪里找到对应的BeanDefination呢?这种通过Bean定义的id找到对象的BeanDefination的对应关系或者说映射关系又是如何保存的呢?这就引出了BeanDefinationRegistry了。

BeanDefinationRegistry是一个接口规范,本质就是提供注册及保存BeanDefination的规范,以他的子类实现DefaultListableBeanFactory为例

Spring通过BeanDefinationReader将配置元信息(xml类型为例)加载到内存生成相应的BeanDefination之后,本质是将其注册到DefaultListableBeanFactory中的一个ConcurrentHashMap类型的属性beanDefinitionMap中,如下

private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);

配置元信息:

Spring IOC容器将对象实例的创建与对象实例的使用分离,当业务中需要依赖某个对象,不再依靠我们自己手动创建,只需向Spring要,Spring就会以注入的方式交给我们需要的依赖对象。既然将对象创建的任务交给了Spring,那么Spring就需要知道创建一个对象所需要的一些必要的信息。而这些必要的信息的主要形式有

  • xml文件配置
 <!-- property元素是定义类的属性,name属性定义的是属性名称 value是值,相当于:Hello hello=new Role();role.setName("Spring");role.setA(a);--><bean id="hello" class="com.exemple.demo.Hello" scope="prototype"><property name="name" value="Spring"/><property name="a" ref="a"/></bean><bean name="a" class="com.exemple.demo.test.A">
<!--        <property name="b" ref="b"></property>--></bean>
  • @bean注解配置

ClassPathBeanDefinitionScanner

扫描类路径下的候选组件,并注册到容器中,注册的过程本质就是调用BeanDefinitionParser,分为类路径下解析器类和配置类解析器类,分别注册类路径和配置文件下的的组件注册

4、实例化 BeanFactory

根据 BeanDefinitionRegistry 中的 BeanDefinition 信息,Spring 实例化一个 BeanFactory,通常是 DefaultListableBeanFactory。BeanFactory 负责管理 Bean 的创建和依赖注入。

5、容器初始化后处理BeanFactoryPostProcessor 处理

BeanFactoryPostProcessor是容器启动阶段Spring提供的一个扩展点,在Spring已经把相关的bean信息注册到注册到beanDefinationRegistry后,再次对beanDefination进行一定程度上的修改与替换,或者自定义bean

BeanDefinitionRegistryPostProcessor

是作为BeanFactoryPostProcessor的子节口,所有的BeanDefinition加载完成之后,Bean真正被实例化之前,可以通过实现BeanDefinitionRegistryPostProcessor接口,对BeanDefinition再做一些定制化的操作,比如修改某个bean的BeanDefinition的属性、手动注册一些复杂的Bean。

自定义并注册bean

  • 修改自动注入类型

ConfigurationClassPostProcessor

  • 解析配置类注解@Configuration生成benaDefition

  • 解析@Import

  • 回调 ImportBeanDefinitionRegistrar的registerBeanDefinitions方法,注册BeanDefinition

  • mapper扫描注册beanDefition MapperScannerConfigurer核心就是给@MapperScan扫描路径下的mapper接口的beanDefition的beanClass属性设置为MapperFactoryBean.class,并给FactoryBean设置

AspectJAutoProxyBeanDefinitionParser

注册AnnotationAwareAspectJAutoProxyCreator,这是一个beanPostProcessor

修改beanDefition的beanClass属性为单例模式

解析配置文件生成benaDefition

解析配置文件,讲替换$占位符为配置文件中的真实的数据,我们的配置元信息中有些可能会修改的配置信息散落到各处,不够灵活,修改相应配置的时候比较麻烦,这时我们可以使用占位符的方式来配置。例如配置Jdbc的DataSource连接的时候可以这样配置

<context:property-placeholder location="classpath:druid.properties" /><!-- 配置druid数据源 -->
<bean name="pooledDataSource" class="com.alibaba.druid.pool.DruidDataSource"init-method="init" destroy-method="close" ><!-- 数据库连接基础信息 --><property name="url" value="${druid.url}" /><property name="driverClassName" value="${druid.url}"/><property name="username" value="${druid.username}" /><property name="password" value="${druid.password}" />
</bean>

BeanFactoryPostProcessor就会对注册到BeanDefinationRegistry中的BeanDefination做最后的修改,替换$占位符为配置文件中的真实的数据。

这里实际工作的是PropertySourcesPlaceholderConfigurer,我们配置的property-placeholder也会以BeanDefination的形式被注册BeanDefinationRegistry中,对应组件PropertySourcesPlaceholderConfigurer,然后再执行容器初试化后置处理的过程中,首先遍历BeanDefinationRegistry,从中取出BeanDefinitionRegistryPostProcessor类型的BeanDefination,即PropertySourcesPlaceholderConfigurer,然后通过这个组件来实际完成对druidDataSource组件对应的BeanDefination进线修改,通过执行PropertySourcesPlaceholderConfigurer的processProperties方法,这个核心方法的作用是访问每个BeanDefinationRegistry中的BeanDefination,然后用给的的属性值替换属性值占位符${…}

至此,整个容器启动阶段就算完成了,容器的启动阶段的最终产物就是注册到BeanDefinationRegistry中的一个个BeanDefination了,这就是Spring为Bean实例化所做的预热的工作

6、实例化单例Bean:

Spring会实例化所有的单例Bean(如果配置为懒加载,则在第一次获取Bean时才会实例化)。这个过程包括实例化Bean、设置Bean的属性、调用Bean的初始化方法等步骤

创建阶段

对象的创建权交由Spring管理了,不再是我们手动new了,这也是IOC的概念,Spring绝大多数情况会通过推断出构造器,进而做反射来创建对象的

4、执行容器启动扩展点

Spring会执行所有注册的BeanFactoryPostProcessor。这些PostProcessor可以对BeanFactory进行自定义修改,例如修改BeanDefinition、添加新的Bean等

Spring是怎么创建对象的呢?绝大多数情况下,Spring是通过反射来创建对象的,不过如果我们提供了Supplier或者工厂方法,Spring也会直接使用我们提供的创建方式

5、Bean相关

实例化bean

主要方式是通过工厂方法,Supplier、反射,FactoryBean,Spring创建bean的方式大概有12种,但本质都是通过这些方式,主要就是用反射

推断构造器

使用反射创建bean,需要首先推断构造器,策略如下

  • 没有重写过构造器,默认使用无参构造器
  • 只重写了一个构造器,那么默认使用这个构造器
  • 重写了多个构造器,优先看是否有被@AutoWired注解标识的构造器,有的话优先使用这个,其次看是否存在重写后的无参构造器,有的话使用这个,否则会报错NoSuchMethodException

依赖注入

在这里插入图片描述
首先看是否支持集合注入

  • 集合属性,优先按PrioriyOrder排序,其次Ordered,最后@Order
  • 非集合属性,首先看是否有@Qualifier的话,会根据@Qualifier的value值作为beanName去查找,差找不到会报错,然后该注解会通过beanType做查找,看找到bean的个数
    • 如果找到一个,返回这个bean
    • 没有找到,且Autowired注解的required为false,返回null,required为true,报错
    • 找到多个,优先选择被@Primary注解标识的,没有@Primary的话看注解的@Priority,选择优先级高的,@Priority也没有的话,将当前的属性的名作为beanName去容器中查找,依然没有找到的话就报错

解析依赖注入相关的方法与属性

AutowiredAnnotationBeanPostProcessor,首先查找所有被@AutoWired注解标识的方法和属性(包括对象属性和集合属性),封装为InjectionMetadata集合,如果是集合属性,会根据PriorityOrdered,Ordered,@Order确定注入集合属性的优先级,CommonAnnotationBeanPostProcessor用于处理@Resource

查找注入属性

填充属性

populateBean方法,通过InstantiationAwareBeanPostProcessor做属性填充

bean组件前置处理BeanPostProcessor

BeanPostProcessor是Bean级处理器,有两个方法

前置处理postProcessBeforeInitialization

对 Bean 的属性进行自定义注入,AOP 预处理等操作

AOP 预处理
AnnotationAwareAspectJAutoProxyCreator,首先根据@Aspect获取切面类、切面类中的所有通知方法,存储在Advisors列表中,获取切点,判断当前bean是否满足切点,满足切点的bean会被生成代理类对象,并设置切入点方法对象,创建代理对象主要是是jdk动态代理和cglib动态代理,Spring中默认使用cglib生成动态代理类对象

事务
InfrastructureAdvisorAutoProxyCreator
匹配逻辑为判断该Bean的类上是否存在@Transactional注解,或者类中的某个方法上是否存在@Transactional注解,如果存在则表示该Bean需要进行动态代理产生一个代理对象作为Bean对象

后置处理postProcessAfterInitialization

AnnotationAwareAspectJAutoProxyCreator验证bean属性的完整性,定制初始化功能,资源回收,生成AOP代理对象

生成AOP代理对象

自定义BeanPostProcessor

7、调用bean阶段

aop、事务,获取到的是代理类的bean对象
普通单实例对象是从一级缓存中获取到对象,如果获取不到,那就开始实例化、依赖注入,初始化、存入缓存、被调用…
原型模式,每次被调用都创建一个新的bean对象

销毁

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

开源一套金融大模型插件(ChatGPT)

shares vscode 插件A 股量化交易系统自研金融大模型&#xff0c;复利Chat 源码地址&#xff1a; https://github.com/xxjwxc/shares

面试题:Rabbitmq怎么保证消息的可靠性?

1.消费端消息可靠性保证&#xff1a; 消息确认&#xff08;Acknowledgements&#xff09;&#xff1a;(自动(默认),手动) 消费者在接收到消息后&#xff0c;默认情况下RabbitMQ会自动确认消息&#xff08;autoAcktrue&#xff09;。为保证消息可靠性&#xff0c;可以设置auto…

CentOS 7设置静态IP地址的详细指南

CentOS 7设置静态IP地址的详细指南 配置静态IP地址是服务器或虚拟机管理的重要步骤之一&#xff0c;特别是在需要稳定、可预测的网络环境时。本文将详细介绍如何在CentOS 7上设置静态IP地址&#xff0c;帮助确保你的系统网络配置符合需求。 1. 查看当前网络配置 在进行任何更…

文件长度超出芯片容量, 超出部份将被忽略!ch341a编程器报错解决方法

出现这个错误提示&#xff0c;说明你正在刷的是华硕主板的cap格式BIOS文件。 编程器不支持这种文件&#xff0c;需要转换成编程器专用版本BIOS文件。 华硕cap格式BIOS转编程器bios文件&#xff0c;转换工具下载地址&#xff1a;https://download.csdn.net/download/baiseled/88…

再见Figma!!新的设计,代码协作神器!【送源码】

软件介绍 Penpot 是一款专门用来帮助设计师和开发者更好地合作的软件。它可以让设计师轻松地做出漂亮的设计稿&#xff0c;还能让这些设计稿变成真正的网站或者应用的一部分。这样&#xff0c;设计师和开发者之间就不会因为沟通不畅而产生麻烦了。 Penpot 专为设计师与开发者之…

在docker中进行日志切割

先在Linux中安装docker&#xff0c;然后在docker中安装appnode面板&#xff0c;并进行docker网络端口映射。接着进入docker&#xff0c;进行nginx日志切割。 安装docker 第一步&#xff0c;卸载旧版本docker。 若系统中已安装旧版本docker&#xff0c;则需要卸载旧版本docke…

书生大模型实战营-基础关-XTuner 微调个人小助手认知

XTuner 微调个人小助手认知 环境配置模型效果预览微调数据准备微调配置微调训练权重格式转换模型合并页面对话 环境配置 # 创建虚拟环境 conda create -n xtuner0812 python3.10 -y# 激活虚拟环境&#xff08;注意&#xff1a;后续的所有操作都需要在这个虚拟环境中进行&#…

Docker搭建Minio容器

Docker搭建Minio容器 前言 在上一集我们介绍了分布式文件存储行业解决方案以及技术选型。最终我们决定选用Minio作为分布式文件存储。 那么这集我们就在Docker上搭建Minio容器即可。 Docker搭建Minio容器步骤 创建Minio文件目录 我们选择创建/minio/data目录 修改目录权…

40.x86游戏实战-找出XXX遍历周围的类型

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 工具下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1rEEJnt85npn7N38Ai0_F2Q?pwd6tw3 提…

【C#】中IndexOf的用法

在 C# 中&#xff0c;IndexOf 方法是字符串和列表&#xff08;如 List<T>&#xff09;等数据结构中常用的方法&#xff0c;用于查找指定元素或子串首次出现的位置。以下是针对不同情况使用 IndexOf 的示例。 对于字符串 对于字符串类型&#xff0c;IndexOf 方法返回子字…

scanpy切换显示颜色总结

函数实现 import scanpy as sc adata sc.datasets.pbmc68k_reduced() print(adata) sc.pl.umap(adata,color["bulk_labels"])def change_show_color(adata,label,category_listNone,color_listNone):for i in range(len(color_list)):if(len(color_list[i])7):colo…

【人工智能】Transformers之Pipeline(九):物体检测(object-detection)

目录​​​​​​​ 一、引言 二、物体检测&#xff08;object-detection&#xff09; 2.1 概述 2.2 技术原理 2.3 应用场景 2.4 pipeline参数 2.4.1 pipeline对象实例化参数 2.4.2 pipeline对象使用参数 2.4 pipeline实战 2.5 模型排名 三、总结 一、引言 pipel…

Chromium编译指南2024 - Android篇:前置要求(一)

1.引言 欢迎阅读《Chromium编译指南2024 - Android篇》。本指南旨在帮助开发者理解和掌握在Android平台上编译Chromium的全过程。Chromium是一个开源的浏览器项目&#xff0c;由Google主导开发&#xff0c;并为多个现代浏览器提供基础代码。Android作为全球使用最广泛的移动操…

[Meachines] [Medium] Magic SQLI+文件上传+跳关TRP00F权限提升+环境变量劫持权限提升

信息收集 IP AddressOpening Ports10.10.10.185TCP:22,80 $ nmap -p- 10.10.10.185 --min-rate 1000 -sC -sV PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 2048 06:d4:89:bf:51:…

redis面试(九)锁重入和互斥

可重入 1&#xff09;如果一开始这个锁是没有的&#xff0c;第一次来加锁&#xff0c;这段lua脚本会如何执行&#xff1f; "if (redis.call(‘exists’, KEYS[1]) 0) then " "redis.call(‘hset’, KEYS[1], ARGV[2], 1); " "redis.call(‘pexpi…

[0CTF 2016]piapiapia1

打开题目 看到登录口 字符串绕过长度限制strlen($_POST[nickname]) > 10

C语言中的结构体和位移段

在C语言中&#xff0c;结构体&#xff08;struct&#xff09;是一种用户自定义的数据类型&#xff0c;允许我们将不同类型的变量组合在一起&#xff0c;形成一个复合数据类型。结构体可以包含整型、浮点型、字符型等多种数据类型的成员。例如&#xff0c;我们可以定义一个表示人…

进程的退出函数及线程

函数wait (1)获取子进程退出状态 &#xff08;2&#xff09;回收资源销毁僵尸态子进程 #include <sys/types.h> #include <wait.h> int wait(int *status) 函数功能是&#xff1a;父进程一旦调用了wait就立即阻塞自己&#xff0c;由wait分析是否当前进程的某…

新160个crackme - 030-Acid Bytes.4

运行分析 需要破解Name和Serial PE分析 upx壳&#xff0c;32位 linux系统upx -d 脱壳 脱壳后发现是Delphi程序 静态分析&动态调试 ida搜索字符串&#xff0c;找到Your Name must be at least 6 Chars long !&#xff0c;双击进入 发现地址为红色&#xff0c;即函数未定义 选…

数据结构:顺序二叉树(堆)

目录 前言 一、堆的实现 1.1 头文件 1.2 堆的初始化及销毁 1.3 堆的插入 1.4 堆的删除 1.5 取堆顶数据和判空 前言 前面我们讲了二叉树有顺序结构和链式结构&#xff0c;今天就来讲一下顺序结构 普通的二叉树是不适合用数组来存储的&#xff0c;因为可能会存在大量的空间…