目录

1、什么是IOC

2、java实现创建对象的方式有哪些

3、基于配置文件的di实现

3.1、什么是di

3.2、入门案例

3.3、环境搭建

接口和实现类

ioc配置文件

测试程序

3.4、案例总结

3.5、简单类型属性的赋值(set注入)

set注入要求

JavaBean

spring配置文件

3.6、非简单类型属性的赋值(set注入)

3.7、构造注入

byName形式

byType形式

3.8、基于注解的di实现

3.8.1、简单类型的注解di实现

3.8.2、引用类型的注解di实现

学生类

School类


1、什么是IOC

IoC (Inversion of Control) : 控制反转, 是一个理论,概念,思想。把对象的创建,赋值,管理工作都交给代码之外的容器实现, 也就是对象的创建是有其它外部资源完成,这样做实现了与解耦合。
正转:对象的创建、赋值等操作交由程序员手动完成,即使用类似new Xxx(Xxx Xxx)、Xxx.setXxx()语句完成对象的创建与赋值,缺点是一旦程序功能发生改变,涉及到的类就要修改代理,耦合度高,不便于维护和管理。
反转:对象的创建、赋值等操作交由代码之外的容器实现,有容器代替程序员完成对象的创建、赋值;且当程序功能发生变化时,只需要修改容器的配置文件即可。

2、java实现创建对象的方式有哪些

1、构造方法:new student()
2、反射
3、序列化
4、动态代理
5、容器:tomcat容器、ioc容器

 其实在以前我们已经接触过了容器创建对象的场景,还记得tomcat服务器吗,在tomcat启动时会实例化servletContext上下文对象;在发出请求时,相应的servlet对象也不是由开发人员进行实例化的,而是在tomcat内部由tomcat容器实例化的,回忆一下在学习javaweb的时候,我们有写过类似new XxxServlet()这样的代码吗,现在想必大家对容器有一个大概的概念了吧。        

3、基于配置文件的di实现

3.1、什么是di

DI(Dependency Injection) :依赖注入, 只需要在程序中提供要使用的对象名称就可以, 至于对象如何在容器中创建, 赋值,查找都由容器内部实现。
DI是ioc技术的实现方式(即容器如何创建对象这一问题的实现方式)

3.2、入门案例

使用ioc容器创建对象,调用对象的方法

3.3、环境搭建

创建maven项目,目前都是javase项目,推荐使用骨架,选择quickstart
加入maven依赖:分别是spring依赖、junit依赖
创建类(接口和它的实现类)
创建spring需要使用的配置文件
测试

接口和实现类

//接口
public interface SomeService {void doSome();
}
//实现类
public class SomeServiceImpl implements SomeService {//无参构造public SomeServiceImpl() {System.out.println("SomeServiceImpl类的无参构造执行了...");}@Override public void doSome() {System.out.println("执行了SomeServiceImpl的doSome()方法");}
}

ioc配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><!--声明bean(告诉spring要创建某个类的对象)1、id:自定义名称,唯一值,spring通过该id的属性值找到对象2、class:要创建类的全限定类名3、下述的声明语句在spring底层类似与执行了以下代码:SomeService service = new SomeServiceImpl();4、对象的保存:spring将对象保存到内部的map中,map.put(id值,对象)map.put("someService",new SomeServiceImpl())5、一个bean标签声明一个java对象6、spring容器根据bean标签创建对象,尽管存在class属性相同的bean标签,只要是id值不同,spring容器就会创建该class的对象--><bean id="someService" class="com.mms.service.impl.SomeServiceImpl"/><bean id="someService2" class="com.mms.service.impl.SomeServiceImpl"/><!--spring容器也可以创建非自定义类的对象,例如java.lang.String类的对象,只要指定了class属性,spring容器就可以创建该类的对象--><bean id="myString" class="java.lang.String"/>
</beans>

测试程序

//使用spring容器创建对象@Testpublic void test02() {//1、指定spring配置文件的名称String config = "beans.xml";//2、创建表示spring容器的对象 ApplicationContext//ClassPathXmlApplicationContext:表示从类路径中加载spring配置文件ApplicationContext ac = new ClassPathXmlApplicationContext(config);//3、从容器中获取对象SomeService service = (SomeService)ac.getBean("someService");//4、调用方法service.doSome();}/*** 测试spring容器创建对象的时机*  在创建spring容器时,会创建配置文件中的所有对象*/@Testpublic void test03() {//1、指定spring配置文件路径String config = "beans.xml";//2、创建spring容器ApplicationContext ac = new ClassPathXmlApplicationContext(config);/*** 测试输出结果:* SomeServiceImpl类的无参构造执行了...* SomeServiceImpl类的无参构造执行了...* 验证了spring调用类的无参构造完成对象的创建*/}//获取spring容器中java对象的信息@Testpublic void test04() {String config = "beans.xml";ApplicationContext ac = new ClassPathXmlApplicationContext(config);//获取spring容器中对象的个数int beansCount = ac.getBeanDefinitionCount();System.out.println("spring容器中的对象个数="+beansCount);//获取spring容器中对象的名称(即bean标签的id值)String[] beansNames = ac.getBeanDefinitionNames();for (String beanName : beansNames) {System.out.println(beanName);}}

3.4、案例总结

spring配置文件中一个bean标签就代表一个对象,该对象有bean标签的id值唯一标识,从spring拿对象是使用getBean(“bean标签的id值”)
spring默认是使用类的无参构造来创建对象的

3.5、简单类型属性的赋值(set注入)

在入门案例的总结我们说过了spring容器默认是使用无参构造构造来实例化对象的,那么对象的属性必定为初始值,例如int类型为0,boolean类型为false等,那么当我们想使用相关属性进行操作时必然要手动使用set方法给属性赋值,那么有没有办法让容器帮我们完成对象属性的赋值呢?让我们直接就能够从容器中拿到有属性值的对象?答案是肯定的,下面就通过代码演示简单类型的属性赋值。

set注入要求

JavaBean必须要有set方法,因为ioc容器是使用javabean的set方法进行属性赋值的
spring容器调用的是setXxx()方法,而不管对象是否具有Xxx属性(即对象没有的属性只要有set方法也可以实现注入),Xxx不区分大小写

看看代码:

JavaBean

public class Student {private String name;private int age;private School school;public void setName(String name) {this.name = name;}public void setAge(int age) {this.age = age;}public void setSchool(School school) {this.school = school;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +", school=" + school +'}';}
}

spring配置文件

<!--声明Student对象--><bean id="student" class="com.mms.component.Student"><!--1、简单类型使用property和value标签给对象属性赋值2、简单类型:8个基本类型+String3、当spring容器加载到这一行时会在创建完对象的同时使用对象的set方法给属性赋值,底层调用的是对象的set方法4、spring容器调用的是setXxx()方法,而不管对象是否具有Xxx属性,Xxx不区分大小写--><property name="name" value="张三"/><property name="age" value="23"/><!--测试对象没有属性的set方法--><property name="graName" value="s1"/></bean>

测试类

//使用set注入给对象属性赋值@Testpublic void test01() {String config = "ba01/applicationContext.xml";ApplicationContext ac = new ClassPathXmlApplicationContext(config);//执行完14行此时Student对象的属性已被赋值,获取对象进行验证Student stu = (Student) ac.getBean("student");System.out.println(stu); //Student{name='张三', age=23}}//验证set注入调用的是对象的set方法@Testpublic void test02() {String config = "ba01/applicationContext.xml";/** 此时会调用set方法进行赋值* setName...* setAge...*/ApplicationContext ac = new ClassPathXmlApplicationContext(config);}//验证没有属性的setXxx方法是否报错@Testpublic void test03() {String config = "ba01/applicationContext.xml";ApplicationContext ac = new ClassPathXmlApplicationContext(config);//获取对象Student stu = (Student) ac.getBean("student");}

3.6、非简单类型属性的赋值(set注入)

上文中的set注入使用property标签的name和value属性给对象属性赋值,但是value知识给简单类型属性赋值,对于非简单类型我们是使用property标签的name和ref属性给对象属性赋值。我们现在给Student类增加一个属性address,该属性是一个引用类型,那当ioc容器创建Student对象时如何给address属性赋值呢?

Student类:别的地方与上文的student类一致,这里只给出address属性和其set方法

//引用类型属性private Address address;public void setAddress(Address address) {System.out.println("引用类型address的set方法执行了...");this.address = address;}

Address类

public class Address {private String homeAddress;private String schoolAddress;public void setHomeAddress(String homeAddress) {this.homeAddress = homeAddress;}public void setSchoolAddress(String schoolAddress) {this.schoolAddress = schoolAddress;}@Overridepublic String toString() {return "Address{" +"homeAddress='" + homeAddress + '\'' +", schoolAddress='" + schoolAddress + '\'' +'}';}
}

applicationContext.xml配置文件

    <!--声明Student对象--><bean id="student" class="com.mms.component.Student"><property name="name" value="张三"/><property name="age" value="23"/><!--测试对象没有属性的set方法--><property name="graName" value="s1"/><!--引用类型属性的set注入property标签属性name:属性名ref:引用对象的id值--><property name="address" ref="address"/></bean><!--Student对象的引用属性Address--><bean id="address" class="com.mms.component.Address"><!--set注入--><property name="homeAddress" value="新疆"/><property name="schoolAddress" value="西安"/></bean>

上文执行流程分析:当ioc容器创建id为student的对象时,会进行set注入,当执行到最后一个propert标签时发现使用了ref属性,则ioc容器知道了name为address的属性是非简单类型,它就会暂时跳过address属性的赋值以及Student对象的创建,转而去配置文件的下文去找bean标签id值等于ref属性值的对象,现将该对象创建,再将该对象赋值给之前的address属性并将Student对象创建。

3.7、构造注入

顾名思义,构造注入是使用javabean的构造方法进行属性的赋值的。与set注入一样,构造注入要求javabean必须提供构造方法,且必须是有参构造(如果是无参构造还怎么给属性赋值,对吧),构造注入使用较少,了解就可以了,我们一般使用set注入。看看代码吧,将Student类的set方法注释,加入构造方法,别的地方不用改变,只需要改变spring配置文件即可(这里就可以看出ioc容器与程序的解耦合的好处了)。
 

<!--构造注入1、使用constructor-arg标签完成构造注入2、构造注入方式一:根据形参名字3、构造注入方式二:根据形参顺序,默认下标从0开始递增--><!--根据形参名构造注入,形参的出现顺序不是必须的--><bean id="student" class="com.mms.value.Student"><constructor-arg name="name" value="李四"/><constructor-arg name="age" value="24"/><constructor-arg name="address" ref="address"/></bean><bean id="address" class="com.mms.value.Address"><constructor-arg name="homeAddress" value="新疆"/><constructor-arg name="schoolAddress" value="西安"/></bean><!--构造注入,使用下标,出现的顺序没要求,因为已经通过下标绑定起来了--><bean id="diByContructor" class="com.mms.value.Student"><constructor-arg index="0" value="赵六"/><constructor-arg index="1" value="26"/><constructor-arg index="2" ref="address"/></bean>

3.8 非简单类型的自动注入

对于非简单类型,我们在上面是使用ref属性指向一个非简单类型的对象来完成赋值的,那么当ioc容器每次给一个对象的非简单类型属性赋值时,就要在bean标签内部写一行ref这样的代码,这样会造成重复代码的大量堆积,可以使用引用类型的自动注入。

有两种方式的引用类型自动注入

byName形式的引用类型自动注入:
通过java对象引用类型的属性名与spring容器中bean标签对象的id值一样且数据类型是一致的,这样能够实现引用类型的自动注入
byType形式的引用类型自动注入
通过java对象引用类型属性的数据类型和spring容器中 bean标签的class属性值是同源关系;
常见的同源关系:
1)java引用类型属性数据类型和bean标签的class属性值数据类型一样
2)java引用类型属性数据类型和bean标签的class属性值数据类型是父子关系
3)java引用类型属性数据类型和bean标签的class属性值数据类型是接口和实现类关系
注意:在一个配置文件中,符合条件的同源关系只能有一个

下面通过配置文件来详细说明两种形式的实现,在这里还是以Student类的address属性为例来说明。

byName形式

    <bean id="student" class="com.mms.ba03.Student" autowire="byName"><!--简单类型赋值--><property name="name" value="张三"/><property name="age" value="23"/></bean><!--引用类型--><bean id="school" class="com.mms.ba03.School"><property name="schoolName" value="石河子大学"/><property name="schoolAddress" value="石河子市"/></bean>

匹配详解: 当ioc容器在创建Student对象时,发现使用了autowire属性且属性值为byName,ioc容器就会去Student类中去拿引用类型的属性名与和spring配置文件中的bean标签的id值进行比对,若发现有一致的且数据类型一致,则将该对象赋值给引用类型属性。

byType形式

    <!--使用byType实现引用类型自动注入--><bean id="student2" class="com.mms.ba03.Student" autowire="byType"><!--简单类型赋值--><property name="name" value="李四"/><property name="age" value="24"/></bean><!--引用类型<bean id="school2" class="com.mms.ba03.School"><property name="schoolName" value="西南大学"/><property name="schoolAddress" value="重庆市"/></bean>--><!--声明School的子类--><bean id="primarySchool" class="com.mms.ba03.PrimarySchool"><property name="schoolName" value="西北大学"/><property name="schoolAddress" value="西安"/></bean>

3.8、基于注解的di实现

除了使用配置文件实现ioc创建对象的功能外,使用spring提供的注解也可以实现di。下面来介绍注解方式的di实现,下面是spring提供的di实现的常用注解。

@Component:该注解的功能是使用spring容器创建对象
1)、在要创建对象的类的声明上方加入该注解,该注解有一个属性value,value为spring创建的该类对象的id值
2)、开发中使用将value省略,直接使用双引号将值键入即可
3)、该注解使用类的无参构造创建对象
@Repository 创建dao类对象,访问数据库的对象
@Service 创建service类对象,业务层对象
@Controller 创建控制器对象,用于分发用户的请求和显示处理结果

 下面通过代码来看看@Component注解是怎么实现di的。

@Component(value = "student")
public class Student {...
}

该语句就等价为在spring配置文件中进行了以下声明

<bean id = "student" class = "com.mms.component.Student"/>

但是怎么让配置文件知道哪些类是使用注解进行创建对象的呢?需要在配置文件中声明组件扫描器

<context:component-scan base-package="com.mms.component"/>

当spring读取配置文件时,读取到组件扫描器声明语句时,就会去base-package指定的包和其子包下去递归的寻找有注解修饰的类,并根据注解的功能去执行相应的动作

3.8.1、简单类型的注解di实现

简单类型的注入使用@Value注解实现,哪些简单类型要设置属性值,直接在简单类型属性声明语句的上面加入注解@Value即可,并在@Value的括号内键入属性值,注意不论简单类型属性的数据类型,均由双引号将属性值括起来。例如之前的Student类使用注解注入如下。

@Component("student")
public class Student {@Value("张三")private String name;@Value("23")private int age;
}

注意别忘了该类要加注解@Component注解,因为要创建该类对象。

3.8.2、引用类型的注解di实现

引用类型的注入使用@Autowired注解完成。

@Autowired
@Autowired是spring提供的属性赋值,用于给引用类型赋值,有byName和byType两种方式,默认使用byType方式自动注入
若是要强制至于byName方式,要在@Autowired注解下面加入 @Qualifier(value = “bean的id”)注解,若程序在给引用类型注入时在xml文件中找不到 该id的bean标签或者手找不到该id的@Component注解,则报错;若不想让程序在赋值失败时报错,可以在@Autowired注解的required属性值置为false

还是拿Student类的school属性的赋值来举例。

学生类

@Component("student")
public class Student {/*引用类型注入(byType方式)@Autowiredprivate School school;*///引用类型赋值(byName方式)@Autowired(required = false)@Qualifier(value = "mySchool")private School school;
}

School类

@Component("mySchool")
public class School {//注入值@Value("西南大学")private String schoolAddress;@Value("新疆")private String homeAddress;@Overridepublic String toString() {return "School{" +"schoolAddress='" + schoolAddress + '\'' +", homeAddress='" + homeAddress + '\'' +'}';}
}

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

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

相关文章

前端项⽬⽂件很⼤,⽽且⻚⾯访问速度慢,如何在前端侧提⾼性能?

1. 网络优化 减少HTTP请求的数量&#xff0c;可以通过合并CSS和JavaScript文件来实现。使用CDN&#xff08;内容分发网络&#xff09;来加速静态资源的加载速度。对图片进行压缩&#xff0c;选择正确的格式&#xff0c;并实现懒加载技术&#xff0c;以减少页面初次加载时的数据…

代码随想录day12(2)字符串:重复的子字符串(leetcode459)

题目要求&#xff1a;给定一个非空的字符串&#xff0c;判断它是否可以由它的一个子串重复多次构成。给定的字符串只含有小写英文字母&#xff0c;并且长度不超过10000。 思路&#xff1a; 一、首先对于暴力解法&#xff0c;可以枚举所有的字串进行判断。但是枚举时实际上只需…

rt thread stdio如何同时生成bin和hex

一、rt thread stdio默认生成bin文件&#xff1a; rt thread stdio 软件编译时&#xff0c;默认生成bin文件&#xff1b; 二、rt thread stdio如何同时生成bin和hex 右键单击-->项目-->属性-->C/C构建-->设置-->构建步骤-->(构建后步骤)命令&#xff1a; …

视频如何无水印保存?这三种下载方法赶紧收藏

在互联网时代&#xff0c;视频已成为我们获取信息、娱乐休闲的重要途径。然而&#xff0c;有时我们想要保存或分享某些视频时&#xff0c;却发现下载起来却带有水印。为了解决这个问题&#xff0c;今天给大家带来几个无水印下载的方法。 方法一&#xff1a;水印云 水印云是一…

Python使用模块和库编程

归纳编程学习的感悟&#xff0c; 记录奋斗路上的点滴&#xff0c; 希望能帮到一样刻苦的你&#xff01; 如有不足欢迎指正&#xff01; 共同学习交流&#xff01; &#x1f30e;欢迎各位→点赞 &#x1f44d; 收藏⭐ 留言​&#x1f4dd; 路在脚下&#xff0c;勇往直前&#x…

Spring Boot2.2.4版本启动项目时,访问登录接口显示页面不存在

问题触发场景&#xff1a;IDEA 2023.3.4 SpringBoot 2.2.4 上面4张图片分别是项目结构、Spring Boot启动配置、SpringMVC配置和页面展示在项目中存放的位置&#xff0c;表面上看上去没有太大问题&#xff0c;项目应该会达到预期结果&#xff0c;但是bug总是在不经意间出现&…

MySQL数据库运维第一篇(日志与主从复制)

文章目录 一、错误日志二、二进制日志三、查询日志四、慢查询日志&#xff08;记录超时的sql语句&#xff09;五、主从复制概括六、主从复制原理七、搭建主从复制八、主从复制的测试 在这篇深入的技术文章中&#xff0c;作者将以明晰透彻的方式详细介绍MySQL数据库中关键的日志…

XGB-16:自定义目标和评估指标

概述 XGBoost被设计为一个可扩展的库。通过提供自定义的训练目标函数和相应的性能监控指标&#xff0c;可以扩展它。本文介绍了如何为XGBoost实现自定义的逐元评估指标和目标。 注意&#xff1a; 排序不能自定义 在接下来的两个部分中&#xff0c;将逐步介绍如何实现平方对数…

【EAI 027】Learning Interactive Real-World Simulators

Paper Card 论文标题&#xff1a;Learning Interactive Real-World Simulators 论文作者&#xff1a;Mengjiao Yang, Yilun Du, Kamyar Ghasemipour, Jonathan Tompson, Leslie Kaelbling, Dale Schuurmans, Pieter Abbeel 作者单位&#xff1a;UC Berkeley, Google DeepMind, …

【 Docker 容器详细介绍和说明】

Docker 容器详细介绍和说明 Docker 容器详细介绍和说明Docker 安装步骤&#xff08;以Ubuntu为例&#xff09;&#xff1a;使用Docker创建并运行容器&#xff1a;VSCode远程连接Docker容器&#xff1a;步骤1&#xff1a;配置Docker环境步骤2&#xff1a;配置PyCharm步骤3&#…

日本发动全面侵华战争他们在怕什么?为何不敢动陕西,

日本全面侵华战争之谜&#xff1a;恐惧与野心的交织 在二十世纪三十年代&#xff0c;日本帝国主义以令人发指的暴行和残忍手段&#xff0c;对中国发动了全面侵华战争。然而&#xff0c;在这场战争中&#xff0c;有一个引人关注的现象&#xff1a;日本侵略者在进攻过程中&#…

python和nodejs一键安装当前项目所有依赖

python和nodejs一键安装当前项目所有依赖。群里有人问怎么快速安装网上下载的源码里面的依赖。所以在这里分享一下。更多问题可以自己加群917400262问我。 目录导航 1.0 python一键安装当前项目所有依赖2.0 nodejs一键安装当前项目所有依赖 1.0 python一键安装当前项目所有依赖…

snakemake: 基础知识

为了有效地学习和使用 Snakemake&#xff0c;你需要具备一定的基础知识。这些基础知识将帮助你更好地理解 Snakemake 的工作原理和如何在你的项目中应用它。以下是学习 Snakemake 所需的一些基础知识&#xff1a; 1. Python 编程 Snakemake 是用 Python 编写的&#xff0c;并…

聊聊国内「类Sora模型」发展现状,和 Sora 的差距到底有多大?

2024 年 2 月 16 日。 就在谷歌发布他新一代的多模态大模型 Gemini 1.5 Pro 的同一天&#xff0c;OpenAI 带着新一代的文生视频模型 Sora 再次抓住了全世界人们的眼球。 “颠覆”、“炸裂”、“变天”、“疯狂”&#xff0c;类似的形容词一夜之间簇拥在 Sora 周围&#xff0c;…

网络传输基本流程(封装,解包)+图解(同层直接通信的证明),报头分离问题,协议定位问题,协议多路复用

目录 网络传输基本流程 引入 封装 过程梳理 图解 报文 解包 过程梳理 图解 -- 同层直接通信的证明 总结 解包时的报头分离问题 举例 -- 倒水 介绍 自底向上传输时的协议定位问题 介绍 解决方法 协议多路复用 介绍 优势 网络传输基本流程 引入 首先,我们明确…

VS查看C++头文件(.h文件)的函数列表

这里使用的是VS2019举例 如下图查看Actor.h文件中的函数列表 设置步骤如下图

【d35】【Java】【力扣】28. 找出字符串中第一个匹配项的下标

题目 给你两个字符串 haystack 和 needle &#xff0c;请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标&#xff08;下标从 0 开始&#xff09;。如果 needle 不是 haystack 的一部分&#xff0c;则返回 -1 。 示例 1&#xff1a; 输入&#xff1a;haystac…

【大数据】通过 docker-compose 快速部署 MinIO 保姆级教程

文章目录 一、概述二、MinIO 与 Ceph 对比1&#xff09;架构设计对比2&#xff09;数据一致性对比3&#xff09;部署和管理对比4&#xff09;生态系统和兼容性对比 三、前期准备1&#xff09;部署 docker2&#xff09;部署 docker-compose 四、创建网络五、MinIO 编排部署1&…

【SQL】608. 树节点(流控制语句 CASE + IF语句)

前述 知识点推荐学习&#xff1a; sql中的 IF 条件语句的用法 MySQL&#xff1a;if语句、if…else语句、case语句&#xff0c;使用方法解析 题目描述 leetcode 题目&#xff1a;608. 树节点 思路 关键点&#xff1a;如何确定有没有子节点 根节点&#xff1a;父节点为空内节…

基于Redo log Undo log的MySQL的崩溃恢复

基于Redo log & Undo log的MySQL的崩溃恢复 Redo log Undo log Redo log 重做日志,记录,修改过的数据 Undo log 回滚日志,记录修改之前的数据 两个我不做详细的介绍了,redo log就是记录哪些地方被修改了 undo log是记录修改之前我们的数据长什么样 更新流程 我们来捋一…