当前位置: 首页 > news >正文

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();}}
}

相关文章:

Spring源码_05_IOC容器启动细节

前面几章&#xff0c;大致讲了Spring的IOC容器的大致过程和原理&#xff0c;以及重要的容器和beanFactory的继承关系&#xff0c;为后续这些细节挖掘提供一点理解基础。掌握总体脉络是必要的&#xff0c;接下来的每一章都是从总体脉络中&#xff0c; 去研究之前没看的一些重要…...

科大讯飞在线语音合成(流式版)python版

1、进入自己的项目 复制APPID、APISecret、APIKey 2、添加好听发音人 复制vcn参数 3、需要替换代码部分&#xff1a; 换自己喜欢的发声人的参数 换上自己的APPID、APISecret、APIKey 4、完整代码&#xff1a; # -*- coding:utf-8 -*- import _thread as thread import base…...

常见搜索算法汇总

常见搜索算法总结 搜索算法是人工智能和计算机科学中用于解决问题、优化路径或发现数据模式的关键技术。本文将对常见的搜索算法进行总结&#xff0c;包括A*算法、D*算法、模拟退火&#xff08;Simulated Annealing&#xff09;、爬山法&#xff08;Hill Climbing&#xff09;、…...

vue 中 ref 详解

一、定义与基本用法 1. 定义 在 Vue.js 中&#xff0c;ref是一个用于在组件中获取 DOM 元素或者子组件实例引用的属性。它提供了一种直接访问元素或组件的方式&#xff0c;使得我们可以在 JavaScript 代码中对它们进行操作。 2. 基本使用 在模板中&#xff0c;可以通过给元…...

探索开源项目 kernel:技术的基石与无限可能

在开源的广袤世界中&#xff0c;有一颗璀璨的明星——kernel&#xff08;https://gitee.com/openeuler/kernel&#xff09;&#xff0c;它宛如一座技术的宝藏&#xff0c;蕴含着无数的智慧与创新&#xff0c;为众多开发者所瞩目和敬仰。 一、初窥 kernel 项目 当我第一次接触…...

C 实现植物大战僵尸(二)

C 实现植物大战僵尸&#xff08;二&#xff09; 前文链接&#xff0c;C 实现植物大战僵尸&#xff08;一&#xff09; 五 制作启动菜单 启动菜单函数 void startUI() {IMAGE imageBg, imgMenu1, imgMenu2;loadimage(&imageBg, "res/menu.png");loadimage(&am…...

Vivado - TCL 命令(DPU脚本、v++命令、impl策略)

目录 1. 简介 2. TCL 示例 2.1 DPU TCL 脚本 2.1.1 源码-精简 2.1.2 依赖关系 2.1.3 查 v 步骤列表 2.1.4 生成 DPU.XO 2.2 CPU 示例 2.2.1 源码-框架 2.2.2 示例设计详解 2.3 创建运行脚本 2.3.1 Generate scripts 2.3.2 runme.sh 文件 2.3.3 design_1_wrapper…...

【JDBC】数据库连接的艺术:深入解析数据库连接池、Apache-DBUtils与BasicDAO

文章目录 前言&#x1f30d; 一.连接池❄️1. 传统获取Conntion问题分析❄️2. 数据库连接池❄️3.连接池之C3P0技术&#x1f341;3.1关键特性&#x1f341;3.2配置选项&#x1f341;3.3使用示例 ❄️4. 连接池之Druid技术&#x1f341; 4.1主要特性&#x1f341; 4.2 配置选项…...

hadoop-common的下载位置分享

1.GitHub - steveloughran/winutils: Windows binaries for Hadoop versions (built from the git commit ID used for the ASF relase) 2.GitHub - cdarlint/winutils: winutils.exe hadoop.dll and hdfs.dll binaries for hadoop windows 3.winutils: hadoop winutils 镜像...

【机器学习】SVM支持向量机(一)

介绍 支持向量机&#xff08;Support Vector Machine, SVM&#xff09;是一种监督学习模型&#xff0c;广泛应用于分类和回归分析。SVM 的核心思想是通过找到一个最优的超平面来划分不同类别的数据点&#xff0c;并且尽可能地最大化离该超平面最近的数据点&#xff08;支持向量…...

Spring Boot介绍、入门案例、环境准备、POM文件解读

文章目录 1.Spring Boot(脚手架)2.微服务3.环境准备3.1创建SpringBoot项目3.2导入SpringBoot相关依赖3.3编写一个主程序&#xff1b;启动Spring Boot应用3.4编写相关的Controller、Service3.5运行主程序测试3.6简化部署 4.Hello World探究4.1POM文件4.1.1父项目4.1.2父项目的父…...

基于Spring Boot + Vue3实现的在线商品竞拍管理系统源码+文档

前言 基于Spring Boot Vue3实现的在线商品竞拍管理系统是一种现代化的前后端分离架构的应用程序&#xff0c;它结合了Java后端框架Spring Boot和JavaScript前端框架Vue.js的最新版本&#xff08;Vue 3&#xff09;。该系统允许用户在线参与商品竞拍&#xff0c;并提供管理后台…...

LeetCode--排序算法(堆排序、归并排序、快速排序)

排序算法 归并排序算法思路代码时间复杂度 堆排序什么是堆&#xff1f;如何维护堆&#xff1f;如何建堆&#xff1f;堆排序时间复杂度 快速排序算法思想代码时间复杂度 归并排序 算法思路 归并排序算法有两个基本的操作&#xff0c;一个是分&#xff0c;也就是把原数组划分成…...

华诺星空 Java 开发工程师笔试题 - 解析

单选题 1.Math.round(-11.5)等于多少?(B) A.-11.5 B.-11 C.-12 D.11.5 2.下列哪个没有继承自Collection接口。( C ) A.List B.Set C.Map D.全部 3.下列说法正确的有(B) A.在类方法中可用this来调用本类的类方法 B.在类方法中调用本类的类方法时可直接调用 C.在类…...

QT:一个TCP客户端自动连接的测试模型

版本 1:没有取消按钮 测试效果&#xff1a; 缺陷&#xff1a; 无法手动停止 测试代码 CMakeLists.txt cmake_minimum_required(VERSION 3.19) project(AutoConnect LANGUAGES CXX)find_package(Qt6 6.5 REQUIRED COMPONENTS Core Widgets Network)qt_standard_project_setup(…...

关于启动vue项目,出现:Error [ERR_MODULE_NOT_FOUND]: Cannot find module ‘xxx‘此类错误

目录 一、问题报错 二、原因分析 三、解决方法 一、问题报错 node环境变量配置有问题&#xff1a; (base) xxxM73H-15:~/VueProject/pproject-vue$ npm run dev /usr/bin/env: “node”: 没有那个文件或目录vue项目启动有问题&#xff1a; (base) xxx:~/VueProject/pproj…...

电路元件与电路基本定理

电流、电压和电功率 电流 1 定义&#xff1a; 带电质点的有序运动形成电流 。 单位时间内通过导体横截面的电量定义为电流强度&#xff0c; 简称电流&#xff0c;用符号 i 表示&#xff0c;其数学表达式为&#xff1a;&#xff08;i单位&#xff1a;安培&#xff08;A&#x…...

指针之矢:C 语言内存幽境的精准飞梭

一、内存和编码 指针理解的2个要点&#xff1a; 指针是内存中一个最小单元的编号&#xff0c;也就是地址平时口语中说的指针&#xff0c;通常指的是指针变量&#xff0c;是用来存放内存地址的变量 总结&#xff1a;指针就是地址&#xff0c;口语中说的指针通常指的是指针变量。…...

uniapp下载打开实现方案,支持安卓ios和h5,下载文件到指定目录,安卓文件管理内可查看到

uniapp下载&打开实现方案&#xff0c;支持安卓ios和h5 Android&#xff1a; 1、申请本地存储读写权限 2、创建文件夹&#xff08;文件夹不存在即创建&#xff09; 3、下载文件 ios&#xff1a; 1、下载文件 2、保存到本地&#xff0c;需要打开文件点击储存 使用方法&…...

免费干净!付费软件的平替款!

今天给大家介绍一个非常好用的电脑录屏软件&#xff0c;完全没有广告界面&#xff0c;非常的干净简洁。 电脑录屏 无广告的录屏软件 这个软件不需要安装&#xff0c;打开就能看到界面直接使用了。 软件可以全屏录制&#xff0c;也可以自定义尺寸进行录制。 录制的声音选择也非…...

Flask RESTful 示例

目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题&#xff1a; 下面创建一个简单的Flask RESTful API示例。首先&#xff0c;我们需要创建环境&#xff0c;安装必要的依赖&#xff0c;然后…...

【OSG学习笔记】Day 18: 碰撞检测与物理交互

物理引擎&#xff08;Physics Engine&#xff09; 物理引擎 是一种通过计算机模拟物理规律&#xff08;如力学、碰撞、重力、流体动力学等&#xff09;的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互&#xff0c;广泛应用于 游戏开发、动画制作、虚…...

定时器任务——若依源码分析

分析util包下面的工具类schedule utils&#xff1a; ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类&#xff0c;封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz&#xff0c;先构建任务的 JobD…...

【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验

系列回顾&#xff1a; 在上一篇中&#xff0c;我们成功地为应用集成了数据库&#xff0c;并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了&#xff01;但是&#xff0c;如果你仔细审视那些 API&#xff0c;会发现它们还很“粗糙”&#xff1a;有…...

MySQL 8.0 OCP 英文题库解析(十三)

Oracle 为庆祝 MySQL 30 周年&#xff0c;截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始&#xff0c;将英文题库免费公布出来&#xff0c;并进行解析&#xff0c;帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作

一、上下文切换 即使单核CPU也可以进行多线程执行代码&#xff0c;CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短&#xff0c;所以CPU会不断地切换线程执行&#xff0c;从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...

深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南

&#x1f680; C extern 关键字深度解析&#xff1a;跨文件编程的终极指南 &#x1f4c5; 更新时间&#xff1a;2025年6月5日 &#x1f3f7;️ 标签&#xff1a;C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言&#x1f525;一、extern 是什么&#xff1f;&…...

Kafka入门-生产者

生产者 生产者发送流程&#xff1a; 延迟时间为0ms时&#xff0c;也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于&#xff1a;异步发送不需要等待结果&#xff0c;同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…...

push [特殊字符] present

push &#x1f19a; present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中&#xff0c;push 和 present 是两种不同的视图控制器切换方式&#xff0c;它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...

WPF八大法则:告别模态窗口卡顿

⚙️ 核心问题&#xff1a;阻塞式模态窗口的缺陷 原始代码中ShowDialog()会阻塞UI线程&#xff0c;导致后续逻辑无法执行&#xff1a; var result modalWindow.ShowDialog(); // 线程阻塞 ProcessResult(result); // 必须等待窗口关闭根本问题&#xff1a…...