前言
一个成熟的开源项目,其代码量还是较为客观的,拿到手通常不知如何下手。那么一套清洗的分析思路尤为重要,帮助自己快速理解作者思路。
1、首要工作是fork一份源码,在这个基础上做一些诸如,添加注释、debug等操作比较方便。
2、查看作者的提交历史,也就是通过git历史来查看这个项目的进化过程。
第一步以及clone到本地,可自行百度如何操作。我们从第二步开始。
正文
1、找到感兴趣的历史版本
以IDEA为例,通过Version Control-->Log,找到git历史记录。翻到最后,看到较早一次带有版本号的提交,既然作者在备注中特意创建了版本号,那么这版应该较为重要。
那么久以这次提交创建一个分支出来,右键New Branch,自定义一个分支名(updateTo1.0.3),然后切到这个分支上。
2、分析1.0.3版本
先看一下项目结构,一共包含四个模块:
2.1、tcc-transaction-api模块
只定义了TransactionContext、TransactionStatus、TransactionXid 三个类,作者编码习惯不错,基本上可以根据代码简单理解含义。
- TransactionXid:实现java-transaction-xa(java事务规范)中的Xid,用于生成全局唯一标识。
- globalTransactionId( 表示 全局事务的 唯一标识)
- branchQualifier( 表示 当前本地事务在 全局事务中的一个分支号)
- TransactionStatus:事务状态,预置了三个TRYING准备、CONFIRMING提交、CANCELLING取消回滚
- TransactionContext:成员包含xid和status,另外还有一个ConcurrentHashMap类型的attachments,看名字是附加信息。
2.2、tcc-transaction-core模块
这个模块就是核心代码了,定义的内容较多,咱们一点点看。
- utils包,肯定是工具集,一个是序列化工具SerializationUtils、一个是字符串工具StringUtils,比较简单。
- BeanFactory:定一个了一个getBean方法,根据类型获取Bean。
- BeanFactoryAdapter:持有静态BeanFactory对象,代理了BeanFactory类。set方法给BeanFactory赋值、代理了getBean()方法。
- InvocationContext:调用上下文,成员包含Class targetClass目标类型、String methodName方法名、Class[] parameterTypes参数类型数组、Object[] args参数,作用肯定是记录信息,供其他位置调用,看情形tcc框架里应用了不少反射。
- MethodType:方法类型,定义了四种ROOT, CONSUMER, PROVIDER, NORMAL,如何分类的,后面再看。
- Terminator:终结者,里面持有两个InvocationContext类型成员,看名字是confirm和cancel的两个上下文信息;
- commit()和rollback()两个方法,内部都调用了invoke()方法,传参则是对应的InvocationContext类型成员。
- invoke()方法,根据InvocationContext中包含的信息,反射方式调用method.invoke(target, invocationContext.getArgs())。
- Participant:参与者,里面包含一个xid、一个Terminator。commit()和rollback()方法是代理了Terminator对应的方法。
- Transaction:名字叫事务,应该比较重要,看内部的定义,是记录信息的。
- 成员有xid、status、transactionType、retriedCount(肯定有重试逻辑)、List<Participant> participants参与者列表。
- commit()和rollback()方法,内部是遍历participants列表,执行commit和rollback方法。
- TransactionConfigurator:接口,定义获取TransactionManager和TransactionRepository的方法。
- TransactionManager:成员有TransactionConfigurator和transactionMap(按线程记录transaction对象)
- 方法有begin()\commit()\rollback()、propagationNewBegin(...)\propagationExistBegin(...)
- 方法内部情况看来,有持久化、缓存信息的操作。
- 那么TransactionManager就是用于在TCC的各个阶段,管理事务信息的。
- TransactionRepository:接口,事务仓库。定义CRUD操作。
- TransactionType:枚举,定义了两个类型ROOT、BRANCH
2.3、tcc-transaction-spring模块
针对spring的模块,看看里面做了什么。
- 这里包含一个sql脚本:定义了一个表,自己配个数据库,创建一下肯定用得到。
- recover包:恢复job和具体实现,恢复逻辑肯定是复用主逻辑,这里先略过。
- support包:
- TccApplicationContext:@Component、实现BeanFactory, ApplicationContextAware。
- TccBeanPostProcessor:@Component、实现ApplicationListener<ContextRefreshedEvent>
- TccTransactionConfigurator:@Component、实现TransactionConfigurator
- utils包:
- CompensableMethodUtils:
- calculateMethodType()根据参数获取方法类型MethodType
- getTransactionContextParamPosition()获取TransactionContext类型的参数位置
- getTransactionContextFromArgs()获取TransactionContext类型参数
- ReflectionUtils:
- getDeclaringType()根据参数信息,找到类型对象
- CompensableMethodUtils:
- @Compensable:注解类,“可补偿的”。定义confirmMethod和cancelMethod两个属性
- JdbcTransactionRepository:jdbc事务仓库,应该是用于数据库记录的(继承JdbcDaoSupport、实现TransactionRepository,内部是操作数据库的)。
下面是重点
- TccCompensableAspect:切面,针对@Compensable注解的方法。切面方法为interceptCompensableMethod().
- TccTransactionContextAspect:切面,针对 第一个参数为TransactionContext类型的方法 或 @Compensable注解的方法。切面方法为interceptTransactionContextMethod()
两个切面都实现了Ordered接口,并定义了order属性,spring中order值标识了切面执行顺序,order越小,执行越靠前。
- tccCompensableAspect.order= Ordered.HIGHEST_PRECEDENCE
- tccTransactionContextAspect.order = Ordered.HIGHEST_PRECEDENCE + 1
因此TccCompensableAspect的优先级高于TccTransactionContextAspect。
2.3.1、TccCompensableAspect
我们分析下TccCompensableAspect都做了什么,直接看一下interceptCompensableMethod方法。
整体思路清晰:前面获取methodType,后面switch根据methodType进行不同的流程处理。
rootMethodProceed()方法处理流程:
- begin()方法中,创建Transaction对象,记录事务类型、状态,缓存、持久化Transaction信息对象。
- pjp.proceed();正常业务逻辑。
- 正常执行,进行commit()提交事务
看一下org.mengyun.tcctransaction.TransactionManager#commit内部做了什么。
除了更新持久化数据的状态,重点调用了transaction.commit();并在调用完成后,删除了持久化信息。
org.mengyun.tcctransaction.Transaction#commit,实际是调用了Participant“参与者”内的commit方法。
而Participant是代理了Terminator。
org.mengyun.tcctransaction.Terminator#commit,最终是调用InvocationContext confirmInvocationContext中记录的方法。
整理下调用链:
aop 在执行完业务方法后--> 执行transactionManager.commit()-->执行transaction.commit()-->执行participant.commit()-->执行terminator.commit()-->执行invoke(confirmInvocationContext)。
一步步,最终执行的是@Compensable注解中定义的confirmMethod。
异常捕获后执行rollback()回滚事务,其内部流程与commit相似,不再赘述。
看过了Root类型方法的处理,我们在看一下PROVIDER类型方法的处理。
根据org.mengyun.tcctransaction.spring.utils.CompensableMethodUtils#calculateMethodType方法的描述,PROVIDER类型的方法,是事务开启后,再次调用的事务方法,属于调用链中的一个节点,类似于分支事务的概念。
providerMethodProceed()方法根据事务状态,三个阶段进行不同的处理:
- TRYING阶段:事务第一阶段;propagationNewBegin(),需要开启分支事务,也就是定义分支事务信息newTransaction,继承ROOT事务的全局标识,设置事务类型为TransactionType.BRANCH。缓存、持久化。
- CONFIRMING阶段:事务第二阶段;propagationExistBegin(),需要将分支事务的状态进行更新,通过xid找到分支事务信息,设置status,更新缓存、持久化。然后执行注册的confirm操作,也就是commit().
- CANCELLING阶段:事务第二阶段;propagationExistBegin(),更新分支事务信息;执行注册的cancel操作,也就是rollback().
上面有个疑问,Participant“参与者”中的成员Terminator是如何封装进去的。
2.3.2、TccTransactionContextAspect
我们分析下TccTransactionContextAspect都做了什么,直接看一下interceptTransactionContextMethod方法。
切面方法中,在执行真正方法前,做了事务控制,进行了一系列的操作,目的是拿到methodType,根据methodType做不同处理,看方法名就是封装Participant。
看一下generateAndEnlistRootParticipant:(generateAndEnlistRootParticipant和generateAndEnlistProviderParticipant方法内容一样)
运用很多反射,拿到切面和compensable注解中的信息,封装为InvocationContext,最终都封装到Participant,并存入transaction中,还进行了持久化。
我们在看一下generateAndEnlistConsumerParticipant,这个是对CONSUMER类型的方法进行处理。
其逻辑与ROOT、PROVIDER类型的方法一致,只不过是CONSUMER类型的方法,没有@Compensable注解,因此需要将自身方法封装为confirm和cancel的上下文对象,最终也是封装为Participant,并存入transaction中,还进行了持久化。
2.4、tcc-transaction-unit-test模块
分析完TCC核心实现,接下来进入测试阶段,验证之前的猜测是否正确。
模块分为两部分,main中定义了业务流程,test定义了单元测试。配置文件需要调整下数据库信息,改成本地的(这里依赖spring模块的db.sql脚本)。
简单描述下测试模拟的业务流程,场景为经典的转账,A给B转账N元,A账户减少N元,B账户增加N元。
- org.mengyun.tcctransaction.unittest.client.TransferService:定义了A账户的操作
- org.mengyun.tcctransaction.unittest.service.AccountServiceImpl:定义了B账户的操作。
- org.mengyun.tcctransaction.unittest.client.AccountServiceProxy:代理了AccountServiceImpl,可以理解为TransferService所在A的服务,需要远程调用AccountService所在B服务,中间通过A服务中的AccountServiceProxy代理调用B服务。
- 其他的类都是模拟持久化层。org.mengyun.tcctransaction.unittest.utils.UnitTest还可以配置异常,来验证回滚,
以最简单的单元测试为例org.mengyun.tcctransaction.unit.test.TransferServiceTest#testTransfer。
初始化数据,进行方法调用,验证结果。
org.mengyun.tcctransaction.unittest.client.TransferService#transfer 带有@Compensabel注解,方法调用入口,我们可以理解为ROOT事务的入口方法,执行流程:
- 执行transferService.transfer(1, 2, 50);后命中切面。
- 先进入TccCompensableAspect,通过begin封装事务信息,在调用pjp.proceed();
- 再进入TccTransactionContextAspect,在这里封装Participant“参与者”,也就是@Compensabel注解中配置的confirmMethod和cancelMethod信息
- 真正调用pjp.proceed()。
- 回到TccCompensableAspect中进行commit或cancel操作。
至此,集成了TCC-Transaction的服务执行完成,做到了TCC三个阶段信息、流程的管理。
遗留问题
在org.mengyun.tcctransaction.spring.TccTransactionContextAspect#generateAndEnlistConsumerParticipant方法中,在单元测试过程中出现循环调用,这里原因未找到。不过我们还要继续往下分析源码,这里先不纠结。
原因:只是调用三次,由于补偿job干扰,执行try后,执行commit但是这里出现异常,再执行cancel,感觉上是不停的调用。