Spring 事务原理总结四

作为一名认知有限的中国人,我对年的喜爱,胜过其他一切,因为它给了我拒绝一切的合理理由。每到这个时候,我都会用各种理由来为自己的不作为开脱,今年亦是如此。看着频频发出警报的假期余额,我内心的焦躁变得更加强烈。为了抚慰这烦人的情绪,我决定让自己静下来,继续梳理工作经常用到的Spring事务。通过前面三篇文章,我知道了事务的配置流程,也懂得了向Spring容器中注册事务的流程,更了解了Spring事务中的相关组件及其作用,但这依旧无法让我认识到这个知识点的全貌,所以希望通过今天的跟踪能完全了解Spring事务;也希望通过这次跟踪对Spring事务进行一次总结;更希望通过这次跟踪结束本系列,以为后面的学习腾出时间。

这里想跟大家说声对不起!在增加这段文字之前,下述描述中有这样一段:2.调用PlatformTransactionManager对象的getTransaction()方法(该方法需要一个TransactionAttribute对象,具体逻辑可以参考JdbcTransactionManager类的父类AbstractPlatformTransactionManager中的源码)获取TransactionStatus对象,具体如下图所示:

但根据实际的接口定义,getTransaction()方法接收的参数的类型为TransactionDefinition,而非TransactionAttribute,所以本次修改将更改这个错误!不过这里这么些也没有问题,因为TransactionAttribute继承了TransactionDefinition接口。关于这两个接口之间的继承关系,如果觉得本博客梳理的可以的同仁可以翻看一下《Spring 事务原理总结三》这篇文章。

回到第一篇文章中的案例(注意这个案例并没有真正操作数据库中的数据),在transferService.check("jack", "tom", BigDecimal.valueOf(100));这行代码处新增一个断点,然后运行代码,会看到下图所示的情况:

继续执行,会看到代码执行到了TransactionInterceptor#invoke(MethodInvocation invocation)方法中(这段逻辑在《Spring 事务原理总结二》这篇文章中有提到过,当时没有深入跟踪这段代码),如下图所示:

这里看到的参数MethodInvocation的实际类型为org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation,其中包含了许多信息,具体如下图所示:

接下来继续执行,会进入到TransactionInterceptor#invokeWithinTransaction (Method method, @Nullable Class<?> targetClass, final InvocationCallback invocation) throws Throwable方法中(这段逻辑在《Spring 事务原理总结二》这篇文章中有提到过,当时没有深入跟踪这段代码,今天我们会详细看一下这段代码的具体逻辑),先来看一下这段逻辑的具体代码:

@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,final InvocationCallback invocation) throws Throwable {// If the transaction attribute is null, the method is non-transactional.TransactionAttributeSource tas = getTransactionAttributeSource();final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);final TransactionManager tm = determineTransactionManager(txAttr);if (this.reactiveAdapterRegistry != null && tm instanceof ReactiveTransactionManager rtm) {boolean isSuspendingFunction = KotlinDetector.isSuspendingFunction(method);boolean hasSuspendingFlowReturnType = isSuspendingFunction &&COROUTINES_FLOW_CLASS_NAME.equals(new MethodParameter(method, -1).getParameterType().getName());if (isSuspendingFunction && !(invocation instanceof CoroutinesInvocationCallback)) {throw new IllegalStateException("Coroutines invocation not supported: " + method);}CoroutinesInvocationCallback corInv = (isSuspendingFunction ? (CoroutinesInvocationCallback) invocation : null);ReactiveTransactionSupport txSupport = this.transactionSupportCache.computeIfAbsent(method, key -> {Class<?> reactiveType =(isSuspendingFunction ? (hasSuspendingFlowReturnType ? Flux.class : Mono.class) : method.getReturnType());ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(reactiveType);if (adapter == null) {throw new IllegalStateException("Cannot apply reactive transaction to non-reactive return type: " +method.getReturnType());}return new ReactiveTransactionSupport(adapter);});InvocationCallback callback = invocation;if (corInv != null) {callback = () -> KotlinDelegate.invokeSuspendingFunction(method, corInv);}return txSupport.invokeWithinTransaction(method, targetClass, callback, txAttr, rtm);}PlatformTransactionManager ptm = asPlatformTransactionManager(tm);final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager cpptm)) {// Standard transaction demarcation with getTransaction and commit/rollback calls.TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);Object retVal;try {// This is an around advice: Invoke the next interceptor in the chain.// This will normally result in a target object being invoked.retVal = invocation.proceedWithInvocation();}catch (Throwable ex) {// target invocation exceptioncompleteTransactionAfterThrowing(txInfo, ex);throw ex;}finally {cleanupTransactionInfo(txInfo);}if (retVal != null && txAttr != null) {TransactionStatus status = txInfo.getTransactionStatus();if (status != null) {if (retVal instanceof Future<?> future && future.isDone()) {try {future.get();}catch (ExecutionException ex) {if (txAttr.rollbackOn(ex.getCause())) {status.setRollbackOnly();}}catch (InterruptedException ex) {Thread.currentThread().interrupt();}}else if (vavrPresent && VavrDelegate.isVavrTry(retVal)) {// Set rollback-only in case of Vavr failure matching our rollback rules...retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);}}}commitTransactionAfterReturning(txInfo);return retVal;}else {Object result;final ThrowableHolder throwableHolder = new ThrowableHolder();// It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.try {result = cpptm.execute(txAttr, status -> {TransactionInfo txInfo = prepareTransactionInfo(ptm, txAttr, joinpointIdentification, status);try {Object retVal = invocation.proceedWithInvocation();if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {// Set rollback-only in case of Vavr failure matching our rollback rules...retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);}return retVal;}catch (Throwable ex) {if (txAttr.rollbackOn(ex)) {// A RuntimeException: will lead to a rollback.if (ex instanceof RuntimeException runtimeException) {throw runtimeException;}else {throw new ThrowableHolderException(ex);}}else {// A normal return value: will lead to a commit.throwableHolder.throwable = ex;return null;}}finally {cleanupTransactionInfo(txInfo);}});}catch (ThrowableHolderException ex) {throw ex.getCause();}catch (TransactionSystemException ex2) {if (throwableHolder.throwable != null) {logger.error("Application exception overridden by commit exception", throwableHolder.throwable);ex2.initApplicationException(throwableHolder.throwable);}throw ex2;}catch (Throwable ex2) {if (throwableHolder.throwable != null) {logger.error("Application exception overridden by commit exception", throwableHolder.throwable);}throw ex2;}// Check result state: It might indicate a Throwable to rethrow.if (throwableHolder.throwable != null) {throw throwableHolder.throwable;}return result;}
}

这个方法的第一行会首先获取一个TransactionAttributeSource对象(实际类型为AnnotationTransactionAttributeSrouce。这里多啰嗦几句,这个类是Spring中处理事务管理的一个类,它负责在基于注解的事务控制机制中解析方法级别的事务属性。在使用声明式事务管理时,比如通过@Transactional注解,其作用至关重要。具体来讲,当Spring发现一个类的方法上标注了@Transactional注解时,AnnotationTransactionAttributeSrouce会在这个类加载和初始化过程中被Spring AOP使用来解析该注解,并根据注解中的属性,如传播行为、隔离级别、回滚规则等,生成相应的事务属性对象,通常是TransactionAttribute的实例或其子类。这样当执行到被注解的方法时,Spring的事务代理能够根据这些属性来正确的管理和控制事务的声明周期,确保方法内数据库操作的原子性和一致性。),具体如下图所示:

接下来会从拿到的TransactionAttributeSource对象中拿到一个TransactionAttribute类型的对象,该对象中包含了许多数据,譬如事务超时时间、事务隔离级别、事务可读属性、事务传播行为等等,具体如下图所示:

接下来继续执行,会执行到第三行,这里的主要作用就是从Spring容器中拿到事务管理器对象(拿对象的详细过程可以研读determineTransactionManager()方法的代码,我们拿到的这个对象的实际类型为JdbcTransactionManager,关于该类的继承结构可以参考前一篇文章,即《Spring 事务原理总结三》),具体如下图所示:

接下来继续执行,会发现先跳过if分支,然后执行PlatformTransactionManager ptm = asPlatformTransactionManager(tm);这段代码,具体如下图所示:

关于asPlatformTransactionManager()方法的源码如下所示(这个方法的主要作用是判断当前的tm对象是否是PlatformTransactionManager类型,如果是则返回当前对象,如果不是则直接抛出参数非法异常——IllegalStateException)):

private PlatformTransactionManager asPlatformTransactionManager(@Nullable Object transactionManager) {if (transactionManager == null) {return null;}if (transactionManager instanceof PlatformTransactionManager ptm) {return ptm;}else {throw new IllegalStateException("Specified transaction manager is not a PlatformTransactionManager: " + transactionManager);}
}

继续执行,会到final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);这一行,个人理解这行代码的主要作用是获取当前正在执行的目标方法的签名,比如这里的org.com.chinasofti.springtransaction.service.TransferServiceImpl.check(这是我们自定义的check()方法,该方法上添加了@Transactional注解),详情参见下图:

接下来一起看一下if分支的判断逻辑,详细代码为:if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager cpptm)),这里首先会判断txAttr(它是一个TransactionAttribute类型的对象)是否为空,因为前面获取过,所以这里等于null的判断结果是false;再来看一下后半段,ptm是一个PlatformTransactionManager类型的对象,虽然这里的CallbackPreferringPlatformTransactionManager继承了PlatformTransactionManager接口,但是这里的实际类型JdbcTransactionManager并没有实现CallbackPreferringPlatformTransactionManager接口,所以后半段逻辑最终返回的结果是true,具体如下图所示:

下面详细看一下if分支中的第一句代码TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);,从字面大概可以猜出这段代码的主要作用就是创建一个TransactionInfo对象,下面来看一下这里涉及的两个方法的代码:

protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,@Nullable TransactionAttribute txAttr, final String joinpointIdentification) {// If no name specified, apply method identification as transaction name.if (txAttr != null && txAttr.getName() == null) {txAttr = new DelegatingTransactionAttribute(txAttr) {@Overridepublic String getName() {return joinpointIdentification;}};}TransactionStatus status = null;if (txAttr != null) {if (tm != null) {status = tm.getTransaction(txAttr);}else {if (logger.isDebugEnabled()) {logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +"] because no transaction manager has been configured");}}}return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}/*** Prepare a TransactionInfo for the given attribute and status object.* @param txAttr the TransactionAttribute (may be {@code null})* @param joinpointIdentification the fully qualified method name* (used for monitoring and logging purposes)* @param status the TransactionStatus for the current transaction* @return the prepared TransactionInfo object*/
protected TransactionInfo prepareTransactionInfo(@Nullable PlatformTransactionManager tm,@Nullable TransactionAttribute txAttr, String joinpointIdentification,@Nullable TransactionStatus status) {TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification);if (txAttr != null) {// We need a transaction for this method...if (logger.isTraceEnabled()) {logger.trace("Getting transaction for [" + txInfo.getJoinpointIdentification() + "]");}// The transaction manager will flag an error if an incompatible tx already exists.txInfo.newTransactionStatus(status);}else {// The TransactionInfo.hasTransaction() method will return false. We created it only// to preserve the integrity of the ThreadLocal stack maintained in this class.if (logger.isTraceEnabled()) {logger.trace("No need to create transaction for [" + joinpointIdentification +"]: This method is not transactional.");}}// We always bind the TransactionInfo to the thread, even if we didn't create// a new transaction here. This guarantees that the TransactionInfo stack// will be managed correctly even if no transaction was created by this aspect.txInfo.bindToThread();return txInfo;
}

通过阅读createTransactionIfNecessary()方法的源码不难发现,这个方法主要做了这样几件事情:1.将TransactionAttribute对象包装为DelegatingTransactionAttribute对象(重写该对象的getName()方法,返回org.com.chinasofti.springtransaction.service.TransferServiceImpl.check);2.调用PlatformTransactionManager对象的getTransaction()方法(该方法需要一个TransactionDefinition对象,具体逻辑可以参考JdbcTransactionManager类的父类AbstractPlatformTransactionManager中的源码)获取TransactionStatus对象;3.调用本类(TransactionAspectSupport)中的prepareTransactionInfo()方法(这个方法就是上面罗列的第二个方法的源码)。接下来看一下prepareTransactionInfo()方法的执行逻辑:1.创建TransactionInfo对象,该对象会持有TransactionAttribute、PlatformTransactionManager及目标方法签名;2.调用TransactionInfo对象上的newTransactionStatus()方法(该方法会接收一个TransactionStatus对象。该方法的主要目的就是将TransactionStatus对象赋值给TransactionInfo对象,也就是说该方法的主要逻辑就是赋值);3.调用TransactionInfo对象上的bindToThread()方法,将TransactionInfo对象绑定到当前线程上,该方法的源码如下所示:

private void bindToThread() {// Expose current TransactionStatus, preserving any existing TransactionStatus// for restoration after this transaction is complete.this.oldTransactionInfo = transactionInfoHolder.get();transactionInfoHolder.set(this);
}

注意:这里看到的源码(bindToThread()、createTransactionIfNecessary()、prepareTransactionInfo()包括TransactionInfo类)都位于TransactionAspectSupport类中。

接下来继续回到TransactionAspectSupport类的invokeWithinTransaction()方法中,直接来到cleanupTransactionInfo(txInfo)这段代码处,最终会调到TransactionInfo对象上的restoreThreadLocalStatus()方法,该方法源码为:

private void restoreThreadLocalStatus() {// Use stack to restore old transaction TransactionInfo.// Will be null if none was set.transactionInfoHolder.set(this.oldTransactionInfo);
}

这里又看到了transactionInfoHolder对象,这个对象是在TransactionAspectSupport对象中创建的,其实际类型就是NamedThreadLocal,说白了就是一个ThreadLocal(关于这个类的解释这里不再赘述,后面会专门写一篇文章对其进行介绍)。这里想在啰嗦几句,还记得前面创建TransactionInfo对象的代码吗?那里的主要目的就是想transactionInfoHolder对象中存放TransactionInfo对象(实际上就是操作TransactionAspectSupport对象中transactionInfoHolder对象)。

接下来继续向下走,会来到提交事务的代码处(commitTransactionAfterReturning(txInfo)),详细执行逻辑如下所示:

这个被调用的方法(commitTransactionAfterReturning(@Nullable TransactionInfo txInfo))的源码如下所示:

protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) {if (txInfo != null && txInfo.getTransactionStatus() != null) {if (logger.isTraceEnabled()) {logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]");}txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());}
}

从源码不难看出,事务提交的主要逻辑就是从TransactionInfo对象中拿到TransactionManager对象然后调用其上的commit()方法(注意:该方法会接收一个TransactionStatus对象),具体执行详情如下所示:

接下来一起看一下commit(TransactionStatus status)方法的源码吧,详情请参见下述源代码:

@Override
public final void commit(TransactionStatus status) throws TransactionException {if (status.isCompleted()) {throw new IllegalTransactionStateException("Transaction is already completed - do not call commit or rollback more than once per transaction");}DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;if (defStatus.isLocalRollbackOnly()) {if (defStatus.isDebug()) {logger.debug("Transactional code has requested rollback");}processRollback(defStatus, false);return;}if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {if (defStatus.isDebug()) {logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");}processRollback(defStatus, true);return;}processCommit(defStatus);
}

这个方法会首先判断当前事务是否需要执行回滚操作。如果需要,则继续调用pocessRollback()方法。如果不需要,则直接调用processCommit(DefaultTransactionStatus status)方法。首先来看一下processCommit()方法的源码:

private void processCommit(DefaultTransactionStatus status) throws TransactionException {try {boolean beforeCompletionInvoked = false;boolean commitListenerInvoked = false;try {boolean unexpectedRollback = false;prepareForCommit(status);triggerBeforeCommit(status);triggerBeforeCompletion(status);beforeCompletionInvoked = true;if (status.hasSavepoint()) {if (status.isDebug()) {logger.debug("Releasing transaction savepoint");}unexpectedRollback = status.isGlobalRollbackOnly();this.transactionExecutionListeners.forEach(listener -> listener.beforeCommit(status));commitListenerInvoked = true;status.releaseHeldSavepoint();}else if (status.isNewTransaction()) {if (status.isDebug()) {logger.debug("Initiating transaction commit");}unexpectedRollback = status.isGlobalRollbackOnly();this.transactionExecutionListeners.forEach(listener -> listener.beforeCommit(status));commitListenerInvoked = true;doCommit(status);}else if (isFailEarlyOnGlobalRollbackOnly()) {unexpectedRollback = status.isGlobalRollbackOnly();}// Throw UnexpectedRollbackException if we have a global rollback-only// marker but still didn't get a corresponding exception from commit.if (unexpectedRollback) {throw new UnexpectedRollbackException("Transaction silently rolled back because it has been marked as rollback-only");}}catch (UnexpectedRollbackException ex) {triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);this.transactionExecutionListeners.forEach(listener -> listener.afterRollback(status, null));throw ex;}catch (TransactionException ex) {if (isRollbackOnCommitFailure()) {doRollbackOnCommitException(status, ex);}else {triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);if (commitListenerInvoked) {this.transactionExecutionListeners.forEach(listener -> listener.afterCommit(status, ex));}}throw ex;}catch (RuntimeException | Error ex) {if (!beforeCompletionInvoked) {triggerBeforeCompletion(status);}doRollbackOnCommitException(status, ex);throw ex;}// Trigger afterCommit callbacks, with an exception thrown there// propagated to callers but the transaction still considered as committed.try {triggerAfterCommit(status);}finally {triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);if (commitListenerInvoked) {this.transactionExecutionListeners.forEach(listener -> listener.afterCommit(status, null));}}}finally {cleanupAfterCompletion(status);}
}

因为本次测试是一个正常流程,所以这里会直接走到doCommit(status)方法处,继续看这个方法的源码如下所示(注意这段源码位于DataSourceTransactionManager类中,关于其继承体系可以参考《Spring 事务原理总结三》这篇文章):

protected void doCommit(DefaultTransactionStatus status) {DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();Connection con = txObject.getConnectionHolder().getConnection();if (status.isDebug()) {logger.debug("Committing JDBC transaction on Connection [" + con + "]");}try {con.commit();}catch (SQLException ex) {throw translateException("JDBC commit", ex);}
}

从这里不难看出,其主要目的就是通过程序与数据库之间的连接对象来提交事务(数据库层面的事务),conn.commit(),这里我有个疑问当单独利用JDBC进行手动事务控制时,会有一个将当前事务设置为false的操作,比如conn.setAutoCommit(false),为什么这里没看到呢?再次梳理一下事务提交的执行流程:

  1. TransactionAspectSupport#commitTransactionAfterReturning (@Nullable TransactionInfo txInfo)
  2. AbstractPlatformTransactionManager#commit(TransactionStatus status)
  3. AbstractPlatformTransactionManager#processCommit(DefaultTransactionStatus status)
  4. DataSourceTransactionManager#doCommit(DefaultTransactionStatus status)

下面让我们继续回到TransactionAspectSupport#invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, final InvocationCallback invocation)方法中,执行完commitTransactionAfterReturning()方法后,该方法就结束了(后面直接return retVal),详细执行过程参见下图:

至此我们把事务正常执行的流程梳理完了,不过这个过程中还遗留了几个小问题,下一篇博客我会对这些问题进行详细跟踪,这些问题分别是:

  1. 为什么这里没看到conn.setAutoCommit(false)?
  2. Spring事务异常回滚的执行流程是什么?
  3. Spring事务失效的场景有那些?
  4. 本篇文章中我们梳理了完整的流程,但是还有一个地方梳理的不够完整,即调用PlatformTransactionManager对象的getTransaction()方法(该方法需要一个TransactionAttribute对象,具体逻辑可以参考JdbcTransactionManager类的父类AbstractPlatformTransactionManager中的源码)获取TransactionStatus对象这个地方

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

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

相关文章

【优化数学模型】1. 基于Python的线性规划问题求解

【优化数学模型】1. 基于Python的线性规划问题求解 一、线性规划问题1.概述2.三要素 二、示例&#xff1a;药厂生产问题三、使用 Python 绘图求解线性规划问题1.绘制约束条件2.绘制可行域3.绘制目标函数4.绘制最优解 四、使用 scipy.optimize 软件包求解线性规划问题1.导入库2.…

CAN通讯协议学习

介绍 它是一种异步通讯&#xff0c;can_high和can_low两条线利用的是电位差传输信号&#xff0c;抗干扰能力强&#xff0c;但是必须要有can控制器如TJA1050&#xff08;我的开发板&#xff09; 当 CAN 节点需要发送数据时&#xff0c;控制器把要发送的二进制编码通过 CAN_Tx 线…

数仓建模—数据网格

数据网格 随着数字化时代的到来,近几年数据领域的新技术概念不断涌现,无论是数据湖、湖仓一体、流批一体、存算一体、数据编织抑或数据网格,很多还爬上了Gartner曲线,其中数据网格备受关注,数据网格从字面意思来看挺抽象的,会劝退很多人,但当你深入去理解这个概念时,才…

wordpress好的网站主题

有什么好的网站主题&#xff0c;都分享在这里了。 蓝色风格的wordpress模板&#xff0c;好的wordpress网站主题&#xff0c;需要既好看&#xff0c;又好用。 https://www.zhanyes.com/qiye/6305.html 血红色的好看的wordpress主题&#xff0c;布局经典&#xff0c;设计好的&am…

基于laravel开发的开源交易所源码|BTC交易所/ETH交易所/交易所/交易平台/撮合交易引擎

开源交易所&#xff0c;基于Laravel开发的交易所 | BTC交易所 | ETH交易所 | 交易所 | 交易平台 | 撮合交易引擎。本项目有完整的撮合交易引擎源码、后台管理&#xff08;后端前端&#xff09;、前台&#xff08;交易页面、活动页面、个人中心等&#xff09;、安卓APP源码、苹果…

宝宝起名神器小程序源码/支持多种流量主模式

还不知道怎么给虎宝宝取名字么&#xff1f;那么这款小程序源码就可以帮到你了&#xff0c;这款小程序支持输入姓氏自动起名。 不满意还可以点击换一换来找到满意的&#xff0c;支持起两个字或者三个字的名字。另外也给该款小程序添加了几个流量主位置&#xff01;&#xff01;…

react渲染流程是怎样的

整体流程&#xff1a; react的核心可以用uifn(state)来表示&#xff0c;更详细可以用&#xff1a; const state reconcile(update); const UI commit(state);上面的fn可以分为如下一个部分&#xff1a; Scheduler&#xff08;调度器&#xff09;&#xff1a; 调度任务&…

【教程】Kotlin语言学习笔记(二)——数据类型(持续更新)

写在前面&#xff1a; 如果文章对你有帮助&#xff0c;记得点赞关注加收藏一波&#xff0c;利于以后需要的时候复习&#xff0c;多谢支持&#xff01; 【Kotlin语言学习】系列文章 第一章 《认识Kotlin》 第二章 《数据类型》 文章目录 【Kotlin语言学习】系列文章一、基本数据…

Python算法题集_二叉树的中序遍历

Python算法题集_二叉树的中序遍历 题94&#xff1a;1. 示例说明2. 题目解析- 题意分解- 优化思路- 测量工具 3. 代码展开1) 标准求解【直接递归】2) 改进版一【函数递归】3) 改进版二【迭代遍历】 4. 最优算法 本文为Python算法题集之一的代码示例 题94&#xff1a; 1. 示例说…

【使用IDEA总结】01——新增作者信息、方法参数返回值

[TOC](目录) 1.类新增作者信息 打开IDEA的Settings&#xff0c;Editor->Code Style->File and Code Templates->Includes->File Header&#xff0c;输入以下作者信息&#xff0c;作者名更换为自己的即可&#xff0c;操作如下图所示 /*** Author Linhaipeng* Date…

MySQL 基础知识(三)之数据库操作

目录 1 显示当前时间、用户名、数据库版本 2 查看已有数据库 3 创建数据库 4 使用数据库 5 查看当前使用的数据库 6 查看当前数据库信息 7 查看数据库编码 8 修改数据库信息 9 删除数据库 10 查看最大连接数 11 查看数据库当前连接数&#xff0c;并发数 12 查看数据…

C++类和对象-C++对象模型和this指针->成员变量和成员函数分开存储、this指针概念、空指针访问成员函数、const修饰成员函数

#include<iostream> using namespace std; //成员变量 和 成员函数 分开储存的 class Person { public: Person() { mA 0; } //非静态成员变量占对象空间 int mA; //静态成员变量不占对象空间 static int mB; //函数也不占对象空间…

抽象的前端

问题背景&#xff1a;vue3&#xff0c;axios 直接导致问题&#xff1a;路由渲染失败 问题报错&#xff1a;Uncaught SyntaxError: The requested module /node_modules/.vite/deps/axios.js?v7bee3286 does not provide an export named post (at LoginIn.vue:16:9) 引入组…

C++ //练习 7.3 修改7.1.1节(第229页)的交易处理程序,令其使用这些成员。

C Primer&#xff08;第5版&#xff09; 练习 7.3 练习 7.3 修改7.1.1节&#xff08;第229页&#xff09;的交易处理程序&#xff0c;令其使用这些成员。 环境&#xff1a;Linux Ubuntu&#xff08;云服务器&#xff09; 工具&#xff1a;vim 代码块 /********************…

淘宝项目实战相关知识点

淘宝各个方面的布局大部分都是常规操作&#xff0c;在这里我就简单记录一下练习过程中的相关知识点&#xff0c;比较简短。相关知识点如下&#xff1a; 行高的取值 假设font-size为16px line-height:normal; line-height:1.5;24px&#xff0c;先继承后计算 line-height:200%;3…

Java并发基础:Exchanger全面解析!

内容概要 Exchanger类的优点在于能够简洁高效地实现两个线程间的数据交换&#xff0c;通过Exchanger&#xff0c;开发者可以避免复杂的锁和同步机制&#xff0c;降低并发编程的难度&#xff0c;同时&#xff0c;它还提供了线程安全的数据交换保障&#xff0c;使得多线程协作更…

android 控制台输出 缺失

问题 android 控制台输出内容缺失 详细问题 笔者进行android开发&#xff0c;期望控制台打印Log日志或是输出内容 Log.i("tag","content");或 System.out.println("content")但是实际上&#xff0c;上述内容并没有按照笔者期望打印 解决方…

2024 年 7 款最佳电脑录屏软件 [免费和付费]

录屏是捕获桌面上活动的软件应用程序。用户可以根据自己的要求创建视频记录。免费屏幕录像机广泛用于演示、演示、教程、游戏等。 录音机还有助于内容创建、远程协作和员工培训。这些录音机具有多种特性和功能。它提供了音频录制、网络摄像头集成和快速编辑工具的选项。您可以根…

跟廖雪峰老师学习Git(持续更新)

Git简介 创建版本库 第一步&#xff0c;创建一个新目录 第二步&#xff0c;通过git init变成Git可以管理的仓库 把文件添加到文本库&#xff0c;不要使用Windows自带的记事本&#xff01; 我用的是VS code 创建readme.txt 放入库中 commit可以一次提交很多文件&#xff0…

点云旋转处理

实现代码为&#xff1a; //以中心化点进行旋转double theta atan(maindirection.a);//计算的是弧度单位for (int i 0; i < origipts.size(); i){pcl::PointXYZ tempone;tempone.x aftercenerlizepts[i].x*cos(theta) aftercenerlizepts[i].y*sin(theta) center.x;temp…