【spring源码系列-06】refresh中obtainFreshBeanFactory方法的执行流程

Spring源码系列整体栏目


内容链接地址
【一】spring源码整体概述https://blog.csdn.net/zhenghuishengq/article/details/130940885
【二】通过refresh方法剖析IOC的整体流程https://blog.csdn.net/zhenghuishengq/article/details/131003428
【三】xml配置文件启动spring时refresh的前置工作https://blog.csdn.net/zhenghuishengq/article/details/131066637
【四】注解方式启动spring时refresh的前置工作https://blog.csdn.net/zhenghuishengq/article/details/131113249
【五】refresh中prepareRefresh的执行流程https://blog.csdn.net/zhenghuishengq/article/details/131186016
【六】refresh中obtainFreshBeanFactory的执行流程https://blog.csdn.net/zhenghuishengq/article/details/131286888

refresh中obtainFreshBeanFactory方法的执行流程

  • 一,bean工厂的创建
    • 1,refreshBeanFactory()
      • 1.1,reader.register
      • 1.2,scanner.scan
    • 2,getBeanFactory

一,bean工厂的创建

前一篇了解了refresh的第一个方法prepareRefresh ,主要是初始化环境,实例化一些监听器和环境等。接下来讲解refresh中的第二个方法,obtainFreshBeanFactory()

@Override
public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {//1:准备刷新上下文环境prepareRefresh();//2:获取告诉子类初始化Bean工厂,不同工厂有不同的实现//  并且将配置文件的属性值加载到当前工厂中ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();//3:对bean工厂进行填充属性prepareBeanFactory(beanFactory);try {// 第四:留个子类去实现该接口,bean工厂的后置处理器postProcessBeanFactory(beanFactory);// 调用我们的bean工厂的后置处理器. //1. 会在此将class扫描成beanDefinition  2.bean工厂的后置处理器调用invokeBeanFactoryPostProcessors(beanFactory);// 注册我们bean的后置处理器registerBeanPostProcessors(beanFactory);// 初始化国际化资源处理器.initMessageSource();// 创建事件多播器initApplicationEventMulticaster();// 这个方法同样也是留个子类实现的springboot也是从这个方法进行启动tomcat的.onRefresh();//把我们的事件监听器注册到多播器上registerListeners();// 实例化我们剩余的单实例bean.finishBeanFactoryInitialization(beanFactory);// 最后容器刷新 发布刷新事件(Spring cloud也是从这里启动的)finishRefresh();}}
}

在refresh的前置工作中,已经提前初始化了一个默认工厂DefaultListableBeanFactory,而这一步,就是初始化具体的工厂实现。接下来继续往下面的源码看 ,此时会调用一个 obtainFreshBeanFactory() 方法

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

进去这个方法中可以发现,第一步就是会进行一个刷新bean工厂的方法 refreshBeanFactory

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {/***  xml加载spring会在这里加载beanDefinition*  javaconfig只是刷新了beanFactory*/refreshBeanFactory();//返回我们的bean工厂ConfigurableListableBeanFactory beanFactory = getBeanFactory();if (logger.isDebugEnabled()) {logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);}return beanFactory;
}

1,refreshBeanFactory()

1,接下来查看这个刷新bean工厂的方法,首先会判断bean工厂是否存在,存在则销毁原先的bean工厂,然后再创建一个默认的工厂 DefaultListableBeanFactory ,创建工厂的过程主要是加载配置文件,设置属性等。

@Override
protected final void refreshBeanFactory() throws BeansException {//若已经存在了 就信息销毁等操作if (hasBeanFactory()) {destroyBeans();closeBeanFactory();}try {//为我们的Spring应用上下文对象创建我们的beanFactoryDefaultListableBeanFactory beanFactory = createBeanFactory();//为容器设置一个序列化ID,可以通过反序列化获取bean工厂beanFactory.setSerializationId(getId());customizeBeanFactory(beanFactory);//加载我们的bean定义loadBeanDefinitions(beanFactory);synchronized(this.beanFactoryMonitor) {this.beanFactory = beanFactory;}} catch (IOException ex) {...}
}

2,这个创建的bean工厂就是我们熟悉的默认bean工厂 DefaultListableBeanFactory

//为我们的spring 上下文创建我们的内置的beanFactory
protected DefaultListableBeanFactory createBeanFactory() {return new DefaultListableBeanFactory(getInternalParentBeanFactory());
}

3,随后就是设置一个序列化的id,可以通过反序列的形式获取到对应的beanFactory

beanFactory.setSerializationId(getId());

4,随后就是定制化bean工厂的操作,会设置两个重要的属性,就是允许覆盖bean定义和允许循环依赖,可以重写这两个方法,修改这两个值的默认属性。

protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {if (this.allowBeanDefinitionOverriding != null) {beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);}if (this.allowCircularReferences != null) {beanFactory.setAllowCircularReferences(this.allowCircularReferences);}
}

5,再接着就是以加载bean定义的形式将bean工厂加载,其具体的方法实现是 loadBeanDefinitions

loadBeanDefinitions(beanFactory);

在这个加载bean定义的抽象类中,可以看到存在四种不同的实现,分别就是对应着不同方式获取上下文的方式

在这里插入图片描述

以注解的方式,继续往下看,可以发现在内部存在着大量的方法的重载,而此时bean定义的信息时存储在工厂类里面的

在这里插入图片描述

在前面两步里面,主要是创建一个读取器和扫描器,获取一些系统环境和变量,资源解析器,处理一些条件注解,加载一些bean的后置处理器等

//读取器
AnnotatedBeanDefinitionReader reader = getAnnotatedBeanDefinitionReader(beanFactory);
//扫描器
ClassPathBeanDefinitionScanner scan = getClassPathBeanDefinitionScanner(beanFactory);

随后再获取bean容器的构造器,如果不存在则重新注册一个单例的构造器

BeanNameGenerator beanNameGenerator = getBeanNameGenerator();

随后再获取一个数据解析器 ScopeMetadataResolver

ScopeMetadataResolver scopeMetadataResolver = getScopeMetadataResolver();

1.1,reader.register

在加载完这些配置之后,接下来就是真正的去对这些bean定义进行注册

reader.register(ClassUtils.toClassArray(this.annotatedClasses));

真正的注册bean的方法是这个 doRegisterBean 方法

在这里插入图片描述

该方法中会存储一些@Configuration注解的类

AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);

也会对一些条件进行筛选判断,如@ConditionalOnClass,@ConditionalOnType等,以及解析一些bean的作用域,填充一些bean的属性,如懒加载等等,最后将这个bean定义进行注册,具体的注册逻辑在上一篇详细的写过

BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry)

1.2,scanner.scan

在reader注册完之后,接下来对一些xml文件或者一些包进行扫描的工作

scanner.scan(StringUtils.toStringArray(this.basePackages));

其具体的扫描扫描方法如下,真正扫描配置类的工作是这个 doScan 方法

public int scan(String... basePackages) {//还没有扫描mapper包之前 容器中所有的bean定义个数int beanCountAtScanStart = this.registry.getBeanDefinitionCount();//真正的扫描我们的mapper包的mapper类//此处调用的是子类的doScan因为被重写了doScan(basePackages);// 注册系统中的配置类处理器if (this.includeAnnotationConfig) {AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);}//返回扫描出mapper的bean定义的个数return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}

接下来查看这个doscan方法 , 如下图所示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k665ep9Y-1687155223629)(img/1686890716541.png)]

首先会创建一个 BeanDefinitionHolder 对象用于保存bean定义,这个对象里面存储着bean定义,属性等

//创建bean定义的holder对象用于保存扫描后生成的bean定义对象
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();

随后一步就是循环这个作为参数传进来的包路径,第一步就是找到候选的 Components

Set<BeanDefinition> candidates = findCandidateComponents(basePackage);

在上面reader.register注册中,在doRegisterBean 方法中将 @Configuration 注解先注册成beanDifinition,所以@Configuration比@Component注解先加载成bean定义。接下来继续查看这个 findCandidateComponents 方法,会对包路径进行解析

public Set<BeanDefinition> findCandidateComponents(String basePackage) {//验证是否为空if (this.componentsIndex != null && indexSupportsIncludeFilters()) {return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);}else {return scanCandidateComponents(basePackage);}
}

在使用注解获取上下文启动spring时,在refresh的前置工作中,就会注册一个 includeFiltersexcludeFilters ,默认会将所有的带有 @Component,@Controller,@Service 等等注解的类加载到这个 includeFilters 的过滤器中,也可以手动将他加入到 excludeFilters 中,这样spring容器就不会加载

protected void registerDefaultFilters() {//加入扫描我们的@Component的this.includeFilters.add(new AnnotationTypeFilter(Component.class));....
}

接着上文,如果是包含在这个包含过滤器中,则直接将这个容器加入到包中,否者会继续扫描这些候选者们,会判断这些文件是否可读等。

看完doScan方法找到全部的候选beanDefinition之后,这些bean定义就是符合标准可以注册到bean工厂的一些bean定义,接下来就会对这些bean定义设置一些属性,随后就是关键的一步,就是将我们解析出来的组件bean定义注册到我们的IOC容器中,随后将这个bean定义返回

if (checkCandidate(beanName, candidate)) {BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);definitionHolder =AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);beanDefinitions.add(definitionHolder);registerBeanDefinition(definitionHolder, this.registry);
}

最后又会调用具体的 doRegisterBean 方法用于真正的注册bean定义

<T> void doRegisterBean(...){}

总结来说,不管是使用读取器注册还是使用扫描器进行扫描操作,都是为了生成bean定义,并将这个bean定义加入到BeanDifinitionMap中,随后交给bean工厂去生产。

2,getBeanFactory

在刷新完bean工厂之后,随后就是get获取一个bean工厂

ConfigurableListableBeanFactory beanFactory = getBeanFactory();

获取bean工厂的方法如下,加了一个同步锁,随后将这个bean工厂对象返回

@Override
public final ConfigurableListableBeanFactory getBeanFactory() {synchronized(this.beanFactoryMonitor) {if (this.beanFactory == null) {throw new IllegalStateException("...");}return this.beanFactory;}
}

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

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

相关文章

vscode里vue文件内<template>标签报错

发现只要把文件名使用驼峰命名法&#xff0c;把Login.vue改为LoginView.vue就不报错了加个大写的View就没有了。 官方参考文档&#xff1a;vue/multi-word-component-names | eslint-plugin-vue

MySQL库表的简单操作

1.创建数据Market&#xff0c;在数据库中创建表customers&#xff0c;表结构如图 (1)创建数据库Market create database Market&#xff1b; (2)创建数据表customers&#xff0c;在c_num字段添加主键约束&#xff0c;c_birth字段上添加非空约束 create table customers&#…

基于stm32单片机的智能家居环境监控系统

​一.硬件方案 智能家居环境监控系统的整体电路主要由stm32单片机最小系统&#xff0c;光MQ-2烟雾传感器电路&#xff0c;红外人体检测电路&#xff0c;DS18B20温度传感器&#xff0c;LCD1602显示电路&#xff0c;水泵驱动电路&#xff0c;风扇驱动电路&#xff0c;LED指示灯&…

【EXCEL】给数据添加图表(数据条、柱状图、折线图等),快速分析功能图文详解

目录 0.环境 1.背景简介 2.具体实现 2.1 给数据添加数据条 实现效果&#xff1a; 具体操作&#xff1a; 2.2 给数据添加柱状图图表 实现效果&#xff1a; 具体操作&#xff1a; 2.3 给数据添加迷你图&#xff08;在表格中的折线图&#xff09; 实现效果&#xff1a; …

UNI-APP_vmin横屏适配问题

vmax和vmin vmax 相对于视口的宽度或高度中较大的那个。其中最大的那个被均分为100单位的vmax vmin 相对于视口的宽度或高度中较小的那个。其中最小的那个被均分为100单位的vmin当竖屏布局时750rpx是竖屏布局屏幕的宽度 vmin不管横竖屏的情况下&#xff0c;100vmin都是手机屏幕…

Claude使用教程,解决Claude不能回复

Claude是ChatGPT最为有⼒的竞争对⼿之⼀&#xff0c;Claude 的研发公司是专注人工智能安全和研究的初创公司 Anthropic&#xff0c;由前 OpenAI 员工共同创立的。今年 3 月份 Anthropic 获得了谷歌 3 亿美元的投资&#xff0c;谷歌也因此获得其 10% 股份。 ⽬前可以通过官⽹加…

【Accumulate】Gitee解决每次推送输入账户密码问题

【前言】 每次建立私人仓库后&#xff0c;一推送就得输入账户密码&#xff0c;真的巨烦人啊。 【解决】 step1&#xff1a; 绑定私匙&#xff1a; 配置Git_犟小孩的博客-CSDN博客 step2&#xff1a; 每次绑定远程仓库的时候&#xff0c;使用SSH绑定 如果已经绑定过了&…

目标检测舰船数据集整合

PS&#xff1a;大家如果有想要的数据集可以私信我&#xff0c;如果我下载了的话&#xff0c;可以发给你们~ 一、光学数据集 1、 DIOR 数据集(已下载yolo版本)&#xff08;论文中提到过&#xff09; DIOR由23463张最优遥感图像和190288个目标实例组成&#xff0c;这些目标实例用…

【适配器模式】—— 每天一点小知识

&#x1f4a7; 适配器模式 \color{#FF1493}{适配器模式} 适配器模式&#x1f4a7; &#x1f337; 仰望天空&#xff0c;妳我亦是行人.✨ &#x1f984; 个人主页——微风撞见云的博客&#x1f390; &#x1f433; 《数据结构与算法》专栏的文章图文并茂&#x1f995;…

【源码】vue2+element+laravel8开发不良事件上报系统源码

系统概述&#xff1a; 医院不良事件报告系统为医院内质量控制、患者安全关注、医疗安全不良事件方面的精细化的管理提供了平台&#xff0c;是等级医院评审的必备内容&#xff0c;评审要求医院直报系统要与卫生部“医疗安全(不良)事件报告系统”建立网络对接。 不良事件报告系统…

C语言 base32与base64加解密

概述 Base32、Base64编码就是分别用32个、64个可打印字符表示二进制数据。 一、Base32规则 32 2^5&#xff0c;所以需要5 Bit来表示一个base32字符。一个字节8 Bit&#xff0c;5和8的最小公倍数是40。编码的过程中&#xff0c;以5个字节为一组转为8个base32字符&#xff0c;不…

【Redisson】Redisson--分布式远程服务(Remote Service)

Redisson系列文章&#xff1a; 【Redisson】Redisson–基础入门【Redisson】Redisson–布隆(Bloom Filter)过滤器【Redisson】Redisson–分布式锁的使用&#xff08;推荐使用&#xff09;【分布式锁】Redisson分布式锁底层原理【Redisson】Redisson–限流器 文章目录 一、Redi…