Spring源码_05_IOC容器启动细节

前面几章,大致讲了SpringIOC容器的大致过程和原理,以及重要的容器和beanFactory的继承关系,为后续这些细节挖掘提供一点理解基础。掌握总体脉络是必要的,接下来的每一章都是从总体脉络中,

去研究之前没看的一些重要细节。

本章就是主要从Spring容器的启动开始,查看一下Spring容器是怎么启动的,调用了父类的构造方法有没有干了什么。😄

直接从创建容器为切入点进去:

ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = context.getBean(User.class);

进去之后会调用到这个方法:

可以看到这里是分了三步:

1、调用父类构造方法

2、设置配置文件地址

3、刷新容器

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {//调用父类构造方法,其实没做啥,就是如果有父容器(默认啥空),设置父容器和合并父容器的environment到当前容器super(parent);//设置配置文件地址:如果有用了$、#{}表达式,会解析到这些占位符,拿environment里面到属性去替换返回setConfigLocations(configLocations);if (refresh) {//刷新容器,是Spring解析配置,加载Bean的入口。// 用了模板方法设计模型:规定了容器中的一系列步骤refresh();}
}

1. super(parent)-调用父类构造方法

其实这个方法点进去,会调用到一系列父类的super方法,但是最终只是调用到了 AbstractApplicationContext的构造方法(其实每个父类里面对应的属性都可以看一看,有些都是直接初始化默认的)

/*** Create a new AbstractApplicationContext with the given parent context.* @param parent the parent context*/
public AbstractApplicationContext(@Nullable ApplicationContext parent) {//会初始化resourcePatternResolver属性为PathMatchingResourcePatternResolver//就是路径资源解析器,比如写的"classpath:*",会默认去加载classpath下的资源this();//设置父容器。并会copy父容器的environment属性合并到当前容器中setParent(parent);
}

1.1 this()

接下来调用自己的this方法

public AbstractApplicationContext() {//设置资源解析器this.resourcePatternResolver = getResourcePatternResolver();
}

就是设置了自己的resourcePatternResolver资源解析器

1.1.1 getResourcePatternResolver()

这个代码没啥,就是创建了一个默认的资源解析处理器 PathMatchingResourcePatternResolver

protected ResourcePatternResolver getResourcePatternResolver() {return new PathMatchingResourcePatternResolver(this);
}

其实这个对象的功能就是把你传进来的字符串的路径,解析加载到具体的文件,返回Spring能识别的Resource对象

ok,this方法走完了应该就继续走之前的setParent(parent)方法

1.2 setParent(parent)

其实这里目前就是走不进去的,默认的parent父容器我们这里没使用,所以是空的,并不会走if的逻辑

但是代码也挺简单,其实就是设置了parent属性,合并父容器的Environment到当前容器的Environment

public void setParent(@Nullable ApplicationContext parent) {this.parent = parent;//如歌有父容器,则合并父容器的Environment的元素到当前容器中//合并PropertySource(也就是key和value)//合并激活activeProfiles文件列表//合并默认文件列表defaultProfilesif (parent != null) {Environment parentEnvironment = parent.getEnvironment();if (parentEnvironment instanceof ConfigurableEnvironment) {getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);}}
}

当然,可以假设我们设置了parent属性。

会先调用到getEnvironment方法,获取环境对象,如果没有的话,会创建一个默认的

1.2.1 getEnvironment
@Override
public ConfigurableEnvironment getEnvironment() {if (this.environment == null) {this.environment = createEnvironment();}return this.environment;
}

默认是空的,会跑到createEnvironment方法

1.2.1.1 createEnvironment()
protected ConfigurableEnvironment createEnvironment() {return new StandardEnvironment();
}

会初始化一个StandardEnvironment类型的对象,我们可以关注他的构造方法,其实并没有内容,但是会默认调用他的父类AbstractEnvironment构造器的方法

public AbstractEnvironment() {//这里会默认加载属性属性变量和环境信息this(new MutablePropertySources());
}

1.2.1.1.1 new MutablePropertySources()

其实这个对象就是使用了迭代器的设计模式,里面用 propertySourceList数组存储不同类型的PropertySource

那么PropertySource是干嘛的呢??

//存放Environment对象里的每个属性,一个PropertySource对象里面存有不同的Properties对象
//Properties对象就是有key和value的键值对象
//比如name=systemProperties -> 系统属性Properties对象
//比如name=systemEnv -> 系统环境变量Properties对象
public abstract class PropertySource<T> {protected final Log logger = LogFactory.getLog(getClass());protected final String name;protected final T source;
}

这里摘取了他的属性。

其实name只是一个类型而已,比如Environment包括了systemProperties(系统属性)和systemEnv(系统环境变量)两种。对应就是不同的name的属性存储器

source属性一般都是Java中的Properties对象,这个对象大家应该都熟悉吧(就跟map差不多,有keyvalue,一般用于读取properties文件使用)

看一下下面的图就知道了,Environment在Spring中算是非常重要的对象了,所以必须了解

好了,知道了创建了这个默认的对象即可。

接下来就是调用AbstractEnvironmentthis方法进去了。

AbstractEnvironment(MutablePropertySources)
protected AbstractEnvironment(MutablePropertySources propertySources) {this.propertySources = propertySources;//创建属性解析器PropertySourcesPropertyResolverthis.propertyResolver = createPropertyResolver(propertySources);//调用子类的方法,加载系统的环境变量和系统属性到environment中customizePropertySources(propertySources);
}

可以看到这里就是设置了Environment内部的propertySources对象(存储属性的容器),

设置了propertyResolver属性解析器,类型为PropertySourcesPropertyResolver还把刚刚那个propertySources设置进去了,这个解析器在后面会用到(在设置配置文件路径时会解析,后面会聊到!)

接下来非常重要的方法就是customizePropertySources方法了,其实在当前类AbstractEnvironment中是空方法,是子类 StandardEnvironment实现的。(这里是不是很熟悉的味道,又是模版方法设计模式,AbstractEnvironment规定了步骤,调用了当前类的空方法,子类会去覆盖这个空方法)😄

ok,我们进来了子类StandardEnvironmentcustomizePropertySources方法

其实可以看到这里就是写了两句代码,分别就是去读取系统属性和系统环境变量的值,加载到Environment

public class StandardEnvironment extends AbstractEnvironment {/** System environment property source name: {@value}. */public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";/** JVM system properties property source name: {@value}. */public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";@Overrideprotected void customizePropertySources(MutablePropertySources propertySources) {//添加系统属性和系统环境变量,封装了一个个propertySource对象,添加到Environment的propertySources属性列表中propertySources.addLast(//系统属性new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));propertySources.addLast(//系统环境变量new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));}}

我们可以看其中一个方法getSystemEnvironment,就是调用了jdk的System.getenv()方法,去获取到你本机的系统环境变量的值,然后最后设置到propertySources -> Environment

@Override
@SuppressWarnings({"rawtypes", "unchecked"})
public Map<String, Object> getSystemEnvironment() {if (suppressGetenvAccess()) {return Collections.emptyMap();}try {//jdk提供的方法,获取系统的环境变量return (Map) System.getenv();}catch (AccessControlException ex) {return (Map) new ReadOnlySystemAttributesMap() {@Override@Nullableprotected String getSystemAttribute(String attributeName) {try {return System.getenv(attributeName);}catch (AccessControlException ex) {if (logger.isInfoEnabled()) {logger.info("Caught AccessControlException when accessing system environment variable '" +attributeName + "'; its value will be returned [null]. Reason: " + ex.getMessage());}return null;}}};}
}

解析完的Environment的里面的值大概是这样:

到这里,应该是理解Environment对象了吧。😄

okk,✋🏻回到之前的调用getEnvironment的地方,咱们已经看完这个方法啦!也就是标题1.2

接下里有了Environment对象,就会进行父子容器的Environment的合并啦!

1.2.2 Environment.merge()-父子容器的Environment合并

这里的代码就非常简单了,主要就是合并父容器的Environment的属性到当前子容器中

public void merge(ConfigurableEnvironment parent) {
//合并PropertySource,也就是具体存在的属性键值对
for (PropertySource<?> ps : parent.getPropertySources()) {if (!this.propertySources.contains(ps.getName())) {this.propertySources.addLast(ps);}
}
//合并活跃的profile - 一般SpringBoot中多开发环境都会设置profile
String[] parentActiveProfiles = parent.getActiveProfiles();
if (!ObjectUtils.isEmpty(parentActiveProfiles)) {synchronized (this.activeProfiles) {Collections.addAll(this.activeProfiles, parentActiveProfiles);}
}
//合并默认的profile
String[] parentDefaultProfiles = parent.getDefaultProfiles();
if (!ObjectUtils.isEmpty(parentDefaultProfiles)) {synchronized (this.defaultProfiles) {this.defaultProfiles.remove(RESERVED_DEFAULT_PROFILE_NAME);Collections.addAll(this.defaultProfiles, parentDefaultProfiles);}
}
}

ok,到这里标题1,调用父类构造的方法到这里就结束了,接下来继续探索setConfigLocations干了什么。

2. setConfigLocations-设置配置文件路径

/*** 设置配置文件地址,并且会将文件路径格式化成标准格式* 比如applicationContext-${profile}.xml, profile存在在Environment。* 假设我的Environment中有 profile = "dev",* 那么applicationContext-${profile}.xml会被替换成 applicationContext-dev.xml* Set the config locations for this application context.* <p>If not set, the implementation may use a default as appropriate.*/
public void setConfigLocations(@Nullable String... locations) {if (locations != null) {//断言,判读当前配置文件地址是空就跑出异常Assert.noNullElements(locations, "Config locations must not be null");this.configLocations = new String[locations.length];for (int i = 0; i < locations.length; i++) {//解析当前配置文件的地址,并且将地址格式化成标准格式this.configLocations[i] = resolvePath(locations[i]).trim();}}else {this.configLocations = null;}
}

这里关键的方法是会调用到resolvePath方法并返回这些字符串路径

点进去,有没有感觉到很惊喜,为什么用了getEnvironment去调用的呢?

其实之前的getEnvironment并没有执行到,因为我们没有设置父类parent,到这里才是第一次初始化这个Environment对象然后调用它的resolveRequiredPlaceholders方法去解析路径

(这里关Environment什么事呢?其实我们可以动态地写我们的配置文件,配置文件会去读取占位符,判断在Environment是否存在这些属性,并完成替换)

protected String resolvePath(String path) {//这里的获取getEnvironment,会默认创建StandardEnvironment对象。//并用这个Environment对象解析路径return getEnvironment().resolveRequiredPlaceholders(path);
}

写个示例就清楚咯!

2.1. 示例

我的电脑中存在HOME这个环境变量

接下来修改我的配置文件名称:

修改完之后发现,配置文件路径确定给解析到了。

了解这个功能即可。平时很少这么使用

ok,解析完配置,接下来就是最核心的方法了,调用refresh容器刷新方法

3. refresh-容器刷新方法

这个方法是IOC的核心方法,只要掌握这个方法中的每一个方法,其实就基本掌握了Spring的IOC的整个流程。

后面将会分为很多章节去解释每个方法。

/*** 容器刷新方法,是Spring最核心到方法。* 规定了容器刷新到流程:比如prepareRefresh 前置刷新准备、* obtainFreshBeanFactory 创建beanfactory去解析配置文、加载beandefinition、* prepareBeanFactory 预设置beanfactory、* invokeBeanFactoryPostProcessors 执行beanfactoryPostProcessor* registerBeanPostProcessors 注册各种beanPostProcesser后置处理器* initMessageSource 国际化调用* initApplicationEventMulticaster 初始化事件多播器* onRefresh 刷新方法,给其他子容器调用,目前这个容器没干啥* registerListeners 注册时间监听器* finishBeanFactoryInitialization 初始化所有非懒加载的bean对象到容器中* finishRefresh 容器完成刷新: 主要会发布一些事件** @throws BeansException* @throws IllegalStateException*/
@Override
public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");// Prepare this context for refreshing.//容器刷新的前置准备//设置启动时间,激活状态为true,关闭状态false//初始化environment//初始化监听器列表prepareRefresh();// Tell the subclass to refresh the internal bean factory.//创建beanFactory对象,并且扫描配置文件,加载beanDeifination,注册到容器中ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// Prepare the bean factory for use in this context.//BeanFactory的预准备处理,设置beanFactory的属性,比如添加各种beanPostProcessor//设置environment为bean对象并添加到容器中,后面可以直接@autowrie注入这些对象prepareBeanFactory(beanFactory);try {// Allows post-processing of the bean factory in context subclasses.//子类去实现的回调方法,当前容器没做什么工作,是个空方法postProcessBeanFactory(beanFactory);StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");// Invoke factory processors registered as beans in the context.//加载并处理beanFactoryPostProcessorinvokeBeanFactoryPostProcessors(beanFactory);// Register bean processors that intercept bean creation.//注册BeanPostProcessor对象到容器中registerBeanPostProcessors(beanFactory);beanPostProcess.end();// Initialize message source for this context.//初始化消息源,国际化使用initMessageSource();// Initialize event multicaster for this context.//初始化事件多播器对象,并注册到容器中initApplicationEventMulticaster();// Initialize other special beans in specific context subclasses.//刷新,又是spring为了扩展,做的一个空实现,让子类可以覆盖这个方法做增强功能onRefresh();// Check for listener beans and register them.//注册监听器到容器中,如果容器中的earlyApplicationEvents列表中有事件列表//就会先发送这些事件。比如可以在前面的onRefresh方法中设置registerListeners();// Instantiate all remaining (non-lazy-init) singletons.//最最重要的方法,根据之前加载好的beandefinition,实例化bean到容器中,//涉及到三级缓存、bean的生命周期、属性赋值等等finishBeanFactoryInitialization(beanFactory);// Last step: publish corresponding event.//完成刷新,会发送事件。//检查earlyApplicationEvents事件列表中有没有新增的未发送的事件,有就发送// 在执行applicationEventMulticaster事件列表中的所有事件finishRefresh();}catch (BeansException ex) {if (logger.isWarnEnabled()) {logger.warn("Exception encountered during context initialization - " +"cancelling refresh attempt: " + ex);}// Destroy already created singletons to avoid dangling resources.destroyBeans();// Reset 'active' flag.cancelRefresh(ex);// Propagate exception to caller.throw ex;}finally {// Reset common introspection caches in Spring's core, since we// might not ever need metadata for singleton beans anymore...resetCommonCaches();contextRefresh.end();}}
}

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

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

相关文章

【Linux】进程间通信 -> 匿名管道命名管道

进程间通信的目的 数据传输&#xff1a;一个进程许需要将它的数据发送给另外一个进程。资源共享&#xff1a;多个进程之间共享同样的资源。通知事件&#xff1a;一个进程需要向另一个或一组进程发送消息&#xff0c;通知它们发生了某种事件&#xff08;如进程终止时要通知父进程…

大模型-使用Ollama+Dify在本地搭建一个专属于自己的聊天助手与知识库

大模型-使用OllamaDify在本地搭建一个专属于自己的知识库 1、本地安装Dify2、本地安装Ollama并解决跨越问题3、使用Dify搭建聊天助手4、使用Dify搭建本地知识库 1、本地安装Dify 参考往期博客&#xff1a;https://guoqingru.blog.csdn.net/article/details/144683767 2、本地…

黑盒测试/白盒测试知识总结

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 对于很多刚开始学习软件测试的小伙伴来说&#xff0c;如果能尽早将黑盒、白盒测试弄明白&#xff0c;掌握两种测试的结论和基本原理&#xff0c;将对自己后期的学习…

数据结构之线性表之顺序表

定义&#xff1a; 由n&#xff08;n>0&#xff09;个数据特性相同的元素构成的有限序列称为线性表 简单来说n个相同数据类型的数据组wsw合在一起的这么一个集合就是一个线性表 线性表包括顺序表和链表 1. 顺序表&#xff08;我们所有的代码实现都用函数来封装&#xff09…

ReactPress 1.6.0:重塑博客体验,引领内容创新

ReactPress 是一个基于Next.js的博客&CMS系统&#xff0c; Github项目地址&#xff1a;https://github.com/fecommunity/reactpress 欢迎Star。 体验地址&#xff1a;http://blog.gaoredu.com/ 今天&#xff0c;我们自豪地宣布ReactPress 1.6.0版本的正式发布&#xff0c;…

(echarts)数据地图散点类型根据条件设置不同的标记图片

(echarts)数据地图散点类型根据条件设置不同的标记图片 1.用在线工具将本地图片转化base64格式 data(){return { base64Img:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQ...",} }在线转换地址&#xff1a;https://www.jyshare.com/front-end/59/ 2.symbol属…

傅里叶变换原理

本文学习自YuHWEI最浅显易懂的傅里叶变换公式和原理_采样率和信号频率计算镜像频率-CSDN博客 本文会对公式实现 更加详细的补充 连续与离散 一般人们口中所说的傅里叶变换都是指连续傅里叶变换&#xff0c;针对的是连续时域信号. 对于计算设备的信号处理&#xff0c;因为采样…

STM32高级 以太网通讯案例2:搭建TCP服务端

需求描述 在TCP通讯的时候&#xff0c;客户端必须主动联系服务器&#xff0c;这样才能实现通讯。服务器与客户端之间的连接是一种长连接&#xff0c;一旦连上一般是不会断开的。 在STM32上启动一个TCP的服务端&#xff0c;在电脑上用TCP客户端去连接服务端。客户端给服务端发…

系统架构师考试 常错题记录 01

1.按照《中华人民共和国著作权法》的权利保护期&#xff08; &#xff09;受到永久保护。 A.发表权 B.修改权 C.复制权 D.发行权 正确答案&#xff1a;B 解析&#xff1a;本题考查知识产权法中的《中华人民共和著作权法》保护期限知识点。 《中华人民共和著作权法》中约定署名权…

WebPack3项目升级webpack5的配置调试记录

文章目录 前言一、webpack3环境1.1、知识点记录1.1.1、配置解释1.1.2、webpack与sass版本对应关系1.1.3、CommonJS与ESModule1.1.4、node版本管理nvm1.1.5、sass-loader、sass与node-sass 1.2、其他1.2.1、.d.ts是什么文件1.2.2、react与types/react版本对应关系1.2.3、webpack…

[源码解析] 模型并行分布式训练Megatron (2) --- 整体架构

link [源码解析] 模型并行分布式训练Megatron (2) --- 整体架构 目录 [源码解析] 模型并行分布式训练Megatron (2) --- 整体架构 0x00 摘要0x01 启动 1.1 分布式启动1.2 构造基础 1.2.1 获取模型1.2.2 获取数据集1.2.3 步进函数 1.2.3.1 广播数据0x02 Pretrain0x03 初始化 3.1 …

事件驱动编程与异步编程:原理、对比及实践案例

在编程领域&#xff0c;事件驱动编程&#xff08;Event-Driven Programming&#xff09;与异步编程&#xff08;Asynchronous Programming&#xff09;是两种极为关键的编程范式&#xff0c;它们对于提升程序运行效率与响应速度效果显著&#xff0c;尤其在应对I/O密集型任务以及…

基于earthSDK三维地图组件开发

上效果 功能点 测量分析相机位置切换geojson数据加载地图打点&#xff0c;显示信息点击回传数据二三位切换 这里二三维切换通上篇openlayers分享&#xff0c;技术交流V:bloxed <template><div class"h100 w100"><div style"width:100%; heig…

计算机毕业设计Python+Spark知识图谱酒店推荐系统 酒店价格预测系统 酒店可视化 酒店爬虫 酒店大数据 neo4j知识图谱 深度学习 机器学习

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

RK3506开发板:智能硬件领域的新选择,带来卓越性能与低功耗

在现代智能硬件开发中&#xff0c;选择一款性能稳定、功耗低的开发板是确保产品成功的关键。Rockchip最新推出的RK3506芯片&#xff0c;凭借其卓越的能效比、多功能扩展性和优秀的实时性能&#xff0c;已经成为智能家电、工业控制、手持终端等领域的热门选择。而基于RK3506的Ar…

【AIGC-ChatGPT进阶副业提示词】星际占卜师:探索星象能量的艺术【限时免费阅读,一天之后自动进入进阶课程】

引言 在这个数字化的时代&#xff0c;我们创造了一个独特的角色 —— 星际占卜师。这不仅是一个简单的运势预测工具&#xff0c;更是一个融合了玄学、预言和能量解读的智能向导。通过精心设计的系统提示词和独特的画境生成机制&#xff0c;星际占卜师能够为用户带来沉浸式的占…

机器学习之PCA降维

主成分分析&#xff08;PCA&#xff0c;Principal Component Analysis&#xff09; 主成分分析&#xff08;PCA&#xff09;是一种常见的无监督学习技术&#xff0c;广泛应用于数据降维、数据可视化以及特征提取等任务。PCA的目标是通过线性变换将数据从高维空间映射到低维空间…

SOTA简繁中文拼写检查工具:FASPell Chinese Spell Checker 论文

拼写纠正系列 NLP 中文拼写检测实现思路 NLP 中文拼写检测纠正算法整理 NLP 英文拼写算法&#xff0c;如果提升 100W 倍的性能&#xff1f; NLP 中文拼写检测纠正 Paper java 实现中英文拼写检查和错误纠正&#xff1f;可我只会写 CRUD 啊&#xff01; 一个提升英文单词拼…

Visual Studio Code历史版本下载

本章教程&#xff0c;介绍如何找到Visual Studio Code的历史版本官方下载地址。 一、历史版本下载地址 下载地址&#xff1a;https://code.visualstudio.com/updates/ 二、常用版本下载地址 August 2017 (version 1.16)&#xff1a;https://code.visualstudio.com/updates/v1_…

Kubernetes(k8s)离线部署DolphinScheduler3.2.2

1.环境准备 1.1 集群规划 本次安装环境为&#xff1a;3台k8s现有的postgreSql数据库zookeeper服务 1.2 下载及介绍 DolphinScheduler-3.2.2官网&#xff1a;https://dolphinscheduler.apache.org/zh-cn/docs/3.2.2 官网安装文档&#xff1a;https://dolphinscheduler.apach…