Spring容器启动的过程(main)

news2024/9/21 10:43:32

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

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.coloradmin.cn/o/2037468.html

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

相关文章

单点Redis中面临哪些问题

我的后端学习大纲 我的Redis学习大纲 1.面试&#xff1a;请说下在单点Redis中面临哪些问题&#xff1a; 1.1.单点Redis的问题&#xff1a; 1.数据丢失问题&#xff1a;Redis是内存存储&#xff0c;服务重启可能会丢失数据 2.并发能力问题&#xff1a;单节点Redis并发能力虽然…

springboot系列十二:拦截器和文件上传

文章目录 基本介绍拦截器应用实例需求分析代码实现注意事项和细节 文件上传需求说明代码实现注意事项和细节课后扩展 基本介绍 1.在Spring Boot项目中, 拦截器是开发中常用手段, 要来做登陆验证, 性能检查, 日志记录等. 2.基本步骤: √ 编写一个拦截器实现HandlerInterceptor…

【简单】 猿人学web第一届 第3题 罗生门

请求逻辑分析 数据接口为https://match.yuanrenxue.cn/api/match/3?page1 这一题的 请求参数 与 cookie 都没有加密参数 每次请求数据接口前都会请求 jssm 接口 requests 照着请求逻辑去请求&#xff0c;发现是失败的&#xff08;数据接口返回包含 js标签 代码&#xff09;…

Unity数据持久化 之 Json序列化与反序列化

语法规则可以看这篇文章&#xff1a;Unity数据持久化 之 Json 语法速通-CSDN博客 Q:Unity是通过什么来对Json文件进行处理的&#xff1f; A:JsonUtility&#xff1a;Unity 提供了 JsonUtility 类&#xff0c;用于将对象序列化为 JSON 字符串或将 JSON 字符串反序列化为对象。…

从 MySQL 迁移到 TiDB:使用 SQL-Replay 工具进行真实线上流量回放测试 SOP

导读 在 MySQL 迁移至 TiDB 的过程中&#xff0c;兼容性和性能验证至关重要。SQL-Replay 是一款实用工具&#xff0c;用于评估数据库的兼容性和性能&#xff0c;支持日志解析、查询回放、性能测量和报告生成等功能。 本文介绍了 SQL-Replay 工具的安装和使用步骤&#xff0c;…

基于springboot的医院后台管理系统的设计与实现

TOC springboot167基于springboot的医院后台管理系统的设计与实现 第1章 绪论 1.1 研究背景 互联网概念的产生到如今的蓬勃发展&#xff0c;用了短短的几十年时间就风靡全球&#xff0c;使得全球各个行业都进行了互联网的改造升级&#xff0c;标志着互联网浪潮的来临。在这…

【C语言】格式化输出占位符及其标志字符详解(基于ISO/IEC 9899:2024)

目录 C语言格式化输出占位符及其标志字符详解&#xff08;基于ISO/IEC 9899:2024&#xff09;1. 格式说明符的基本结构1.1 标志字符&#xff08;Flags&#xff09;示例代码 1.2 宽度&#xff08;Width&#xff09;示例代码 1.3 精度&#xff08;Precision&#xff09;示例代码 …

一键换肤(Echarts 自定义主题)

一键换肤&#xff08;Echarts 自定义主题&#xff09; 一、使用官方主题配置工具 官方主题配置工具&#xff1a;https://echarts.apache.org/zh/theme-builder.html 如果以上主题不满足使用&#xff0c;可以自己自定义主题 例如&#xff1a;修改背景、标题等&#xff0c;可…

字节跳动飞书一面0715

进程间通信方式有哪些 1、管道通信&#xff0c;分为匿名管道和有名管道&#xff0c;匿名管道只能在有亲缘关系如父子进程间使用。有名管道可以允许无亲缘关系进程间的通信。它们都是半双工的通信方式&#xff0c;数据只能单向流动。 2、消息队列&#xff0c;用内核中的链表实现…

深入理解WAF(Web应用防火墙)及其安全防御策略

随着互联网的普及和发展&#xff0c;Web应用也面临着越来越多的安全威胁&#xff0c;如SQL注入、跨站脚本&#xff08;XSS&#xff09;、跨站请求伪造&#xff08;CSRF&#xff09;等。这些威胁不仅可能造成数据泄露&#xff0c;还可能导致业务中断和品牌受损。本文将以快快网络…

JavaEE 第9节 阻塞队列详解

一、概念 阻塞队列是在普通队列&#xff08;先进先出的数据结构&#xff09;的基础上增加了阻塞属性的特殊队列 1&#xff09;当阻塞队列空的时候&#xff0c;如果继续出队元素会进入阻塞状态&#xff0c;直到其他线程入队元素。 2&#xff09;当阻塞队列满的时候&#xff0c;…

瑞萨电子并购Altium 引领行业创新与发展

公开资料显示&#xff0c;2023 年 6 月&#xff0c;瑞萨电子曾宣布在 Altium 的 Altium 365 云平台上实现了所有 PCB 设计的标准化开发。瑞萨电子一直与 Altium 合作&#xff0c;将其所有产品的 ECAD 库发布到 Altium Public Vault。借助 Altium365 上的制造商零件搜索等功能&a…

ROW_NUMBER(), RANK(), DENSE_RANK() SQL排序函数图文详解

ROW_NUMBER(), RANK(), DENSE_RANK() ROW_NUMBER(): 为结果集中的每一行分配唯一的连续编号。即使有重复的值&#xff0c;ROW_NUMBER() 也会为它们分配不同的序号。 SELECT column_name, ROW_NUMBER() OVER (ORDER BY column_name) AS row_num FROM table_name;2. RANK(): 对结…

高可用集群keepalived从部署到实战一篇解决

目录 一.高可用集群 1.1 集群类型 1.2 系统可用性 1.3 系统故障 1.4 实现高可用 1.5.VRRP&#xff1a; 1.5.1 VRRP 相关术语 1.5.2 VRRP 相关技术 二.Keepalived 部署 2.1 keepalived 简介 2.2keepalived架构 2.3 Keepalived 环境准备 2.4 Keepalived 相关文件 2.…

java实现七牛云内容审核功能,文本、图片和视频的内容审核(鉴黄、鉴暴恐、敏感人物)

目录 1、七牛云内容审核介绍 2、查看内容审核官方文档 2.1、文本内容审核 2.1.1、文本内容审核的请求示例 2.1.2、文本内容审核的返回示例 2.2、图片内容审核 2.2.1、请求参数 2.2.2、返回参数 2.3、视频内容审核 3、代码实现 3.1、前期代码准备 3.2、文本内容审核…

基于Spring + Vue的旅游景区项目+源代码+文档说明

文章目录 源代码下载地址项目介绍项目功能界面预览 项目备注源代码下载地址 源代码下载地址 点击这里下载源码 项目介绍 基于Spring Vue的旅游景区项目 项目功能 民宿管理员&#xff1a;订单数量统计&#xff0c;订单交易额统计&#xff0c;客房统计饼图&#xff0c;酒店…

【STM32嵌入式系统设计与开发拓展】——14_定时器之输入捕获

参考哔站&#xff1a;链接: 铁头山羊 一、微控制器的高级定时与控制功能集合 1、时基单元 2、输入捕获 3、输出比较 4、从模式控制器 5、高级定时器的输出控制 二、问题集合 1、什么是定时器 定时器是一种专门负责定时功能的片上外设GPI0AFI0EXTIUSART RCC I2C) 2、定时器…

飞越现实:3D可视化引领飞行体验新纪元

在浩瀚的蓝天之下&#xff0c;飞机以优雅的姿态划破长空&#xff0c;每一次起降、每一次盘旋&#xff0c;都是对速度与科技的完美诠释。而今&#xff0c;随着科技的飞速发展&#xff0c;我们不再仅仅满足于仰望天际的壮丽景象&#xff0c;而是能够借助先进的3D可视化技术&#…

JavaEE 第10节 线程池(Thread Pool)介绍

目录 一、线程池是什么 二、为什么线程池中取线程会比直接向操作系统申请来的高效&#xff1f; 三、JAVA标准库中的线程池 &#xff08;1&#xff09;类&#xff1a;ThreadPoolExecutor 1、int corePoolSize与int maximumPoolSize 2、long keepAlive和TimeUnit unit 3、Blo…

MySQL第6讲--DQL(数据查询语言)的基本操作之基本和条件查询

文章目录 前言DQL(数据查询语言)基本操作查询操作基本查询示例1&#xff1a;查询表格的name&#xff0c;age&#xff0c;并返回&#xff1b;示例2&#xff1a;查询表格中的所有字段&#xff1b;示例3&#xff1a;查询所有员工的工号并返回&#xff0c;起别名&#xff1b;示例4&…