
4.2 元素解析功能架构设计
4.2.1 BPMN2.0元素概述
绘制流程文档时,定义的元素需要遵循BPMN2.0规范。BPMN2.0规范将所有的流程定义元素抽象提取为三大要素如图4-1所示。

图4-1 BPMN2.0三大要素
(1)Event(事件):流程的创建、流转、结束等均需要事件支持,例如在流程文档绘制阶段,定义开始节点和结束节点是一个必不可少的环节。可通过事件机制为工作流系统增加辅助功能。
(2)Gateways(网关):所谓“网关”就是用来辅助决定流程实例最终流转的目的地,可以用来并行执行节点、也可以作为聚合或者条件分支使用,常用的网关类型有排他网关、并行网关、兼容网关三种。
(3)Activities(活动):有生命周期的元素或者节点都可以称之为“活动”,例如任务节点、子流程、引用流程等。活动节点可以作为任何连线元素的源头或者目标。
举个通俗易懂的例子,可以把流程文档的定义过程想象为一个水流管道,水流的发源地、目的地可以理解为事件,水流的分支或者聚合可以理解为网关,水流途径的节点统称为活动,水流途径的管道则可以理解为连线,网关和连线最终决定水流的宏观走向以及运动轨迹。
4.2.2 元素解析功能架构设计
在深入学习流程文档解析之前,首先分析Activiti元素解析的功能架构设计图,如图4-2所示。

图4-2 元素解析功能架构设计
根据上图可以把Activiti元素解析功能架构分为三层。
(1)元素定义层。
元素定义层完全交给客户端,开发人员可以结合自己的业务场景组装一系列元素,最终完成流程文档的定义工作,流程文档定义完毕之后就可以直接调用元素解析层实现流程文档的解析工作。
(2)元素解析层。
负责定位流程文档、初始化元素解析器(包括子元素)、加载自定义元素解析器、查找元素解析器,最终将需要解析的元素以及属性进行解析,并封装映射为引擎中的一个个实体对象。
(3)基础支撑层。
例如数据库连接管理事务管理等,Activiti将这些公用组件抽取出来作为基础模块使用,为上层的元素解析层提供基础服务支撑。
4.2.3 开闭原则
如何实现流程文档的解析工作,首先每个元素都需要定义公共属性,例如id、name等,可以把类似id、name等公共属性的解析封装为一个通用的解析方法,这样程序解析流程文档元素时,只需要调用该方法即可完成公共属性的解析工作。流程文档中还有些元素的属性信息是容易变化的,为什么容易变化呢,因为Activiti版本升级时可能会对部分元素添加新特性,此时就需要对扩展元素对应的属性承载类添加一个新的属性定义,更致命的问题是添加新的属性就需要修改该元素的解析代码,为了应对类似这样的变化,Activiti为每一个元素包括子元素定义了一个对应的元素解析器,从而使每一个元素解析器只负责一个元素的解析工作,这样设计的好处就是使元素与元素解析器一一对应,分离抽取元素解析器的职责,进而让每一个元素解析器之间尽量相互独立运行,当任意一个元素解析器的逻辑需要修改时不会级联修改其他的元素解析器,这样流程文档自上而下解析时只需根据元素名称查找元素对应的解析器进行处理即可,下面用图4-3对该处理流程进行通俗易懂的描绘。

图4-3 流程文档解析过程通俗的理解
根据图4-3,可以发现上文写的解析流程文档的代码有一个很大的缺陷,该处理过程中程序做了大量判断当前标记或事件类型的逻辑,并根据不同的事件类型进行不同的处理,所有元素的解析代码糅合在一起,如果现在需要在XML文档中新增加一个元素,就需要修改整个解析方法,这样的设计严重违反了开闭原则(对扩展开放、对修改关闭),为了遵守开闭原则,程序在设计之初需要对系统进行抽象、提取,更加直观一点就是面向接口编程,抽象化是开闭原则实现的基石,具体的实现思路就是可以在系统中定义一个相对稳定的抽象层或者定义一个通用的模板方法,将不同的实现行为移至具体的实现层中完成,这样扩展类行为的时候只需要修改具体的实现层代码即可,而无须对抽象层过多修改,附带的好处就是类与类之间相互独立,职责更加明确,不会出现类之间相互污染的情况。
4.2.4 元素与元素属性承载类以及元素解析器的对应关系
上文中提到了元素与元素解析器通常是一对一关系,接下来讲解元素以及元素对应的解析器、元素属性承载类(元素解析的同时,需要将已经解析完毕的属性值封装到指定的类中,为了便于区分,将这一系列的类统称为元素属性承载类)三者之间的关系,如表4-2所示,如果期望查找某个元素的解析逻辑,可以直接通过表4-2查找元素对应的解析器,然后查看元素解析器中的处理逻辑即可。
表4-2 元素、元素属性承载类和元素解析器对应关系

注意
BaseBpmnXMLConverter类作为所有元素解析器的基类存在。
4.2.5 元素属性承载类架构
下面重点讲解表4-2中的元素属性承载类,首先分析BaseElement类的设计架构,如图4-4所示。

图4-4 实体对象架构
通过上面的类图,可以很清晰地从全局角度了解BaseElement的脉络,后续章节会逐步讲解上面类图中涉及的类,接下来先大致讲解各个类的职责。
• HasExtensionAttributes:该接口定义了用户自定义属性的存储和获取方法。因为所有的元素属性承载类都实现了该接口,所以可以大胆推测所有的元素都是可以扩展的。
• BaseElement:该抽象类实现了HasExtensionAttributes接口,封装了id、xmlRowNumber、xmlColumnNumber(元素在XML文件中的坐标信息)等属性以及克隆BaseElement实例对象的抽象方法。
• FlowElement:该抽象类继承BaseElement类,封装了元素定义的名称(name)、描述信息(documentation)、执行监听器(executionListeners)属性信息以及克隆FlowElement实例对象的抽象方法。
• FlowNode:该抽象类继承FlowElement类,并对元素的如下属性进行了定义,异步执行(asynchronous)、独占模式(notExclusive)、节点的出线(incomingFlows)、节点的入线(outgoingFlows)等信息。
• Activity:该抽象类继承FlowNode类,并对元素的如下属性进行了定义,默认连线(defaultFlow)、多实例(loopCharacteristics)等信息,该类为子流程、引用流程以及Task节点的基类。
• Task:该抽象类继承Activity类,该类是所有任务类的基类,例如ScriptTask、UserTask等。
• SubProcess:子流程元素属性承载类,子流程中所有的元素都在该类中聚合。
• Event:该抽象类继承FlowNode类,是StartEvent、EndEvent等事件类型的基类。
• Gateway:ParallelGateway、InclusiveGateway等网关的父类。
• SequenceFlow:对连线信息进行封装,例如条件表达式conditionExpression等。
• Artifact:该抽象类继承BaseElement类,该类中定义了克隆Artifact实例对象的抽象方法。