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

update_engine-FilesystemVerifierAction和PostinstallRunnerAction

在介绍完了DownloadAction之后,还剩下FilesystemVerifierAction和PostinstallRunnerAction,下面开始对其进行分析。

FilesystemVerifierAction

在数据下载完成后,在DownloadAction中会切换到FilesystemVerifierAction

void DownloadAction::TransferComplete(HttpFetcher* fetcher, bool successful) {
  if (writer_) {
   ........
  // Write the path to the output pipe if we're successful.
  if (code == ErrorCode::kSuccess && HasOutputPipe())
    SetOutputObject(install_plan_);
  processor_->ActionComplete(this, code);
}

最后的ActionComplete会开始执行FilesystemVerifierAction。

src/system/update_engine/payload_consumer/filesystem_verifer_action.cc

 1 void FilesystemVerifierAction::PerformAction() {2   // Will tell the ActionProcessor we've failed if we return.3   ScopedActionCompleter abort_action_completer(processor_, this);4 5   if (!HasInputObject()) {6     LOG(ERROR) << "FilesystemVerifierAction missing input object.";7     return;8   }9   install_plan_ = GetInputObject();   //获取上一个Action传过来的install_plan_
10 
11   if (install_plan_.partitions.empty()) {
12     LOG(INFO) << "No partitions to verify.";
13     if (HasOutputPipe())
14       SetOutputObject(install_plan_);
15     abort_action_completer.set_code(ErrorCode::kSuccess);
16     return;
17   }
18 
19   StartPartitionHashing();      //开始计算分区的hash
20   abort_action_completer.set_should_complete(false);
21 }

  接着看StartPartitionHashing

 1 void FilesystemVerifierAction::StartPartitionHashing() {2   if (partition_index_ == install_plan_.partitions.size()) {       //判断是否验证到了最后一个分区3     Cleanup(ErrorCode::kSuccess);4     return;5   }6   InstallPlan::Partition& partition =7       install_plan_.partitions[partition_index_];8 9   string part_path;         
10   switch (verifier_step_) {                    //默认值是KVerifyTargetHash
11     case VerifierStep::kVerifySourceHash:
12       part_path = partition.source_path;
13       remaining_size_ = partition.source_size;
14       break;
15     case VerifierStep::kVerifyTargetHash:
16       part_path = partition.target_path;         //分区的路径
17       remaining_size_ = partition.target_size;   //大小
18       break;
19   }
20   LOG(INFO) << "Hashing partition " << partition_index_ << " ("
21             << partition.name << ") on device " << part_path;
22   if (part_path.empty())
23     return Cleanup(ErrorCode::kFilesystemVerifierError);
24 
25   brillo::ErrorPtr error;
26   src_stream_ = brillo::FileStream::Open(             //打开对应的分区文件
27       base::FilePath(part_path),
28       brillo::Stream::AccessMode::READ,
29       brillo::FileStream::Disposition::OPEN_EXISTING,
30       &error);
31 
32   if (!src_stream_) {
33     LOG(ERROR) << "Unable to open " << part_path << " for reading";
34     return Cleanup(ErrorCode::kFilesystemVerifierError);
35   }
36 
37   buffer_.resize(kReadFileBufferSize);   //重置缓存区的大小
38   read_done_ = false;                    //未被读取完成
39   hasher_.reset(new HashCalculator());   //设置HashCalculator
40 
41   // Start the first read.
42   ScheduleRead();               //开始读取
43 }

 首先判断是否验证的分区的所有hash,如果验证完成了,调用CleanUp做最后的工作。

CleanUp

 1 void FilesystemVerifierAction::Cleanup(ErrorCode code) {2   src_stream_.reset();3   // This memory is not used anymore.4   buffer_.clear();5 6   if (cancelled_)7     return;8   if (code == ErrorCode::kSuccess && HasOutputPipe())9     SetOutputObject(install_plan_);
10   processor_->ActionComplete(this, code);
11 }

可以看到主要就是清空缓存区,设置install_plan_,切换到下一个Action。如果没有验证完成,就获取要验证的分区路径和大小,这个大小只是要验证的大小,不一定是分区的真正大小。对于镜像文件而言1G的大小能被安装在2G的分区上。接下来调用ScheduleRead()开始进行验证。

ScheduleRead()

 1 void FilesystemVerifierAction::ScheduleRead() {2   size_t bytes_to_read = std::min(static_cast<int64_t>(buffer_.size()), 3                                   remaining_size_);  //获取要读取数据的大小4   if (!bytes_to_read) {   //读取完成5     OnReadDoneCallback(0);6     return;7   }8 9   bool read_async_ok = src_stream_->ReadAsync(
10     buffer_.data(),
11     bytes_to_read,
12     base::Bind(&FilesystemVerifierAction::OnReadDoneCallback,
13                base::Unretained(this)),
14     base::Bind(&FilesystemVerifierAction::OnReadErrorCallback,
15                base::Unretained(this)),
16     nullptr);  //开始读取
17 
18   if (!read_async_ok) {
19     LOG(ERROR) << "Unable to schedule an asynchronous read from the stream.";
20     Cleanup(ErrorCode::kError);
21   }
22 }

获取读取数据的真实大小,开始读取数据。

 1 void FilesystemVerifierAction::OnReadDoneCallback(size_t bytes_read) {2   if (bytes_read == 0) {        //读取完成3     read_done_ = true;4   } else {5     remaining_size_ -= bytes_read;  6     CHECK(!read_done_);                                     7     if (!hasher_->Update(buffer_.data(), bytes_read)) {   //计算hash8       LOG(ERROR) << "Unable to update the hash.";9       Cleanup(ErrorCode::kError);
10       return;
11     }
12   }
13 
14   // We either terminate the current partition or have more data to read.
15   if (cancelled_)
16     return Cleanup(ErrorCode::kError);
17 
18   if (read_done_ || remaining_size_ == 0) {
19     if (remaining_size_ != 0) {
20       LOG(ERROR) << "Failed to read the remaining " << remaining_size_
21                  << " bytes from partition "
22                  << install_plan_.partitions[partition_index_].name;
23       return Cleanup(ErrorCode::kFilesystemVerifierError);
24     }
25     return FinishPartitionHashing();   //计算完成后
26   }
27   ScheduleRead();   //如果没有计算完成,继续计读取计算
28 }

在这个方法中会对读取的数据进行hash计算,每次计算其实都是基于前一次的计算结果来进行的,不然就会有太对的数据加载到内存中,导致内存不足。当计算完成后

 1 void FilesystemVerifierAction::FinishPartitionHashing() {2   if (!hasher_->Finalize()) {3     LOG(ERROR) << "Unable to finalize the hash.";4     return Cleanup(ErrorCode::kError);5   }6   InstallPlan::Partition& partition =7       install_plan_.partitions[partition_index_];8   LOG(INFO) << "Hash of " << partition.name << ": "9             << Base64Encode(hasher_->raw_hash()); 
10 
11   switch (verifier_step_) {
12     case VerifierStep::kVerifyTargetHash:
13       if (partition.target_hash != hasher_->raw_hash()) {   //对保存的targethash和计算得到的hash进行一个比较
14         LOG(ERROR) << "New '" << partition.name
15                    << "' partition verification failed.";
16         if (partition.source_hash.empty()) {
17           // No need to verify source if it is a full payload.
18           return Cleanup(ErrorCode::kNewRootfsVerificationError);
19         }
20         // If we have not verified source partition yet, now that the target
21         // partition does not match, and it's not a full payload, we need to
22         // switch to kVerifySourceHash step to check if it's because the source
23         // partition does not match either.
24         verifier_step_ = VerifierStep::kVerifySourceHash;  //计算source hash
25       } else {
26         partition_index_++;   //计算下一个分区
27       }
28       break;
29     case VerifierStep::kVerifySourceHash:
30       if (partition.source_hash != hasher_->raw_hash()) {  //保存的source hash和计算得到的也不相同
31         LOG(ERROR) << "Old '" << partition.name
32                    << "' partition verification failed.";
33         LOG(ERROR) << "This is a server-side error due to mismatched delta"
34                    << " update image!";
35         LOG(ERROR) << "The delta I've been given contains a " << partition.name
36                    << " delta update that must be applied over a "
37                    << partition.name << " with a specific checksum, but the "
38                    << partition.name
39                    << " we're starting with doesn't have that checksum! This"
40                       " means that the delta I've been given doesn't match my"
41                       " existing system. The "
42                    << partition.name << " partition I have has hash: "
43                    << Base64Encode(hasher_->raw_hash())
44                    << " but the update expected me to have "
45                    << Base64Encode(partition.source_hash) << " .";
46         LOG(INFO) << "To get the checksum of the " << partition.name
47                   << " partition run this command: dd if="
48                   << partition.source_path
49                   << " bs=1M count=" << partition.source_size
50                   << " iflag=count_bytes 2>/dev/null | openssl dgst -sha256 "
51                      "-binary | openssl base64";
52         LOG(INFO) << "To get the checksum of partitions in a bin file, "
53                   << "run: .../src/scripts/sha256_partitions.sh .../file.bin";
54         return Cleanup(ErrorCode::kDownloadStateInitializationError);
55       }
56       // The action will skip kVerifySourceHash step if target partition hash
57       // matches, if we are in this step, it means target hash does not match,
58       // and now that the source partition hash matches, we should set the error
59       // code to reflect the error in target partition.
60       // We only need to verify the source partition which the target hash does
61       // not match, the rest of the partitions don't matter.
62       return Cleanup(ErrorCode::kNewRootfsVerificationError);
63   }
64   // Start hashing the next partition, if any.
65   hasher_.reset();   //重置hash计算器
66   buffer_.clear();  //清空缓存
67   src_stream_->CloseBlocking(nullptr);
68   StartPartitionHashing(); //接着计算
69 }

 可见当一个分区的hash被计算出来的时候就会根据保存好的进行比较,如果target的hash不一致就会转向比较该分区的source hash,其实比较source hash主要就是为了确定错误的类型,只要target hash不一致,无论source hash是否一致都不会继续下一个分区的计算了。就这样一直到最后一个分区验证完后,执行最后一个Action,PostinstallRunnerAction。

PostinstallRunnerAction

PostinstallRunnerAction执行每个分区更新完后的postinstall script。但是在高通平台的,android8.0上无论是全包还是差分包升级并没有实质性的postinstall script。在PostinstallRunnerAction中仅仅是将target_slot标记为active状态。目前只分析于执行相关的代码。

src/system/update_engine/payload_consumer/postinstall_runner_action.cc

 1 void PostinstallRunnerAction::PerformAction() {2   CHECK(HasInputObject());3   install_plan_ = GetInputObject();   //获取install_plan_4 5   if (install_plan_.powerwash_required) {    //是否需要进行数据的擦除6     if (hardware_->SchedulePowerwash()) {7       powerwash_scheduled_ = true;8     } else {9       return CompletePostinstall(ErrorCode::kPostinstallPowerwashError);
10     }
11   }
12 
13   // Initialize all the partition weights.
14   partition_weight_.resize(install_plan_.partitions.size());  //初始化每个分区的权重
15   total_weight_ = 0;
16   for (size_t i = 0; i < install_plan_.partitions.size(); ++i) {
17     // TODO(deymo): This code sets the weight to all the postinstall commands,
18     // but we could remember how long they took in the past and use those
19     // values.
20     partition_weight_[i] = install_plan_.partitions[i].run_postinstall;
21     total_weight_ += partition_weight_[i];  //计算总的权重
22   }
23   accumulated_weight_ = 0;
24   ReportProgress(0);                      //更新进度
25 
26   PerformPartitionPostinstall();          //开始真正的流程
27 }

来看PerformPartitionPostinstall()

 1 void PostinstallRunnerAction::PerformPartitionPostinstall() {2   if (install_plan_.download_url.empty()) {3     LOG(INFO) << "Skipping post-install during rollback";4     return CompletePostinstall(ErrorCode::kSuccess);5   }6 7   // Skip all the partitions that don't have a post-install step.8   while (current_partition_ < install_plan_.partitions.size() &&9          !install_plan_.partitions[current_partition_].run_postinstall) {   //run_postinstall为false
10     VLOG(1) << "Skipping post-install on partition "
11             << install_plan_.partitions[current_partition_].name;
12     current_partition_++;
13   }
14   if (current_partition_ == install_plan_.partitions.size())
15     return CompletePostinstall(ErrorCode::kSuccess);
16   ...................
17   ...................
18   ...................
19 }

在当前分析中run_postinstall为false,会跳过post-install。之后会直接执行CompletePostinstall(ErrorCode::kSuccess)

 1 void PostinstallRunnerAction::CompletePostinstall(ErrorCode error_code) {2   // We only attempt to mark the new slot as active if all the postinstall3   // steps succeeded.4   if (error_code == ErrorCode::kSuccess &&5       !boot_control_->SetActiveBootSlot(install_plan_.target_slot)) {   //设置target_slot为active6     error_code = ErrorCode::kPostinstallRunnerError;7   }8 9   ScopedActionCompleter completer(processor_, this);
10   completer.set_code(error_code);
11 
12   if (error_code != ErrorCode::kSuccess) {
13     LOG(ERROR) << "Postinstall action failed.";
14 
15     // Undo any changes done to trigger Powerwash.
16     if (powerwash_scheduled_)
17       hardware_->CancelPowerwash();
18 
19     return;
20   }
21 
22   LOG(INFO) << "All post-install commands succeeded";
23   if (HasOutputPipe()) {                      //设置输出的install_plan
24     SetOutputObject(install_plan_);
25   }
26 }

最终将target_slot设置为active在重启之后就会从target_slot开始启动了。

分析到这里就算是对update_engine的核心过程有了个大概的了解,除了对升级的知识点的认识,还体会到了它的架构。不足之处就是还有很多的细节未涉及。

相关文章:

update_engine-FilesystemVerifierAction和PostinstallRunnerAction

在介绍完了DownloadAction之后&#xff0c;还剩下FilesystemVerifierAction和PostinstallRunnerAction&#xff0c;下面开始对其进行分析。 FilesystemVerifierAction 在数据下载完成后&#xff0c;在DownloadAction中会切换到FilesystemVerifierAction void DownloadAction:…...

深度学习乳腺癌分类 计算机竞赛

文章目录 1 前言2 前言3 数据集3.1 良性样本3.2 病变样本 4 开发环境5 代码实现5.1 实现流程5.2 部分代码实现5.2.1 导入库5.2.2 图像加载5.2.3 标记5.2.4 分组5.2.5 构建模型训练 6 分析指标6.1 精度&#xff0c;召回率和F1度量6.2 混淆矩阵 7 结果和结论8 最后 1 前言 &…...

【Python百宝箱】掌握Python Web开发三剑客:Flask、Django、FastAPI一网打尽

前言 在当今互联网时代&#xff0c;Web应用的开发变得愈发重要和复杂。选择一个合适的Web框架&#xff0c;掌握安全性与认证、数据库与ORM库、前端框架与交互、测试与调试工具等关键知识点&#xff0c;是每个Web开发者都必须面对的挑战。本文将带你深入了解三个流行的Python W…...

【人工智能时代的刑法体系与责任主体概述】

第一节&#xff1a;引言 随着科技的快速发展&#xff0c;人工智能 (Artificial Intelligence, AI) 正日益成为我们生活中不可或缺的一部分。从自动驾驶汽车到语音助手&#xff0c;从智能家居到金融机器人&#xff0c;AI 的广泛应用正不断改变着我们的生活方式和社会结构。然而…...

透视maven打包编译正常,intellj idea编译失败问题的本质

前言 maven多模块类型的项目&#xff0c;在Java的中大型应用中非常常见&#xff0c; 在 module 很多的情况&#xff0c;经常会出现各种各样的编辑依赖错误问题&#xff0c;今天记录一种比较常见的 case &#xff1a; A 子模块依赖 B 子模块&#xff0c;在 Terminal 上终端上 …...

npm报错

npm报错 npm ERR! Fix the upstream dependency conflict, or retry npm ERR! this command with --force or --legacy-peer-deps npm ERR! to accept an incorrect (and potentially broken) dependency resolution. npm ERR! npm ERR! npm ERR! For a full report s…...

【FFmpeg实战】ffmpeg播放器-音视频解码流程

音视频介绍 音视频解码流程 FFmpeg解码的数据结构说明 AVFormatContext&#xff1a;封装格式上下文结构体,全局结构体,保存了视频文件封装格式相关信息AVInputFormat&#xff1a;每种封装格式&#xff0c;对应一个该结构体AVStream[0]&#xff1a;视频文件中每个视频&#xff…...

基于SSM的高校毕业选题管理系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…...

一个简单的Oracle Redaction实验

本实验包含了&#xff1a; 简单的Oracle Redaction演示针对指定用户的Redaction 实验环境 假设有一个19c多租户数据库&#xff0c;PDB名为orclpdb1。 我们将在orclpdb1中建立2个用户&#xff1a; redact_user: redact管理员schema_user: schema用户 基础实验 首先进入数…...

getchar函数的功能有哪些

getchar函数是C语言标准库中的一个函数&#xff0c;主要用于从标准输入&#xff08;通常是键盘&#xff09;获取一个字符。它的功能包括&#xff1a; 从标准输入获取一个字符&#xff1a;getchar函数会等待用户输入一个字符&#xff0c;然后将其返回给程序。可以通过控制台输入…...

信息机房监控系统(动环辅助监控系统)

信息机房监控系统是一个综合性的系统&#xff0c;用于对机房的所有设备及其环境进行集中监控和管理。这种系统主要针对机房的各个子系统进行监控&#xff0c;包括动力系统、环境系统、消防系统、保安系统、网络系统等。 依托电易云-智慧电力物联网&#xff0c;以下是信息机房监…...

最强英文开源模型Llama2架构与技术细节探秘

prerequisite: 最强英文开源模型LLaMA架构探秘&#xff0c;从原理到源码 Llama2 Meta AI于2023年7月19日宣布开源LLaMA模型的二代版本Llama2&#xff0c;并在原来基础上允许免费用于研究和商用。 作为LLaMA的延续和升级&#xff0c;Llama2的训练数据扩充了40%&#xff0c;达到…...

编程刷题网站以及实用型网站推荐

1、牛客网在线编程 牛客网在线编程https://www.nowcoder.com/exam/oj?page1&tab%E8%AF%AD%E6%B3%95%E7%AF%87&topicId220 2、力扣 力扣https://leetcode.cn/problemset/all/ 3、练码 练码https://www.lintcode.com/ 4、PTA | 程序设计类实验辅助教学平台 PTA | 程…...

基于STC12C5A60S2系列1T 8051单片机的SPI总线器件数模芯片TLC5615实现数模转换应用

基于STC12C5A60S2系列1T 8051单片的SPI总线器件数模芯片TLC5615实现数模转换应用 STC12C5A60S2系列1T 8051单片机管脚图STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式及配置STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式介绍SPI总线器件数模芯片TLC5615介绍通过按…...

【并发编程】Synchronized的使用

&#x1f4eb;作者简介&#xff1a;小明java问道之路&#xff0c;2022年度博客之星全国TOP3&#xff0c;专注于后端、中间件、计算机底层、架构设计演进与稳定性建设优化&#xff0c;文章内容兼具广度、深度、大厂技术方案&#xff0c;对待技术喜欢推理加验证&#xff0c;就职于…...

【Python】Python基础

文章目录 一、字面值常量和表达式二、变量2.1 定义变量2.2 变量的命名规则2.3 变量的类型2.4 不同类型大小2.5 动态类型 三、注释四、输入与输出五、运算符5.1 算术运算符5.2 关系运算符5.3 逻辑运算符5.4 赋值运算符 一、字面值常量和表达式 print(1 2 * 3) # 7 print(1 2 …...

gitlab环境准备

1.准备环境 gitlab只支持linux系统&#xff0c;本人在虚拟机下使用Ubuntu作为操作系统&#xff0c;gitlab镜像要使用和操作系统版本对应的版本&#xff0c;(ubuntu18.04,gitlab-ce_13.2.3-ce.0_amd64 .deb) book100ask:/$ lsb_release -a No LSB modules are available. Dist…...

Apache Doris (五十四): Doris Join类型 - Bucket Shuffle Join

🏡 个人主页:IT贫道_大数据OLAP体系技术栈,Apache Doris,Clickhouse 技术-CSDN博客 🚩 私聊博主:加入大数据技术讨论群聊,获取更多大数据资料。 🔔 博主个人B栈地址:豹哥教你大数据的个人空间-豹哥教你大数据个人主页-哔哩哔哩视频 目录...

【AI】行业消息精选和分析(23-11-20)

技术发展 &#x1f3a8; LCM即时绘画&#xff0c;体验所见所得&#xff1a; - LCM LoRA支持即时绘图生成&#xff0c;体验直观。 - 在线体验地址提供直接访问。 - 清华大学SimianLuo开发&#xff0c;加速稳定扩散模型运行。 &#x1f48a; VM Pill&#xff1a;可吞咽装置追踪生…...

Matplotlib实现Label及Title都在下方的最佳姿势

Matplotlib实现Label及Title都在下方的最佳姿势 1. 问题背景2. 基本思想&#xff08;可以不看&#xff09;3. 方法封装4. 调用实例5. 总结6. 起飞 1. 问题背景 用python绘制下面这种图的时候&#xff0c;一般用xlable作为子图的标题&#xff0c;这是因为plt.title()方法绘制的…...

使用 uWSGI 部署 Django 应用详解

概要 部署 Django 应用到生产环境是一个至关重要的步骤&#xff0c;其中选择合适的 WSGI 服务器对于确保应用的稳定性和性能至关重要。uWSGI 是一个流行的选择&#xff0c;它不仅高效、轻量&#xff0c;还非常灵活。本文将详细介绍如何使用 uWSGI 来部署 Django 应用&#xff…...

MyBatis在注解中使用动态查询

以前为了使用注解并在注解中融入动态查询&#xff0c;会使用Provider。后来发现只要加入"<script>包含动态查询的SQL语句</script>"就可以了。 例如&#xff1a; Select("<script>" "select v.*,u.avatar,u.nickname from videos…...

百云齐鲁 | 云轴科技ZStack成功实践精选(山东)

山东省作为我国重要的工业基地和北方地区经济发展的战略支点&#xff0c;在“十四五”规划中将数字强省建设分为数字基础设施、数字科技、数字经济、数字政府、数字社会、数字生态六大部分&#xff0c;涵盖政治、经济、民生等多个方面&#xff0c;并将大数据、云计算、人工智能…...

【Electron】electron-builder打包失败问题记录

文章目录 yarn下载的包不支持require()winCodeSign-2.6.0.7z下载失败nsis-3.0.4.1.7z下载失败待补充... yarn下载的包不支持require() 报错内容&#xff1a; var stringWidth require(string-width)^ Error [ERR_REQUIRE_ESM]: require() of ES Module /stuff/node_modules/…...

OpenCV快速入门:直方图、掩膜、模板匹配和霍夫检测

文章目录 前言一、直方图基础1.1 直方图的概念和作用1.2 使用OpenCV生成直方图1.3 直方图归一化1.3.1 直方图归一化原理1.3.2 直方图归一化公式1.3.3 直方图归一化代码示例1.3.4 OpenCV内置方法&#xff1a;normalize()1.3.4.1 normalize()方法介绍1.3.4.2 normalize()方法参数…...

HDD与QLC SSD深度对比:功耗与存储密度的终极较量

在当今数据世界中&#xff0c;存储设备的选择对于整体系统性能和能耗有着至关重要的影响。硬盘HDD和大容量QLC SSD是两种主流的存储设备&#xff0c;而它们在功耗方面的表现是许多用户关注的焦点。 扩展阅读&#xff1a; 1.面对SSD的步步紧逼&#xff0c;HDD依然奋斗不息 2.…...

医疗软件制造商如何实施静态分析,满足 FDA 医疗器械网络安全验证

随着 FDA 对网络安全验证和标准提出更多要求&#xff0c;医疗软件制造商需要采用静态分析来确保其软件满足这些新的安全标准。继续阅读以了解如何实施静态分析来满足这些安全要求。 随着 FDA 在其软件验证指南中添加更多网络安全要求&#xff0c;医疗设备制造商可以转向静态分…...

【设计模式】聊聊策略模式

策略模式的本质是为了消除if 、else代码&#xff0c;提供拓展点&#xff0c;对拓展开放&#xff0c;对修改关闭&#xff0c;也就是说我们开发一个功能的时候&#xff0c;要尽量的采用设计模式进行将不变的东西进行抽取出来&#xff0c;将变化的东西进行隔离开来&#xff0c;这样…...

二维偏序问题

偏序 偏序(Partial Order)的概念: 设 A 是一个非空集,P 是 A 上的一个关系,若 P 满足下列条件: Ⅰ 对任意的 a ∈ A,(a, a) ∈ P;(自反性 reflexlve)Ⅱ 若 (a, b) ∈ P,且 (b, a) ∈ P,则 a = b;(反对称性,anti-symmentric)Ⅲ 若 (a, b) ∈ P,(b, c) ∈ P,则 (a,…...

解析Spring Boot中的CommandLineRunner和ApplicationRunner:用法、区别和适用场景详解

在Spring Boot应用程序中&#xff0c;CommandLineRunner和ApplicationRunner是两个重要的接口&#xff0c;它们允许我们在应用程序启动后执行一些初始化任务。本文将介绍CommandLineRunner和ApplicationRunner的区别&#xff0c;并提供代码示例和使用场景&#xff0c;让我们更好…...