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文件列表
    //合并默认文件列表defaultProfiles
    if (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;
    //创建属性解析器PropertySourcesPropertyResolver
    this.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";


	@Override
	protected 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
          @Nullable
          protected 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.
          //加载并处理beanFactoryPostProcessor
          invokeBeanFactoryPostProcessors(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();
       }
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2024-12-29-sklearn学习(25)无监督学习-神经网络模型(无监督) 烟笼寒水月笼沙,夜泊秦淮近酒家。

文章目录 sklearn学习(25) 无监督学习-神经网络模型&#xff08;无监督&#xff09;25.1 限制波尔兹曼机25.1.1 图形模型和参数化25.1.2 伯努利限制玻尔兹曼机25.1.3 随机最大似然学习 sklearn学习(25) 无监督学习-神经网络模型&#xff08;无监督&#xff09; 文章参考网站&a…

BUG分析 - 重启有时失败

1. 倒查版本 1.0_11 - ok1.0_12 - fail 2.对比1.0_11和1.0_12 失败时的日志 ================================== 1.0_11 ============================== 2024-12-26 09:46:51.886 INFO [26332] [ThreadPLCPool::in

git注意事项

提交代码的备注 feat : 开发 新增功能 fix: 修复 git相关 1. git安装及全局用户设置 Git安装 npm install git -ggit修改用户名邮箱密码 git config --global --replace-all user.name "要修改的用户名" git config --global --replace-all user.email"要修改…

LeetCode每日三题(六)数组

一、最大子数组和 自己答案&#xff1a; class Solution {public int maxSubArray(int[] nums) {int begin0;int end0;if(numsnull){//如果数组非空return 0;}else if(nums.length1){//如果数组只有一个元素return nums[0];}//初值选为数组的第一个值int resultnums[0];int i…

一个通用的居于 OAuth2的API集成方案

在现代 web 应用程序中&#xff0c;OAuth 协议是授权和认证的主流选择。为了与多个授权提供商进行无缝对接&#xff0c;我们需要一个易于扩展和维护的 OAuth 解决方案。本文将介绍如何构建一个灵活的、支持多提供商的 OAuth 系统&#xff0c;包括动态 API 调用、路径参数替换、…

小程序配置文件 —— 15 页面配置

页面配置 小程序的页面配置&#xff0c;也称为局部配置&#xff0c;每一个小程序页面也可以使用自己的 .json 文件来对页面的窗口表现进行配置&#xff1b; 需要注意的是&#xff1a;页面配置文件的属性和全局配置文件中的 window 属性几乎一致&#xff0c;只不过这里不需要额…

【从零开始入门unity游戏开发之——C#篇37】进程、线程和C# 中实现多线程有多种方案

文章目录 进程、线程和C#多线程一、进程的基本概念二、线程的基本概念三、C#中的多线程1、为什么需要多线程&#xff1f;2、*C# 中如何实现多线程**2.1 **使用 Thread 类**&#xff08;1&#xff09;示例&#xff08;2&#xff09;线程休眠&#xff08;3&#xff09;设置为后台…

评分模型在路网通勤习惯分析中的应用——提出问题(1)

1、问题的由来、目标和意义 最近一段时间和公司其它业务部门讨论时&#xff0c;发现一个有趣的交通路网问题&#xff0c;车辆从S点行驶到V点共用时40分钟&#xff0c;这段时间内路网中的卡口摄像头识别到了车辆通过的信息。如下图所示&#xff1a; 设计师需要通过这些有限的路…

机器学习DAY7: 特征工程和特征选择(数据预处理)(完)

本文通过特征提取、特征转换、特征选择三个过程介绍数据预处理方法&#xff0c;特征提取将原始数据转换为适合建模的特征&#xff0c;特征转换将数据进行变换以提高算法的准确性&#xff0c;特征选择用来删除无用的特征。 知识点 特征提取特征转换特征选择 本次实验的一些示…

【Unity3D】Jobs、Burst并行计算裁剪Texture3D物体

版本&#xff1a;Unity2019.4.0f1 PackageManager下载Burst插件(1.2.3版本) 利用如下代码&#xff0c;生成一个Texture3D资源&#xff0c;它只能脚本生成&#xff0c;是一个32*32*32的立方体&#xff0c;导出路径记得改下&#xff0c;不然报错。 using UnityEditor; using Uni…

紫光同创-盘古200pro+开发板

本原创文章由深圳市小眼睛科技有限公司创作&#xff0c;版权归本公司所有&#xff0c;如需转载&#xff0c;需授权并注明出处&#xff08;www.meyesemi.com) 一、开发系统介绍 开发系统概述 MES2L676-200HP 开发板采用紫光同创 logos2 系列 FPGA&#xff0c;型号&#xff1a;…

【后端】LNMP环境搭建

长期更新各种好文&#xff0c;建议关注收藏&#xff01; 本文近期更新完毕。 LNMPlinuxnginxmysqlphp 需要的资源 linux服务器 web服务软件nginx 对应的语言编译器代码文件 数据库mysql安装 tar.gz包或者命令行安装 进入root&#xff1a; sodu 或su mkdir path/{server,soft}…

VSCode设置Playwright教程

1.安装扩展 打开VS Code&#xff0c;在扩展—>搜索"Playwright Test for VSCode"&#xff0c;点击安装 按快捷键CommandShiftP&#xff0c;输入install playwright&#xff0c;点击安装Playwright 安装成功会有如下提示 2.调试脚本 打开tests/example.spec.ts文…

RK3566和Robo_C的EMC防护设计细节

USB部分的防护细节&#xff1a; ROBO C的USB接口&#xff1a; PF级别的电容滤波&#xff1a; TVS电容&#xff08;TVS Capacitor&#xff09;&#xff1a;用于与TVS二极管配合&#xff0c;保护电路免受瞬态电压冲击。电容一般较小&#xff0c;通常为几十皮法&#xff08;pF&am…

MicroDiffusion——采用新的掩码方法和改进的 Transformer 架构,实现了低预算的扩散模型

介绍 论文地址&#xff1a;https://arxiv.org/abs/2407.15811 现代图像生成模型擅长创建自然、高质量的内容&#xff0c;每年生成的图像超过十亿幅。然而&#xff0c;从头开始训练这些模型极其昂贵和耗时。文本到图像&#xff08;T2I&#xff09;扩散模型降低了部分计算成本&a…

使用 Three.js 创建一个 3D 人形机器人仿真系统

引言 在这篇文章中&#xff0c;我们将探讨如何使用 Three.js 创建一个简单但有趣的 3D 人形机器人仿真系统。这个机器人可以通过键盘控制进行行走和转向&#xff0c;并具有基本的动画效果。 技术栈 HTML5Three.jsJavaScript 实现步骤 1. 基础设置 首先&#xff0c;我们需要…

【c++高阶DS】最小生成树

&#x1f525;个人主页&#xff1a;Quitecoder &#x1f525;专栏&#xff1a;c笔记仓 目录 01.最小生成树Kruskal算法Prim算法 01.最小生成树 连通图中的每一棵生成树&#xff0c;都是原图的一个极大无环子图&#xff0c;即&#xff1a;从其中删去任何一条边&#xff0c;生成…

自学记录鸿蒙API 13:实现人脸比对Core Vision Face Comparator

完成了文本识别和人脸检测的项目后&#xff0c;我发现人脸比对是一个更有趣的一个小技术玩意儿。我决定整一整&#xff0c;也就是对HarmonyOS Next最新版本API 13中的Core Vision Face Comparator API的学习&#xff0c;这项技术能够对人脸进行高精度比对&#xff0c;并给出相似…

2024/12/29 黄冈师范学院计算机学院网络工程《路由期末复习作业一》

一、选择题 1.某公司为其一些远程小站点预留了网段 172.29.100.0/26&#xff0c;每一个站点有10个IP设备接到网络&#xff0c;下面那个VLSM掩码能够为该需求提供最小数量的主机数目 &#xff08; &#xff09; A./27 B./28 C./29 D./30 -首先审题我们需要搞清楚站点与网…

redis cluster集群

华子目录 什么是redis集群redis cluster的体系架构什么是数据sharding&#xff1f;什么是hash tag集群中删除或新增节点&#xff0c;数据如何迁移&#xff1f;redis集群如何使用gossip通信?定义meet信息ping消息pong消息fail消息&#xff08;不是用gossip协议实现的&#xff0…