4

Activiti 7 源码学习 - 废物大师兄

 1 year ago
source link: https://www.cnblogs.com/cjsblog/p/16718814.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Activiti 7 源码学习

1.  启动分析

源码版本是 7.1.0.M6

首先从 ProcessEngineAutoConfiguration 开始

874963-20220922103339318-656264547.png

ProcessEngineAutoConfiguration 是activiti-spring-boot-starter 7.1.0.M6自动配置的入口类,在这里主要看 SpringProcessEngineConfiguration

874963-20220922110030736-1041378864.png
874963-20220922110248193-980533333.png

主要是配置了自动部署

最最最重要的是 buildProcessEngine() 方法,将来根据配置构建 ProcessEngine 的时候它就派上用场了

ProcessEngine processEngine = ProcessEngineConfiguration.createProcessEngineConfigurationFromResourceDefault().buildProcessEngine();

下面重点看一下如何构建 ProcessEngine

874963-20220922111744353-1612017929.png

在父类(ProcessEngineConfigurationImpl)的 buildProcessEngine() 里调用了一个非常重要的方法 init()

可以看到在init()方法里初始化了很多组件,接下来挑几个来重点看一下

initAgendaFactory()

874963-20220922112352483-380377636.png
874963-20220922112359319-306461704.png

initCommandContextFactory()

874963-20220922113124742-1410230397.png
874963-20220922113254475-1316321360.png

new了一个CommandContextFactory,重要的是CommandContextFactory中持有当前processEngineConfiguration的引用

initCommandExecutors()

874963-20220922114429380-2103409265.png
874963-20220922114458152-280464668.png

初始化拦截器interceptor要重点说下,这里构造了一个拦截器链,而且拦截器链的最后是CommandInvoker,并且将第一个拦截器放到CommandExecutor里面,姑且先记下,后面有用到

874963-20220922140407460-169127169.png

initServices()

874963-20220922120948390-1026572416.png

initBehaviorFactory()

874963-20220922121342850-1690958625.png

在初始化各个组件以后,new了一个ProcessEngineImpl,并将当前的配置 ProcessEngineConfigurationImpl 赋值给它

因此,这个代表流程引擎的ProcessEngine就变成了一个基础的入口类,它提供了对工作流操作的所有服务的访问。

874963-20220922122140124-641915952.png

2.  CommandContextInterceptor

在默认的拦截器中有一个 CommandContextInterceptor 特别重要

874963-20220922130939081-1857891545.png

在其execute()方法中设置上下文CommandContext

  1. 查找栈顶部的元素,如果为空,则新new一个CommandContext,如果不为空,则将获取到的CommandContext的熟悉reused设为true
  2. 将刚才获取到的CommandContext压入栈中
  3. 将当前processEngineConfiguration压入另一个栈中
  4. 调用下一个拦截器
874963-20220922132319610-1668594294.png

也就是说,每个命令在经过CommandContextInterceptor后都有了自己的上下文

那么,CommandContext中到底有什么呢?继续看

874963-20220922133009525-1413689045.png

CommandContext中有命令(Command),还有agenda(ActivitiEngineAgenda)

3.  Command

Activiti这里采用命令模式,将操作以及与之相关的信息都封装成命令。

下面以完成任务为例来看一下命令是如何被完成的

前面初始化services的时候说过了,会将创建好的CommandExecutor设置到各个Service中,因此TaskService中commandExecutor的出现就不足为奇了

874963-20220922134433381-371173673.png

可以看到,完成任务的时候,直接new了一个CompleteTaskCmd,然后交由commandExecutor去执行

874963-20220922134651360-805408.png

CompleteTaskCmd主要有两个属性:任务ID 和 流程变量

既然命令交给了CommandExecutor执行,那么接下来看下它是如何执行的。

在前面 initCommandExecutor() 的时候我们指定,它其实是 CommandExecutorImpl,并且我们还知道它持有默认的命令配置,以及拦截器链中的第一个拦截器

874963-20220922135640867-1470822737.png

从代码中可以看到 CommandExecutorImpl#execute() 直接从拦截器链中的第一个拦截器开始往后依次调用。可以预见到,它肯定会经过CommandContextInterceptor,于是在当前请求线程的局部变量中就会有一个栈(Stack),在栈的顶部放了一个CommandContext,在这个CommandContext中有待执行的Command,有processEngineConfiguration,还有agenda。

它这个CommandContext被设计成是每个线程私有的,就是每个线程都有自己的一个CommandContext

在线程局部变量中存放这栈,栈里面放着对象

874963-20220922141851276-1535023142.png
874963-20220922141857904-1864222378.png

拦截器链的最后一个拦截器是 CommandInvoker

4.  CommandInvoker

874963-20220922142628685-903209248.png

重头戏来了,接下来 CommandInvoker 的每个方法都要仔细看了

874963-20220922144141780-776760826.png

可以看到,真正去执行命令是在CommandInvoker中触发的

5.  Agenda

874963-20220922144647213-1223442219.png
874963-20220922145038579-478477916.png

agenda (译:议程,待议事项,议事日程)的意思是“议程”,“会议议程”,“待议事项”

可以把 agenda 想象成是一个会议,首先每个命令请求都有一个 CommandContext,CommandContext里面有Agenda

这样的话,CommandContext 相当于会议室,Agenda 相当于这次会议的议程,就是这次会议要商议的事项有哪些,每个 Operation 相当于一个待议事项,在会议进行期间会不断产生很多新的事项,然后一个一个事项的过,直到所有的事项都处理完了。

也可以把 agenda 想象成线程池,不断有新的任务被丢进线程池,工作线程就不断从工作队列中取任务执行

还是 “会议室 --> 会议 --> 议程 --> 事项 --> 处理事项”更加形象生动

每个命令请求就相当于发起一次会议,会议的目的是处理这次的命令请求。为了开会讨论解决问题,需要有个会议室,然后发起会议,会议上有很多要解决的问题,一个一个解决问题,直到所有问题都被解决,会议结束

874963-20220922151518661-1602398962.png

每个待议事项都是一个 Runnable 类型的对象,注意别搞混了,Runnable 本身不是线程。我个人猜测,之所以设计成Runnable类型的主要是为了方便异步处理,我们可以配置Activiti的活动是同步还是异步执行,而直接调用Runnable的run()方法就是同步执行,把它放到线程池就是异步执行,业务处理的逻辑都在run()方法里,完全不用关心是同步还是异步执行,这种设计太绝了,妙啊。。。(PS:纯属个人猜测,没有求证过,O(∩_∩)O哈哈~)

命令执行的结果放到会议室(CommandContext)

活动结束后,会调用planContinueProcessOperation(),流程继续执行,进入下一个活动节点

6.  CompleteTaskCmd

回到最初的完成任务命令,我们指定任务执行调用的Runnable的run()方法,run()方法里面是调用命令的execute方法

874963-20220922153432292-992334856.png

所以,接下来看完成任务这个命令具体做了什么

874963-20220922155223414-104477050.png
874963-20220922155231313-1840546913.png
874963-20220922155242861-97798705.png

7.  ActivityBehavior

要理解 Behavior 必须要和流程图联系起来,流程图上的一些元素比如 网关、用户任务、子流程、事件等等都有对应的行为,每种行为的处理方式都不同

ActivityBehavior 的实现类比较多,层级也比较深,不一一列举,以其中一个为例看看就行了

874963-20220922161509466-270315281.png

继续回到完成任务,刚才看到往agenda中放了一个 TriggerExecutionOperation,该操作触发等待状态并继续该流程,并离开该活动。

874963-20220922162340072-1657495321.png

874963-20220922162348444-872296796.png

Command:命令

ActivitiEngineAgendaFactory:用于创建ActivitiEngineAgenda

ActivitiEngineAgenda:议程,待议事项,用于循环执行Operation

AbstractOperation:事项/操作,它实现了Runnable接口

CommandContextFactory:用于创建CommandContext

CommandContext:每个命令执行线程都有自己的CommandContext,其内部有对Command和Agenda的引用

CommandExecutor:执行Command,从拦截器链的第一个拦截器开始执行

CommandInvoker:拦截器链上的最后一个拦截器,负责将命令封装成Operation,在Agenda中执行Operation的时候就会调用具体命令的execute方法

ActivityBehavior:代表活动的行为,这是真正底层的驱动流程流转的核心

874963-20220922172401885-1213842567.png
感谢您的阅读,如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!如需远程调代码或讲解或需要源码,请打赏!
欢迎各位转载,但必须在文章页面中给出作者和原文链接!

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK