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

Spring之@Transactional源码解析

前言

我们在日常开发的时候经常会用到组合注解,比如:@EnableTransactionManagement + @Transactional、@EnableAsync + @Async、@EnableAspectJAutoProxy + @Aspect。今天我们就来抽丝剥茧,揭开@Transactional注解的神秘面纱

@EnableTransactionManagement注解的作用

当我们看到类似@Enablexxx这样的注解,一般源码中都会存在@Import注解。@Import注解在Spring的解析阶段有着十分重要的地位,是Spring的一个重要的扩展点。其注入的class一般继承ImportSelectorImportBeanDefinitionRegistrar接口,作用分别如下

  • 继承ImportSelector接口:selectImports方法返回的类名数组会被解析成bean
  • 继承ImportBeanDefinitionRegistrar接口:会在解析阶段执行registerBeanDefinitions方法

感兴趣的小伙伴可以阅读下方链接对应博文,该博文主要讲解了Spring对@ComponentScan、@Import、@PropertySource、@Bean等注解的解析流程,可以更好的帮助我们理解本篇文章Spring之ConfigurationClassPostProcessor解析流程icon-default.png?t=N7T8https://blog.csdn.net/qq_38257958/article/details/134761961?spm=1001.2014.3001.5501

1.@EnableTransactionManagement注解源码

 

通过上面的源码,我们简单理论分析

@EnableTransactionManagement注解会注入一个类型为TransactionManagementConfigurationSelector的class,该class的父类实现selectImports方法,父类方法又会调用子类的同名方法。根据上文中阐述的@Import注解的作用,此时Spring容器中多了两个BeanDefinition:一个beanClass为AutoProxyRegistrar,另一个beanClass为ProxyTransactionManagementConfiguration

结论1:@EnableTransactionManagement注解会import一个类型为TransactionManagementConfigurationSelector的class,该class实现ImportSelector接口,其接口方法返回[AutoProxyRegistrar,ProxyTransactionManagementConfiguration]类名数组,即Spring容器在后期会存在beanClass为AutoProxyRegistrar和ProxyTransactionManagementConfiguration的两个bean

2.AutoProxyRegistrar源码

AutoProxyRegistrar实现ImportBeanDefinitionRegistrar接口,所以会在Spring的解析阶段执行registerBeanDefinitions方法,我们重点关注截图框住的方法,它会注入一个beanClass为InfrastructureAdvisorAutoProxyCreator的BeanDefinition。InfrastructureAdvisorAutoProxyCreator是BeanPostProcessor(后文简称bpp)的子类,它会在普通bean的生命周期对bean进行一些干预,比如当前bpp就会在Spring执行bpp的postProcessAfterInitialization方法的时候会对bean进行动态代理(这个我们后文分析)

结论2:AutoProxyRegistrar会注入一个类型为InfrastructureAdvisorAutoProxyCreator的bean

3.ProxyTransactionManagementConfiguration源码

这个类比较简单,就是一个配置类,利用@Configuration + @Bean的组合,创建了几个bean

结论3:ProxyTransactionManagementConfiguration会注入beanClass为BeanFactoryTransactionAttributeSourceAdvisor、TransactionAttributeSource、TransactionInterceptor的三个bean

小结

@EnableTransactionManagement注解会import一个实现ImportSelector接口的类,import的类会注入两个bean(beanClass分别为AutoProxyRegistrar和ProxyTransactionManagementConfiguration),其中AutoProxyRegistrar会进一步解析,然后注入一个类型为InfrastructureAdvisorAutoProxyCreator的bpp。ProxyTransactionManagementConfiguration是一个配置类,会注入几个bean,协助@EnableTransactionManagement注解完成相关功能

2.什么是BeanPostProcessor

BeanPostProcessor从本质上说,它也是一个bean,不过它优先实例化,然后作用于普通bean。比如我们耳熟能详的属性注入、动态代理,都是BeanPostProcessor在不同阶段对普通bean进行的处理。

详情阅读下方链接博文
Spring之BeanPostProcessoricon-default.png?t=N7T8https://blog.csdn.net/qq_38257958/article/details/134753005?spm=1001.2014.3001.5502

3.InfrastructureAdvisorAutoProxyCreator的作用

InfrastructureAdvisorAutoProxyCreator继承BeanPostProcessor,在spring执行到postProcessAfterInitialization的时候会查找可以作用于当前bean的Advisors,如果存在符合条件的Advisors,则进行动态代理

具体查找过程可以查看下方链接博文。简单来说就是查找普通bean所属的class和方法上有没有@Transactional注解,如果满足条件则进行AOP动态代理。Spring之AOP源码解析(下)icon-default.png?t=N7T8https://blog.csdn.net/qq_38257958/article/details/136182213?spm=1001.2014.3001.5502

根据我们设置的参数,有可能进行JDK动态代理也有可能进行cglib动态代理,如果是JDK动态代理我们关注JdkDynamicAopProxy这个类,如果是cglib动态代理我们关注DynamicAdvisedInterceptor这个类。不管是什么动态代理都会有一个field(advised),这个参数存储了可以作用于当前bean的Advisors,每个Advisor都有一个advice对象(MethodInterceptor的父接口),Spring会将这些advice串成一个拦截器链,链式调用各个拦截器的invoke方法,我们画图演示流程

PS:有兴趣的小伙伴可以把我写的几篇关于AOP的文章都阅读一下,可以更好的帮助我们理解这篇博文。

 4.TransactionInterceptor源码

主要关注其invoke方法,invoke方法主要调用了invokeWithinTransaction方法

主体流程

    protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,final TransactionAspectSupport.InvocationCallback invocation) throws Throwable {// 上文分析的ProxyTransactionManagementConfiguration注入的三个bean之一(AnnotationTransactionAttributeSource)TransactionAttributeSource tas = getTransactionAttributeSource();// 1.从类上查找@Transactional注解// 2.从方法上查找@Transactional注解// 3.将步骤1或2查找到的@Transactional注解进行解析,构建成TransactionAttribute对象(RuleBasedTransactionAttribute)final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);// 1.如果txAttr为null或者beanFactory为null,返回注入的TransactionManager// 2.如果txAttr.getQualifier不为null(即Transactional注解的value属性值),则从beanFactory获取配置的bean返回// 3.如果注入了transactionManagerBeanName则从beanFactory获取bean返回// 4.如果没注入TransactionManager,就从beanFactory获取class为PlatformTransactionManager的bean返回final TransactionManager tm = determineTransactionManager(txAttr);// 省略webFlux相关代码PlatformTransactionManager ptm = asPlatformTransactionManager(tm);final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {// 开启事务(如果需要)TransactionAspectSupport.TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);Object retVal;try {// 执行下一个拦截器的invoke方法或者目标方法retVal = invocation.proceedWithInvocation();} catch (Throwable ex) {// 处理异常completeTransactionAfterThrowing(txInfo, ex);throw ex;} finally {// 解除线程和事务的绑定关系cleanupTransactionInfo(txInfo);}if (retVal != null && vavrPresent && TransactionAspectSupport.VavrDelegate.isVavrTry(retVal)) {// Set rollback-only in case of Vavr failure matching our rollback rules...TransactionStatus status = txInfo.getTransactionStatus();if (status != null && txAttr != null) {retVal = TransactionAspectSupport.VavrDelegate.evaluateTryFailure(retVal, txAttr, status);}}// 提交事务commitTransactionAfterReturning(txInfo);return retVal;}}
  1. 基础数据准备
    1. 获取TransactionAttributeSource
    2. 获取TransactionAttribute
    3. 推断TransactionManager
  2. 开启事务
  3. 执行拦截器方法或者目标方法
  4. 处理异常(如果存在)
  5. 提交事务

事务同步管理器TransactionSynchronizationManager

这个类在@Transactional源码中起着重要作用,它不仅管理每个线程的资源和事务同步,也协助完成与mybatis的集成

开启事务

TransactionAspectSupport#createTransactionIfNecessary

AbstractPlatformTransactionManager#getTransaction

AbstractPlatformTransactionManager#startTransaction

DataSourceTransactionManager#doBegin

    protected void doBegin(Object transaction, TransactionDefinition definition) {DataSourceTransactionManager.DataSourceTransactionObject txObject = (DataSourceTransactionManager.DataSourceTransactionObject) transaction;Connection con = null;try {// 如果事务还没有获取Connection或者Connection还没标记为与事务同步if (!txObject.hasConnectionHolder() ||txObject.getConnectionHolder().isSynchronizedWithTransaction()) {// 获取ConnectionConnection newCon = obtainDataSource().getConnection();if (logger.isDebugEnabled()) {logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");}// 将Connection标记为new ConnectiontxObject.setConnectionHolder(new ConnectionHolder(newCon), true);}// 将Connection没标记为与事务同步txObject.getConnectionHolder().setSynchronizedWithTransaction(true);con = txObject.getConnectionHolder().getConnection();// 设置事务的隔离级别Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);txObject.setPreviousIsolationLevel(previousIsolationLevel);txObject.setReadOnly(definition.isReadOnly());// 将Connection的自动提交关闭if (con.getAutoCommit()) {txObject.setMustRestoreAutoCommit(true);if (logger.isDebugEnabled()) {logger.debug("Switching JDBC Connection [" + con + "] to manual commit");}con.setAutoCommit(false);}prepareTransactionalConnection(con, definition);// 将事务标记为已激活txObject.getConnectionHolder().setTransactionActive(true);// 设置过期时间(如果手动设置了)int timeout = determineTimeout(definition);if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {txObject.getConnectionHolder().setTimeoutInSeconds(timeout);}// 如果是新连接,事务同步管理器同步资源if (txObject.isNewConnectionHolder()) {TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());}}catch (Throwable ex) {if (txObject.isNewConnectionHolder()) {DataSourceUtils.releaseConnection(con, obtainDataSource());txObject.setConnectionHolder(null, false);}throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);}}

PS : Spring源码中存在很多模板模式,很多方法都是交给子类去实现,比如上文中的doGetTransaction,isExistingTransaction,doBegin等方法,如果我们不确定具体走的是那个子类,可以多多去debug看看。这里TransactionManager主要是子类DataSourceTransactionManager

注意:我们在mysql中开启事务,可以使用BEGIN、START TRANSACTION等,但是在源码中,我们并没有发现这样的sql语句。其实事务可以隐式开启,在上述doBegin方法的源码中,存在con.setAutoCommit(false)这样的方法,其实这就等价于执行sql语句set autocommit = OFF(隐式开启事务)

事务隔离级别

  • REQUIRED
  • SUPPORTS
  • MANDATORY
  • REQUIRES_NEW
  • NOT_SUPPORTED
  • NEVER
  • NESTED

通过源码整理事务处理流程

异常处理

TransactionAspectSupport#completeTransactionAfterThrowing

我们在上文中指出该TransactionAttribute类型为RuleBasedTransactionAttribut,然后如果我们指定@Transactional注解的rollbackFor、rollbackForClassName、noRollbackFor、noRollbackForClassName属性,后期会被解析成RollbackRuleAttribute对象,相关源码明细可以查看AbstractFallbackTransactionAttributeSource(AnnotationTransactionAttributeSource的父类)的getTransactionAttribute方法

case1:未指定rollbackFor、rollbackForClassName、noRollbackFor、noRollbackForClassName

当我们未指定上述四个属性,会调用super.rollbackOn的方法

当我们未指定rollbackFor、rollbackForClassName、noRollbackFor、noRollbackForClassName属性的时候,事务只有在遇到RuntimeException异常或者Error的时候才会回滚

case2:指定rollbackFor、rollbackForClassName、noRollbackFor、noRollbackForClassName

当我们指定了四个属性中的一个或者多个,就会被解析成RollbackRuleAttribute(NoRollbackRuleAttribute),最后通过getDepth方法获取winner,如果winner是RollbackRuleAttribute旧回滚,否则就提交事务

getDepth方法主要判断抛出的异常与指定的异常之间的关系

  • 如果抛出异常和指定异常一致,则depth为0
  • 如果抛出异常和指定异常子类,则depth加1(递归判断)
  • 如果抛出异常为Throwable,则depth为-1

depth值越小(大于0),优先级越高

触发器

不管最后事务是提交还是回滚,都会执行相应的触发器方法,我们可以利用这一特性,做一些扩展

与mybatis的整合

mybatis相关接口会被JDK动态代理,代理对象的类型是MapperProxy。感兴趣小伙伴可以阅读我之前的博文 《@MapperScan源码解析》

MapperProxy#invoke

PlainMethodInvoker#invokeMapperMethod#executeSqlSessionTemplate#selectOne
SqlSessionInterceptor#invoke(sqlSessionProxy是一个代理对象,所以会进入相应拦截器方法)SqlSessionUtils#getSqlSession 

我们看到了我们熟悉的TransactionSynchronizationManager,当mybatis执行sql的时候会从事务同步管理器里面获取resource,保证了同个事务里面的增删改查都是使用的同一个SqlSession

相关文章:

Spring之@Transactional源码解析

前言 我们在日常开发的时候经常会用到组合注解,比如:EnableTransactionManagement Transactional、EnableAsync Async、EnableAspectJAutoProxy Aspect。今天我们就来抽丝剥茧,揭开Transactional注解的神秘面纱 EnableTransactionManagement注解的作用 当我们看到类似Ena…...

第三届国际亲子游泳学术峰会,麒小佑为亲游行业提供健康解决方案

第三届国际亲子游泳学术峰会大合影 2024年2月26—28日&#xff0c;第三届国际亲子游泳学术峰会在中国青岛成功召开。 第三届国际亲子游泳学术峰会是中国婴幼游泳行业最高标准的学术性会议&#xff0c;由亲游圈主办&#xff0c;旨在为本行业搭建一个高端圈层&#xff0c;帮助机…...

Python光速入门 - Flask轻量级框架

FlASK是一个轻量级的WSGI Web应用程序框架&#xff0c;Flask的核心包括Werkzeug工具箱和Jinja2模板引擎&#xff0c;它没有默认使用的数据库或窗体验证工具&#xff0c;这意味着用户可以根据自己的需求选择不同的数据库和验证工具。Flask的设计理念是保持核心简单&#xff0c…...

C/C++ 说说引用这玩仍是干啥的

引用的本质就是给某个实例对象起个外号。生活中李逵&#xff0c;也叫黑旋风。诸葛亮&#xff0c;又叫孔明。 引用的方式&#xff1a; 类型& 引用名对象名 举个例子 int i0; int& ki;//这种方式就是引用----->i有了自己的小名&#xff0c;从次叫k了 std::cout<…...

swoole

php是单线程。php是靠多进程来处理任务&#xff0c;任何后端语言都可以采用多进程处理方式。如我们常用的php-fpm进程管理器。线程与协程,大小的关系是进程>线程>协程,而我们所说的swoole让php实现了多线程,其实在这里来说,就是好比让php创建了多个进程,每个进程执行一条…...

kubectl基础命令详解

管理名称空间资源 查看名称空间 [rootceshi-130 conf]# kubectl get ns [rootceshi-130 conf]# kubectl get namespace NAME STATUS AGE default Active 7d17h kube-node-lease Active 7d17h kube-public Active 7d17h kube-system …...

collection的遍历方式

增强for遍历 增强for的底层就是迭代器&#xff0c;为了简化迭代器的代码书写的。 他是jdk5之后出现的&#xff0c;其内部原理就是一个Iterator迭代器。 所有的单列集合和数组才能用增强for进行遍历。 package myCollection;import java.util.ArrayList; import java.util.C…...

SpringBoot中@Async使用注意事项

前言 Async这个注解想必大家都用过&#xff0c;是用来实现异步调用的。一个方法加上这个注解以后&#xff0c;当被调用时会使用新的线程来调用。但其实这里面也有一个坑。 问题 这个注解使用时存在如下问题&#xff1a;在没有自定义线程池的场景下&#xff0c;默认会采用Sim…...

IEEE 802.11 RTS/CTS/BA/Management

RTS/CTS IEEE 802.11 RTS/CTS即RTS/CTS协议(Request To Send/Clear To Send)即请求发送/清除发送协议是被802.11无线网络协议采用的一种用来减少由隐藏节点问题所造成的冲突的机制。 相当于一种握手协议,主要用来解决"隐藏终端"问题。"隐藏终端"(Hid…...

【风格迁移】对比度保持连贯性损失 CCPL:解决图像局部失真、视频帧间的连贯性和闪烁

对比度保持连贯性损失 CCPL&#xff1a;解决图像局部失真、视频帧间的连贯性和闪烁 提出背景解法&#xff1a;对比度保持连贯性损失&#xff08;CCPL&#xff09; 局部一致性假设 对比学习机制 邻域调节策略 互信息最大化对比学习&#xff1a;在无需标签的情况下有效学习区分…...

【C++】贪心算法

贪心算法&#xff08;Greedy Algorithm&#xff09;是一种基于贪心策略的算法&#xff0c;它在每一步选择中都采取当前状态下最优的选择&#xff0c;以希望最终得到全局最优解。贪心算法通常适用于满足最优子结构性质的问题&#xff0c;即问题的最优解可以通过其子问题的最优解…...

记一次dockerfile无法构建问题追溯

我有一个dockerfile如下&#xff1a; ENTRYPOINT ["/sbin/tini"&#xff0c;"-g", "--"] CMD /home/scrapy/start.sh 我原本的用意是先启动tini&#xff0c;再执行下面的cmd命令启动start.sh。 为啥要用tini&#xff1f; 因为我的这个docker…...

React使用 useImperativeHandle 自定义暴露给父组件的实例方法(包括依赖)

关键词 React useImperativeHandle 摘要 useImperativeHandle 是 React 提供的一个自定义 Hook&#xff0c;用于在函数组件中显式地暴露给父组件特定实例的方法。本文将介绍 useImperativeHandle 的基本用法、常见应用场景&#xff0c;以及如何处理其依赖项&#xff0c;以帮…...

yolov5v7v8目标检测增加计数功能--免费源码

在yolo系列中&#xff0c;很多网友都反馈过想要在目标检测的图片上&#xff0c;显示计数功能。其实官方已经实现了这个功能&#xff0c;只不过没有把相关的参数写到图片上。所以微智启软件工作室出一篇教程&#xff0c;教大家如何把计数的参数打印到图片上。 一、yolov5目标检测…...

JPA常见异常 JPA可能抛出的异常

1、EntityNotFoundException&#xff08;实体不存在异常&#xff09;: 通过 JPA 查找一个不存在的实体。 2、NonUniqueResultException&#xff08;非唯一结果异常&#xff09;&#xff1a; 查询返回了多个结果&#xff0c;但期望只有一个结果。 3、TransactionRequiredExcep…...

Dockerfile的艺术:构建高效容器镜像的指令详解与实战指南

在容器化技术风靡全球的今天&#xff0c;Dockerfile作为构建 Docker 镜像的蓝图&#xff0c;其编写技巧与理解深度直接影响着应用部署的效率与稳定性。本文将深入剖析Dockerfile中的核心指令&#xff0c;以实战角度为您呈现一份详尽的解读与操作指南&#xff0c;并在文末抛出一…...

软件开发项目管理中各角色职责介绍

项目经理&#xff1a;项目经理在项目全生命周期中扮演着核心统筹与协调者的角色&#xff0c;负责从项目的启动、规划、执行、监控直至收尾的全过程管理。具体职责包括但不限于以下几点&#xff1a; 制定项目计划&#xff1a;依据项目业务主客户需求&#xff0c;明确项目范围、时…...

将时间转换为 `刚刚`、`几秒前`、`几分钟前`、`几小时前`、`几天前`、几月前或按照传入格式显示

const formatPast (date, type "default", zeroFillFlag true) > {// 定义countTime变量&#xff0c;用于存储计算后的数据let countTime;// 获取当前时间戳let time new Date().getTime();// 转换传入参数为时间戳let afferentTime new Date(date).getTime(…...

Oracle存储过程干货(二):PLSQL控制语句

注&#xff1a;本文的数据都来源于&#xff0c;oracle自带的emp表。 —if then elsif end if,单条件判断— declarev_grade char(1); beginv_grade : B;if v_grade A thendbms_output.put_line(哥真牛逼);elsedbms_output.put_line(哥还得加油);end if; end; /—if then els…...

深入Gradle:初识构建自动化的魅力

在软件开发的世界中&#xff0c;构建工具是不可或缺的一部分。它们帮助我们自动化编译、测试和打包应用程序的过程&#xff0c;从而节省时间并减少错误。在众多构建工具中&#xff0c;Gradle以其灵活性、可扩展性和卓越的性能而脱颖而出。本篇文章将带你走进Gradle的世界&#…...

HTML 语义化

目录 HTML 语义化HTML5 新特性HTML 语义化的好处语义化标签的使用场景最佳实践 HTML 语义化 HTML5 新特性 标准答案&#xff1a; 语义化标签&#xff1a; <header>&#xff1a;页头<nav>&#xff1a;导航<main>&#xff1a;主要内容<article>&#x…...

7.4.分块查找

一.分块查找的算法思想&#xff1a; 1.实例&#xff1a; 以上述图片的顺序表为例&#xff0c; 该顺序表的数据元素从整体来看是乱序的&#xff0c;但如果把这些数据元素分成一块一块的小区间&#xff0c; 第一个区间[0,1]索引上的数据元素都是小于等于10的&#xff0c; 第二…...

调用支付宝接口响应40004 SYSTEM_ERROR问题排查

在对接支付宝API的时候&#xff0c;遇到了一些问题&#xff0c;记录一下排查过程。 Body:{"datadigital_fincloud_generalsaas_face_certify_initialize_response":{"msg":"Business Failed","code":"40004","sub_msg…...

从零实现富文本编辑器#5-编辑器选区模型的状态结构表达

先前我们总结了浏览器选区模型的交互策略&#xff0c;并且实现了基本的选区操作&#xff0c;还调研了自绘选区的实现。那么相对的&#xff0c;我们还需要设计编辑器的选区表达&#xff0c;也可以称为模型选区。编辑器中应用变更时的操作范围&#xff0c;就是以模型选区为基准来…...

Oracle查询表空间大小

1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...

PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建

制造业采购供应链管理是企业运营的核心环节&#xff0c;供应链协同管理在供应链上下游企业之间建立紧密的合作关系&#xff0c;通过信息共享、资源整合、业务协同等方式&#xff0c;实现供应链的全面管理和优化&#xff0c;提高供应链的效率和透明度&#xff0c;降低供应链的成…...

DAY 47

三、通道注意力 3.1 通道注意力的定义 # 新增&#xff1a;通道注意力模块&#xff08;SE模块&#xff09; class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...

电脑插入多块移动硬盘后经常出现卡顿和蓝屏

当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时&#xff0c;可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案&#xff1a; 1. 检查电源供电问题 问题原因&#xff1a;多块移动硬盘同时运行可能导致USB接口供电不足&#x…...

什么是库存周转?如何用进销存系统提高库存周转率?

你可能听说过这样一句话&#xff1a; “利润不是赚出来的&#xff0c;是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业&#xff0c;很多企业看着销售不错&#xff0c;账上却没钱、利润也不见了&#xff0c;一翻库存才发现&#xff1a; 一堆卖不动的旧货…...

linux 错误码总结

1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...