Flutter Widget 生命周期 key探究

news2025/2/13 0:01:09

Widget

在Flutter中,一切皆是Widget(组件),Widget的功能是“描述一个UI元素的配置数据”,它就是说,Widget其实并不是表示最终绘制在设备屏幕上的显示元素,它只是描述显示元素的一个配置数据。

实际上,Flutter中真正代表屏幕上显示元素的类是 Element,也就是说Widget 只是描述 Element 的配置数据。并且一个 Widget 可以对应多个 Element,因为同一个 Widget 对象可以被添加到 UI树的不同部分,而真正渲染时,UI树的每一个 Element 节点都会对应一个 Widget 对象。

两种Widget模型

StatelessWidget

StatelessWidget用于不需要维护状态的场景,其对应的Element是StatelessElement
在这里插入图片描述
在这里插入图片描述

StatefulWidget

在这里插入图片描述
在这里插入图片描述
相反,StatefulWidget用于需要维护状态的场景,其对应的Element是StatefulElement,StatefulElement持有State
createState() 用于创建和StatefulWidget相关的状态,它在StatefulWidget的生命周期中可能会被多次调用。
例如,当一个StatefulWidget同时插入到widget树的多个位置时,Flutter framework就会调用该方法为每一个位置生成一个独立的State实例,其实,本质上就是一个StatefulElement对应一个State实例。

生命周期

理论

Flutter 中说的生命周期,是独指有状态组件(StatefulWidget)的生命周期,对于无状态组件生命周期只有一次 build 这个过程,也只会渲染一次,StatefulWidget生命周期图如下:
在这里插入图片描述
Flutter 中的生命周期,包含以下几个阶段:

  • createState :该函数为 StatefulWidget 中创建 State 的方法,当 StatefulWidget 被调用时会立即执行 createState 。
  • initState :该函数为 State 初始化调用,紧接着createState之后调用,可以在此期间执行 State 各变量的初始赋值,同时也可以在此期间与服务端交互
  • didChangeDependencies :第一种情况是StatefulElement mount时会回调,这种情况会紧跟initState被回调
    还有一种情况是当State对象的“依赖”发生变化时会被调用,这种依赖是指通过context.dependOnInheritedWidgetOfExactType进行的依赖
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • build :主要是返回需要渲染的 Widget ,由于 build 会被调用多次,因此在该函数中只能做返回 Widget 相关逻辑
  • reassemble, 在 debug 模式下,每次热重载都会调用该函数,因此在 debug 阶段可以在此期间增加一些 debug 代码,来检查代码问题。
  • didUpdateWidget ,在widget重新构建时,Flutter framework会调用Widget.canUpdate来检测Widget树中同一位置的新旧节点,然后决定是否需要更新,如果Widget.canUpdate返回true则会调用此回调。Widget.canUpdate会在新旧widget的key和runtimeType同时相等时会返回true,也就是说在在新旧widget的key和runtimeType同时相等时didUpdateWidget()就会被调用。父组件发生 build 的情况下,子组件该方法才会被调用,其次该方法调用之后一定会再调用本组件中的 build 方法。
  • deactivate ,在组件被移除节点后会被调用,如果该组件被移除节点,然后未被插入到其他节点时,则会继续调用 dispose 永久移除。
  • dispose ,永久移除组件,并释放组件资源。

Flutter 生命周期的整个过程可以分为四个阶段

  1. 初始化阶段:createState 和 initState
  2. 组件创建阶段:didChangeDependencies didUpdateWidget 和 build
  3. 组件销毁阶段:deactivate 和 dispose

实例

class LifeCycleTest extends StatefulWidget {
  final String TAG = "LifeCycleTest";

  
  State<StatefulWidget> createState() {
    print('$TAG createState');
    return LifeCycleTestState();
  }
}

class LifeCycleTestState extends State<LifeCycleTest> {
  
  void initState() {
    print('${widget.TAG} initState');
    super.initState();
  }

  
  void reassemble() {
    print('${widget.TAG} reassemble');
    super.reassemble();
  }

  
  void didChangeDependencies() {
    print('${widget.TAG} didChangeDependencies');
    super.didChangeDependencies();
  }

  
  void didUpdateWidget(covariant LifeCycleTest oldWidget) {
    print('${widget.TAG} didUpdateWidget');
    super.didUpdateWidget(oldWidget);
  }

  
  void deactivate() {
    print('${widget.TAG} deactivate');
    super.deactivate();
  }

  
  void dispose() {
    print('${widget.TAG} dispose');
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    print('${widget.TAG} build');
    return Scaffold(
      body: SafeArea(
        child: Column(
          children: [
            GestureDetector(
              onTap: () {
                setState(() {});
              },
              child: Container(
                width: 100,
                height: 100,
                color: Colors.red,
              ),
            ),
            MyInheritedWidget(LifeCycleTestChild(), 20)
          ],
        ),
      ),
    );
  }
}

class LifeCycleTestChild extends StatefulWidget {
  final String TAG = "LifeCycleTestChild";

  
  State<StatefulWidget> createState() {
    print('$TAG createState');
    return LifeCycleTestChildState();
  }
}

class LifeCycleTestChildState extends State<LifeCycleTestChild> {
  
  void initState() {
    print('${widget.TAG} initState');
    super.initState();
  }

  
  void reassemble() {
    print('${widget.TAG} reassemble');
    super.reassemble();
  }

  
  void didChangeDependencies() {
    print('${widget.TAG} didChangeDependencies');
    super.didChangeDependencies();
  }

  
  void didUpdateWidget(covariant LifeCycleTestChild oldWidget) {
    print('${widget.TAG} didUpdateWidget');
    super.didUpdateWidget(oldWidget);
  }

  
  void deactivate() {
    print('${widget.TAG} deactivate');
    super.deactivate();
  }

  
  void dispose() {
    print('${widget.TAG} dispose');
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    print('${widget.TAG} build');
    var dependOnInheritedWidgetOfExactType =
        context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();
    return Container(
      child: Text("${(dependOnInheritedWidgetOfExactType as MyInheritedWidget).count}"),
    );
  }
}

class MyInheritedWidget extends InheritedWidget {
  final int count;

  MyInheritedWidget(
    Widget child,
    this.count,
  ) : super(child: child);

  
  bool updateShouldNotify(covariant InheritedWidget oldWidget) {
    return true;
  }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
上面图说明了几个点

  • 第二个图:didChangeDependencies在widget第一次初始化的时候都会调用
  • 第二个图:LifeCycleTest组件发生build,LifeCycleTestChild子组件调用didUpdateWidget,自身并没有调用didUpdateWidget,第三个图,热加载后都调用了didUpdateWidget,说明了父组件发生 build 的情况下,子组件该方法才会被调用
  • 第三个图:热加载后,只有LifeCycleTestChild调用了didChangeDependencies,说明通过context.dependOnInheritedWidgetOfExactType进行的依赖会调用该方法

Getx生命周期

先看下Controller的集成层级
在这里插入图片描述
再看下对应类的定义
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
其中 GetxController只是有个 update 方法用于通知组件刷新。
在 DisposableInterface 中覆盖了onInit 方法,实际多干了一件事,就是监听第一帧回调,等第一帧回调过来之后再调用onReady
然后我们再看下这些生命周期分别是在什么时候调用的
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • onInit:组件在内存分配后会被马上调用,可以在这个方法对 controller 做一些初始化工作。
  • onReady:在 onInit 一帧后被调用,适合做一些导航进入的事件,例如对话框提示、SnackBar 或异步网络请求。
  • onClose:在 onDelete 方法前调用、用于销毁 controller 使用的资源,例如关闭事件监听,关闭流对象,或者销毁可能造成内存泄露的对象,例如 TextEditingController,AniamtionController。也适用于将数据进行离线持久化。
    [图片]
    所以有了 GetxController 的生命周期后,我们就可以完全替换掉 StatefulWidget 了。
    onInit 或 onReady替换 initState
    onClose 替换 dispose,比如关闭流

Key

key的作用是:控制Element树上的Element是否被复用
如果两个widget的runtimeType和key相等(用==比较),那么原本指向旧widge的element,它的指针会指向新的widget上(通过Element.update方法)。如果不相等,那么旧element会从树上移除,根据当前新的widget重新构建新element,并加到树上指向新widget。
在这里插入图片描述
在这里插入图片描述
基于Element的复用机制的解释

在Flutter中,Widget是不可变的,它仅仅作为配置信息的载体而存在,并且任何配置或者状态的更改都会导致Widget的销毁和重建,但好在Widget本身是非常轻量级的,因此实际耗费的性能很小。与之相反,RenderObject就不一样了,实例化一个RenderObject的成本是非常高的,频繁地实例化和销毁RenderObject对性能的影响非常大,因此为了高性能地构建用户界面,Flutter使用Element的复用机制来尽可能地减少RenderObject的频繁创建和销毁。当Widget改变的时候,Element会通过组件类型以及对应的Key来判断旧的Widget和新的Widget是否一致:

1、如果某一个位置的旧Widget和新Widget不一致,就会重新创建Element,重建Element的同时也重建了RenderObject;
2、如果某一个位置的旧Widget和新Widget一致,只是配置发生了变化,比如组件的颜色变了,此时Element就会被复用,而只需要修改Widget对应的Element的RenderObject中的颜色设置即可,无需再进行十分耗性能的RenderObject的重建工作。
在这里插入图片描述

在这里插入图片描述

分类

flutter 中的key总的来说分为以下两种:

  • 局部键(LocalKey):ValueKey、ObjectKey、UniqueKey
  • 全局键(GlobalKey):GlobalObjectKey

ValueKey

ValueKey是通过某个具体的Value值来做区分的Key,如下:

key:ValueKey(1),
key:ValueKey("2"),
key:ValueKey(true),
key:ValueKey(0.1),
key:ValueKey(Person()), // 自定义类实例

可以看到,ValueKey的值可以是任意类型,甚至可以是我们自定义的类的实例。判断2个ValueKey是否相等是根据里面的value是否来判断的,如果value是自定义类,则可以通过重写自定义类的操作符来实现

例如,现在有一个展示所有学生信息的ListView列表,每一项itemWidget所对应的学生对象均包含某个唯一的属性,例如学号、身份证号等,那么这个时候就可以使用ValueKey,其值就是对应的学号或者身份证号。

在这里插入图片描述

ObjectKey

ObjectKey的使用场景如下:
现有一个所有学生信息的ListView列表,每一项itemWidget对应的学生对象不存在某个唯一的属性(比如学号、身份证号),任一属性均有可能与另外一名学生重复,只有多个属性组合起来才能唯一的定位到某个学生,那么此时使用ObjectKey就最合适不过了。

ObjectKey判断两个Key是否相同的依据是:两个对象是否具有相同的内存地址,不论自定义对象是否重写了==运算符判断,均会被视为不同的Key

在这里插入图片描述

UniqueKey

顾名思义,UniqueKey是一个唯一键,不需要参数,并且每一次刷新都会生成一个新的Key。
一旦使用UniqueKey那么就不存在Element复用了

GlobalKey

GlobalKey是全局唯一的键,一般而言,GlobalKey有如下几种用途:

  • 获取配置、状态以及组件Element
    • _globalKey.currentWidget:获取当前组件的配置信息(存在widget树中)
    • _globalKey.currentState:获取当前组件的状态信息(存在Element树中)
    • _globalKey.currentContext:获取当前组件的Element
  • 实现组件的局部刷新
    将需要单独刷新的widget从复杂的布局中抽离出去,然后通过传GlobalKey引用,这样就可以通过GlobalKey实现跨组件的刷新了。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

key作用示例

一般情况下我们不使用key,程序也是能正常运行的,只有部分特殊情况下需要使用key,下面我们看一个例子

import 'dart:math';

import 'package:flutter/material.dart';

class PositionedTiles extends StatefulWidget {
  
  State<StatefulWidget> createState() => PositionedTilesState();
}

class PositionedTilesState extends State<PositionedTiles> {
  late List<Widget> tiles;

  
  void initState() {
    super.initState();
    tiles = [
      // StatefulColorfulTile(),
      // StatefulColorfulTile(),
      // StatefulColorfulTile(key: UniqueKey()),
      // StatefulColorfulTile(key: UniqueKey()),
      StatelessColorfulTile(),
      StatelessColorfulTile(),
    ];
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Column(
          children: tiles,
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.sentiment_very_satisfied),
        // child: Icon(Icons.sentiment_very_dissatisfied),
        onPressed: swapTiles,
      ),
    );
  }

  void swapTiles() {
    setState(() {
      tiles.insert(1, tiles.removeAt(0));
    });
  }
}

// ignore: must_be_immutable
class StatelessColorfulTile extends StatelessWidget {
  Color color = ColorUtil.randomColor();  //color属性直接在widget中

  
  Widget build(BuildContext context) {
    return Container(color: color, child: Padding(padding: EdgeInsets.all(70.0)));
  }
}

class StatefulColorfulTile extends StatefulWidget {
  StatefulColorfulTile({Key? key}) : super(key: key);

  
  State<StatefulWidget> createState() => StatefulColorfulTileState();
}

class StatefulColorfulTileState extends State<StatefulColorfulTile> {
  Color? color; //color属性在State中

  
  void initState() {
    super.initState();
    color = ColorUtil.randomColor();
    print('initState');
  }

  
  Widget build(BuildContext context) {
    return Container(color: color, child: Padding(padding: EdgeInsets.all(70.0)));
  }
}

class ColorUtil {
  static Color randomColor() {
    var red = Random.secure().nextInt(255);
    var greed = Random.secure().nextInt(255);
    var blue = Random.secure().nextInt(255);
    return Color.fromARGB(255, red, greed, blue);
  }
}

上面的代码效果如下,可以看到使用StatelessColorfulTile时,点击按钮后两个色块能成功交换:

当我们把代码换成下面这样
在这里插入图片描述
神奇的事情发生了,点击交换按钮没有任何反应
那在使用StatefulColorfulTile的前提下,如何让色块再次点击按钮后能发生交换呢?我猜聪明的你已经想到了,就是设置key属性,即把代码改成下面这个样子
在这里插入图片描述
下面我们来解释下为什么会出现这样的结果:

  1. 为什么StatelessWidget的能交换
    在这里插入图片描述
    在这里插入图片描述
    当代码调用PositionedTiles.setState交换两个Widget后,flutter会从上到下逐一对比Widget树和Element树中的每个节点,如果发现节点的runtimeType和key一致的话(这里没有key,因此只对比runtimeType),那么就认为该Element仍然是有效的,可用复用,于是只需要更改Element的指针,就可以直接复用
    对于StatelessWidget中的color信息是直接在widget中的,那widget重新build直接就更新了颜色

  2. 为啥StatefulColorfulTile要加key才能交换
    StatefulWidget的color属性是放在State中的,我们上面说过State被Element管理
    我们先看下不带key时的树结构

首先还是Widget更新后,flutter会根据runtimeType和key比较Widget从而判断是否需要重新构建Element,这里key为空,只比较runtimeType,比较结果必然相等,所以Element直接复用。
StatefulColorfulTile在重新渲染时,Color属性不再是从Widget对象(即自身)里获取,而是从Element的State里面获取,而Element根本没发生变化,所以取到的Color也没有变化,最终就算怎么渲染,颜色都是不变的,视觉效果上也就是两个色块没有交换了。
接着看有了key之后的树结构

交换前:
在这里插入图片描述

交换后,发现两边key不相等,于是尝试在Element 列表里面查找是否还有相同的key的Element,发现有,于是重新排列Element让相同key的配对
在这里插入图片描述
rebuild后,Element已交换,重新渲染后视觉上就看到两个色块交换位置了:
在这里插入图片描述
在这种加了key又交换位置的情况下,Element和widget都是直接复用的,所以点击交换位置,widget没有触发build方法,原因在于canUpdate方法返回false,didUpdateWidget也没有回调,build方法也不会被触发

接下来我们在原来的demo上做些小改动,在要交换的2个Widget外面分别套上Padding,我们看下效果:
在这里插入图片描述
我们发现每次点击交换位置,2个Widget都变成了新的颜色,即两个 Widget 的 Element 并不是交换顺序,而是被重新创建了

当交换子节点的位置时,Flutter 的 element-to-widget 匹配逻辑一次只会检查树的一个层级。
在Column这一层级,padding 部分的 runtimeType 并没有改变,且不存在 Key。Element复用,然后再比较下一个层级。由于内部的 StatefulColorfulTile 存在 key,且现在的层级在 padding 内部,该层级没有多子 Widget。canUpdate 返回 flase,Flutter 将会认为这个 Element 需要被替换。然后重新生成一个新的 Element 对象装载到 Element 树上替换掉之前的 Element。第二个 Widget 同理。

所以为了解决这个问题,我们需要将 key 放到 Padding 的 这一层级就可以了

根据上面的例子我们能了解到:如果要在有状态的、类型相同、同一层级的 widget 集合上进行添加、删除、排序等操作,可能需要使用到 key。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

分库分表 21 招

&#xff08;一&#xff09;好好的系统&#xff0c;为什么要分库分表&#xff1f; 咱们先介绍下在分库分表架构实施过程中&#xff0c;会接触到的一些通用概念&#xff0c;了解这些概念能够帮助理解市面上其他的分库分表工具&#xff0c;尽管它们的实现方法可能存在差异&#…

自动化测试框架seldom

创建项目 | seldom文档 这个框架还是不错的&#xff0c;一直在优化&#xff0c;测试框架里的功能这里都有了。 seldom继承unittest单元测试框架&#xff0c;可以用来做UI和接口自动化项目。 安装 pip install seldom 创建项目 > seldom -P mypro 创建测试用例 # tes…

第8章 维护

文章目录 第8章 维护一、软件交付使用的工作二、软件交付使用的方式1) 直接方式2) 并行方式3) 逐步方式 8.1 软件维护的定义1、软件维护的定义2、软件维护的原因3、软件维护的类型1、改正性维护2、适应性维护3、完善性维护4、预防性维护 8.2 软件维护的特点8.2.1结构化维护和非…

12.异常-Exception|Java学习笔记

文章目录 异常介绍异常体系图一览运行时异常编译异常异常处理异常处理的方式try-catch 异常处理throws 异常处理注意事项和使用细节 自定义异常自定义异常的步骤 throw和throws的区别 异常介绍 基本概念&#xff1a;Java语言中&#xff0c;将程序执行中发生的不正常情况称为“…

【TCP/IP】多进程服务器的实现(进阶) - 多进程服务器模型及代码实现

经过前面的铺垫&#xff0c;我们已经具备实现并发服务器的基础了&#xff0c;接下来让我们尝试将之前的单任务回声服务器改装成多任务并发模式吧&#xff01; 多任务回声服务器模型 在编写代码前&#xff0c;先让我们大致将多任务&#xff08;回声&#xff09;服务器的模型抽象…

mac下部署和访问 Kubernetes 仪表板(Dashboard)

简介 Dashboard 是基于网页的 Kubernetes 用户界面。 你可以使用 Dashboard 将容器应用部署到 Kubernetes 集群中&#xff0c;也可以对容器应用排错&#xff0c;还能管理集群资源。 你可以使用 Dashboard 获取运行在集群中的应用的概览信息&#xff0c;也可以创建或者修改 Kub…

技术分享——隐私计算简介

随着数据规模的不断扩大和网络技术的快速发展&#xff0c;数据安全和隐私保护成为了热门的话题。隐私计算作为一种新兴的数据安全和隐私保护技术&#xff0c;为数据安全和隐私泄露问题提供了新的思路和方法。 2020年10月19日&#xff0c;Gartner发布2021年前沿战略科技趋势&am…

VTK源码编译安装记录与教程(VS2019+QT5.15.2+PCL1.12.1+VTK9.1.0配置,超详细)

因为PCL库&#xff08;傻瓜式安装&#xff09;中自动安装的VTK库并不完整&#xff0c;不支持QT环境的UI界面开发&#xff0c;于是&#xff0c;想用QT在VS2019上开发图形界面程序&#xff0c;需要单独自己通过VTK源码编译安装&#xff0c;再进行配置。本人安装时开发环境已经装好…

2023拒绝行业内卷!八年软件测试月薪30K*16薪行业心得 想入行必看

目前工作做软件测试工作8年&#xff0c;属于高级测试员那个级别吧&#xff01;现在看到各行各业的人都在转行学习软件测试&#xff0c;想给大家一些学习建议和忠告。 很多粉丝都跟我说今年行情很差&#xff0c;找不到工资&#xff0c;真的找不到工作了吗&#xff1f; 我们常在网…

simhash原理以及用python3实现simhash算法详解(附python3源码)

1. 为什么需要Simhash? 传统相似度算法:文本相似度的计算,一般使用向量空间模型(VSM),先对文本分词,提取特征,根据特征建立文本向量,把文本之间相似度的计算转化为特征向量距离的计算,如欧式距离、余弦夹角等。 缺点:大数据情况下复杂度会很高。 Simhash应用场景:…

Graph Learning笔记 - 长尾分布问题

Graph Learning笔记 - 长尾分布问题 分享四篇论文入门图神经网络时的学习笔记。 SL-DSGCN 论文&#xff1a;Investigating and Mitigating Degree-Related Biases in Graph Convolutional Networks 来源&#xff1a;2020CIKM 概要 GCN在图的半监督学习上能取得良好表现&a…

lora,固定模特+固定衣服,怎么实现?

在电商行业&#xff0c;经常会有一个需求&#xff0c;就是把固定的衣服让模型穿上&#xff0c;然后拍很多的图片&#xff0c;放在商品主图、详情页、买家秀...... 人工智能发展到现在&#xff0c;最近aigc也挺热门的&#xff0c;有没有办法用“人工智能”算法就实现这个功能&a…

逆向汇编反汇编——函数分析

add esp,8就是把esp调整到函数调用之前的状态&#xff0c;用以平衡堆栈 默认采用的是cdcall&#xff1a;外平栈 stdcall:内平栈 什么是堆栈平衡&#xff1f; 》原来的堆栈是什么样的&#xff0c;函数调用之后堆栈还是什么样的(即&#xff0c;esp和ebp的值保持不变)&#xf…

掌握Scala数据结构(2)MAP、TUPLE、SET

一、映射 (Map) &#xff08;一&#xff09;不可变映射 1、创建不可变映射 创建不可变映射mp&#xff0c;用键->值的形式 创建不可变映射mp&#xff0c;用(键, 值)的形式 注意&#xff1a;Map是特质&#xff08;Scala里的trait&#xff0c;相当于Java里的interface&#…

git选择指定分支中的指定目录进行合并

指定路径合并 先进入branch A &#xff1a; git checkout branchA 将dir2中的变更转移至branchA&#xff1a; git checkout branchB dir2 所有变更将出现在branchA中的dir2中&#xff0c;检查后提交即可。 git commit -m "sync branchB dir2 to branchA" 也可以…

读财报丨第二增长曲线渐显,但涂鸦智能的未来还看PaaS业务?

2022年以来&#xff0c;全球物联网行业持续低迷&#xff0c;赛道内不少玩家出现业绩下滑&#xff0c;而且陆续传出巨头企业关停相关业务板块的消息。这背后是消费电子行业库存积压&#xff0c;客户需求难以释放等宏观因素&#xff0c;导致IoT领域面临一定的经营困境。 近日&am…

STM32——07-STM32定时器Timer

定时器介绍 软件定时 缺点&#xff1a;不精确、占用 CPU 资源 void Delay500ms () //11.0592MHz { unsigned char i , j , k ; _nop_ (); i 4 ; j 129 ; k 119 ; do { do { while ( -- k ); } while ( -- j ); } while ( -- i ); } 定时器工…

108-Spring的底层原理(下篇)

这里续写上一章博客&#xff08;107章博客&#xff09;&#xff1a; Spring 声明式事务的支持&#xff1a; 编程式事务&#xff1a;在业务代码中添加事务控制代码&#xff0c;这样的事务控制机制就叫做编程式事务 声明式事务&#xff1a;通过xml或者注解配置的方式达到事务…

【Linux】进程间通信(管道)

文章目录 进程通信的目的进程间通信发展进程间通信分类管道System V IPCPOSIX IPC 管道什么是管道管道的读写规则管道的特点&#xff1a;匿名管道处理退出问题命名管道创建一个命名管道匿名管道与命名管道的区别命名管道的打开规则 进程通信的目的 数据传输&#xff1a;一个进程…

应用层协议 —— websocket

websocket介绍 websocket是从HTML5开始支持的一种网页端和服务端保持长连接的消息推送机制。 传统的web程序都是属于“一问一答”的形式&#xff0c;即客户端给服务器发送了一个HTTP请求&#xff0c;服务器给客户端返回一个HTTP响应。这种情况下服务器属于被动的一方&#xff…