整体架构
Activiti采用了一个分层架构完成自底向上的包装。架构图如下
大致包括:
核心接口层,被PVM接口定义。PVM会在后面的章节中详细讲述。
核心实现层,基于PVM的思想和接口,定义了一些关键实体包含ActivityImpl(该类抽象了节点实现),FlowElementBehavior实现(该类抽象了节点指令动作),ExecutionImpl(流程执行实体类)
命令层,Activiti在编码模式上直接限定整体风格为命令模式。也就是将业务逻辑封装为一个个的Command接口实现类。这样新增一个业务功能时只需要新增一个Command实现即可。这里需要特别提到的是,命令本身需要运行在命令上下文中,也就是CommandContext类对象。
命令拦截层,采用责任链模式,通过责任链模式的拦截器层,为命令的执行创造条件。诸如开启事务,创建CommandContext上下文,记录日志等
业务接口层,面向业务,提供了各种接口。这部分的接口就不再面向框架开发者了,而是面向框架的使用者。
部署层,严格来说,这个与上面说到的并不是一个完整的分层体系。但是为了突出重要性,单独拿出来说。流程运转的前提是流程定义。而流程定义解析就是一切的开始。从领域语言解析为Java的POJO对象依靠的就是部署层。后文还会细说这个环节。
流程引擎,所有接口的总入口。上面提到的业务接口层,部署层都可以从流程引擎类中得到。因此这里的流程引擎接口其实类似门面模式,只作为提供入口。
命令模式
Activit整体上采用命令模式进行代码功能解耦。将流程引擎的大部分涉及到客户端的需求让外部以具体命令实现类的方式实现。
完成这个编码模式,有几个重点类需要关注
引擎内的大部分功能都是通过单独的命令完成。
责任链模式
Activiti的命令模式还需要搭配其对应的责任链来完成。具体来说,Activiti中存在一个命令拦截器链条,该命令拦截器链条由几大块的拦截器实现组成,如下
其中重要的默认拦截器有2个:
事务拦截器,主要职责是使得后续命令运行在事务环境下。
CommandContext拦截器,主要职责是在有必要的时候创建CommandContext对象,并在使用完成后关闭该上下文。
常用拦截器:
责任链模式构建源码分析:
ProcessEngineConfigurationImpl:
事务拦截器命令上下文拦截器
该拦截器的功能非常重要,可以说是Activiti操作的核心之一。其作用是在后续拦截器执行前检查当前上下文环境,如果不存在CommandContext对象,则创建一个;在后续拦截器执行后,将CommandContext对象close。CommandContext包含了本次操作中涉及到所有的数据对象。
流程定义解析
Activiti遵循BPMN2.0规范,因此框架中少不了对BPMN2.0规范的定义文件(XML形式)的解析类。Activiti采用的STAX的拉模型进行XML解析。这里先不分析其具体的解析类的内在联系,而是概念性的阐述下Activiti对解析的概念分层。
包下面的与各个XML元素定义对应的POJO类。此时这些POJO类仅仅只是XML文件的一个Java表达。
三者之间的关系简单用图表达就是
Activiti之PVM执行树
核心理念
任何框架都是核心理念上发展细化而来。Activiti的核心理念就是流程虚拟机(Process Virtual Machine,以下简称PVM)。PVM试图提供一组API,通过API本身来描述工作流方面的各种可能性。没有了具体实现,也使得PVM本身可以较好的适应各种不同的工作流领域语言,而Activiti本身也是在PVM上的一种实现。
PVM对流程定义期的描述
首先来看下流程定义本身。在工作流中,流程定义可以图形化的表达为一组节点和连接构成的集合。比如下图
即使没有任何知识也能大概明白这张图表达的是一个流程以及执行顺序的意图。流程定义的表达方式不限,可以使用图形的方式表达,可以使用领域语言,也可以传统的XML(比如Activiti用的就是BPMN2.0 Schema下的XML)。特别的,当前已经有了标准化的BPMN2.0规范。
PVM将流程定义描述为流程元素的集合。再将流程元素细分为2个子类:流程节点和连线。
流程节点是某一种动作表达的抽象描述。节点本身是可以嵌套的,也就是节点可以拥有子节点。
连线表达是不同节点之间的转移关系。一个连线只能有一个源头节点和一个目标节点。而节点本身可以有任意多的进入连线和外出连线。
从类图的角度也能很好的看出这种关系,流程节点PvmActivity和连线PvmTransition都是流程元素PvmProcessElement。
从类图可以看到PvmActivity继承于PvmScope。这种继承关系表明流程节点本身有其归于的作用域(PvmScope),节点本身也可能是另外一些节点的作用域,这也符合节点可能拥有子节点的原则。关于作用域本身,后文还会再次详细讲解,这里先按下不表
PVM对流程运行期的描述
通过流程节点和连线,PVM完成了对流程定义的表达。流程定义是一个流程的静态表达,流程执行则是依照流程定义启动的一个运行期表达,每一个流程执行都具备自己唯一的生命周期。流程执行需要具备以下要素:
流程节点的具体执行动作。
Activiti提供了接口。该接口内部仅有一个execute方法。该接口的实现即为不同PvmActivity节点提供了具体动作。ActivityBehavior有丰富的不同实现,对应了流程中丰富的不同功能的节点。每一个PvmActivity对象都会持有一个ActivityBehavior对象。
流程执行当前处于哪一个流程节点。
Activiti提供了接口。该接口有一个方法。用以返回当前流程执行所处的流程节点。
PVM综述
从上面对PVM定义期和运行期的解释可以看出,整个概念体系并不复杂。涉及到的类也不多。正是因为PVM只对工作流中最基础的部分做了抽象和接口定义,使得PVM的实现上有了很多的可能性。
然而也正是由于定义的简单性,实际上这套PVM在转化为实际实现的时候需要额外附加很多的特性才能真正完成框架需求。
ActivitiImpl与作用域
在解析完成后,一个流程定义中的所有节点都会被解析为ActivityImpl对象。ActivityImpl对象本身可以持有事件订阅(根据BPMN2.0规范,目前有定时,消息,信号三种事件订阅类型)。因为ActivityImpl本身可以嵌套并且可以持有订阅,因此引入作用域概念(Scope)。
一个ActivityImpl在以下两种情况下会被定义为作用域ActivityImpl。
该ActivityImpl是可变范围,则它是一个作用域。可变范围可以理解为该节点的内容定义是可变的。比如流程定义、子流程,其内部内容是可变的。根据BPMN定义,可变范围有:流程定义,子流程,多实例,调用活动。
该ActivityImpl定义了一个上下文用于接收事件。比如:具备边界事件的ActivityImpl,具备事件子流程的ActivityImpl,事件驱动网关,中间事件捕获ActivityImpl。
作用域是一个很重要的概念,情况1中作用域定义的是复杂节点的生命周期,情况2中作用域定义的是事件的捕获范围。
发起流程实例源码
核心步骤
RuntimeServiceImpl#startProcessInstanceByKey
cmd.StartProcessInstanceCmd.execute()[日志,上下文,事务,命令模式调用者(execute cmd)]
#1.planOperation(newContinueProcessOperation(commandContext,execution))
ContinueProcessOperation#continueThroughFlowNode(currentFlowElement:startEvent)
NoneStartEventActivityBehavior.execute(execution)
TakeOutgoingSequenceFlowsOperation (currentFlowElement:startEvent)
ContinueProcessOperation#continueThroughFlowNode(currentFlowElement:userTask)
UserStartEventActivityBehavior.execute(execution)//重复步骤4
service API入口源码:
多个对象责任链依次执行:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UrgFNzHT-1615645423635)(\images\image-20210105142628489.png)]
CommandInvoker命令模式执行者源码:
执行StartProcessInstanceCmd的execute方法
------------END-----------
领取专属 10元无门槛券
私享最新 技术干货