Activiti权威指南
上QQ阅读APP看书,第一时间看更新

4.3 元素解析环境准备

4.3.1 文档转换器

3.6节详细讲解了流程文档内容与BpmnModel实例对象的相互转化,该转换过程涉及流程文档中的元素解析,下面详细讲解引擎解析流程文档的具体处理步骤,首先定义一个流程文档oneTaskProcess.bpmn20.xml,该流程文档的内容如代码清单4-3所示,流程定义图如图4-5所示。

图4-5 文档定义图

代码清单4-3 oneTaskProcess.bpmn20.xml

上述流程文档中定义了开始节点start、任务节点operationTask、usertask和结束节点End。

注意

所有元素的属性id值必须在流程文档中全局唯一。

下面定义一个类,该类主要用于解析流程文档,如代码清单4-4所示。

代码清单4-4 ExtensionOperationProcessTest.java

将上面代码中convertToBpmnModel方法的处理逻辑进行如下总结。

(1)第3~4行获取流程文档的文件流。流程文档的文件流获取方式有很多,可以使用绝对定位的方式获取,也可以从网络资源加载获取,本案例使用类加载器的方式获取流程文档的文件流。

(2)包裹文件流。第5行将获取到的InputStream类型的输入流,然后将其包裹为Activiti引擎可识别的StreamSource。

(3)第6行实例化BpmnXMLConverter类,该类非常重要,负责调度元素的解析工作,并维护元素解析器与元素之间的对应关系。

(4)第7~8行调用BpmnXMLConverter实例对象的convertToBpmnModel方法解析元素,该方法最终返回BpmnModel类型的实例对象bpmnModel,并将元素解析之后的结果填充到bpmnModel对象中。配合下面的时序图(如图4-6所示),可能会更加容易理解。

图4-6 BpmnXMLConverter类的convertToBpmnModel方法时序图

上面的代码中,构造了StreamSource实例对象,这样后续进行资源处理时就可以通过StreamSource实例对象获取文件流信息,那么StreamSource文件流是如何封装的呢?

4.3.2 封装流程文档数据流

StreamSource接口的定义如代码清单4-5所示。

代码清单4-5 StreamSource.java

StreamSource接口继承InputStreamProvider接口,该接口只定义了一个方法getInputStream(),该方法返回InputStream实例对象,不同来源的资源文件都有相应的StreamSource实现:如byte数组(BytesStreamSource)、InputStream资源(InputStreamSource)、URL网络资源(UrlStreamSource)、classpath资源(ResourceStreamSource)等,该类的类图如图4-7所示。

图4-7 资源文件处理相关类图

在实际项目开发中,资源文件的定位以及数据流的获取需要经常使用,如果不打算自己实现则可以直接通过Activiti提供的相关类进行操作,如代码清单4-6所示。

代码清单4-6 ExtensionOperationProcessTest.java

获取到inputStream实例对象之后,就可以按照平时的开发方式进行相关实现。StreamSource接口可以对需要操作的资源文件进行统一处理,其实现原理非常简单,以ResourceStreamSource类中的getInputStream方法为例,该方法的实现方式便是直接调用classLoader提供的底层方法进行操作。

StreamSource接口定义了getInputStream方法,并提供了一系列访问不同资源的实现类,而BpmnXMLConverter类中的convertToBpmnModel方法需要InputStreamProvider类型的参数,这样的设计是典型的策略模式,了解了StreamSource类的相关设计原理之后,接下来深入探究BpmnXMLConverter类的初始化过程。

4.3.3 初始化元素解析器

代码清单4-4实例化了BpmnXMLConverter类,实例化该类的同时流程引擎做了什么工作呢?首先进入BpmnXMLConverter类,该类的相关定义如代码清单4-7所示。

代码清单4-7 BpmnXMLConverter.java

BpmnXMLConverter类中并没有相关构造方法,所以接下来分析该类的静态代码块,首先明确一点,该类的静态代码块主要用于初始化类中的各种属性值,且类中的静态代码块只会被执行一次,由于实例化BpmnXMLConverter类的同时该类已经被JVM加载,所以会首先执行第5~9行代码,该代码则直接调用第10行以及第13行定义的方法,最终将流程元素以及其对应的解析器添加convertersToBpmnMap和convertersToXMLMap集合。

流程文档中的大部分元素与之对应的解析器均是一一对应关系,但对于dataObject类型的元素来说,就需要特殊处理一下,因为该类型的元素仅仅是数据类型不同而已,其他属性定义几乎完全相同,常用数据类型的有String、Boolean、Integer等,因此没有必要为每一种具体的数据类型单独定义一个解析器,只需要在dataObject元素对应的解析器中根据数据类型进行区分处理即可,附带的好处就是可以将不同数据类型的元素解析工作集中起来管理,这样也可以控制不同数据类型的元素按照指定的先后顺序进行解析。

注意

ValuedDataObjectXMLConverter类负责解析dataObject元素。

第5~9行代码执行完之后,开始实例化第1~2行中的各种内置元素解析器,例如signal元素的解析器SignalParser。第14行使用convertersToBpmnMap集合存储元素以及元素对应的解析器,该集合为Map数据结构,key为String类型,存储流程文档中定义的元素名称,对应converter.getXMLElementName()方法的返回值(流程文档中元素的名称), value为元素对应的解析器,例如解析结束事件(endEvent)元素的时候可以直接从convertersToBpmnMap集合中查找key为endEvent的值,这样就可以查询到EndEventXMLConverter类。

思考一个问题:为什么使用Map数据结构存储,而不是其他数据结构方式存储,例如List方式存储,关于这样设计的动机和意图可以参考4.4.1节的讲解。

4.3.4 文档转换器功能

了解了BpmnXMLConverter类的初始化过程之后,接下来分析该类的功能结构,如图4-8所示。

图4-8 BpmnXMLConverter类功能结构图

图4-8描述了BpmnXMLConverter类中的核心方法,下面细化讲解该类中的方法所提供的功能。

(1)convertToBpmnModel方法:解析流程文档中的元素,最终将元素解析结果封装为BaseElement实例对象,该方法最终返回BpmnModel实例对象。可以将BpmnModel实例对象理解为流程文档解析之后的内存对象,流程文档中所有元素的解析结果均存储在该实例对象中,开发人员可以直接通过该实例对象获取流程文档中定义的所有元素信息。

(2)convertToXML方法:将BpmnModel实例对象转化为流程文档内容,该方法的操作与convertToBpmnModel方法的操作完全相反,convertToBpmnModel方法则将流程文档内容转化为BpmnModel实例对象。

(3)validateModel方法:使用BPMN20.xsd文件以及该文件所引入的其他XSD文件来验证流程文档中定义的元素是否符合其约束。

(4)addConverter方法:向BpmnXMLConverter类中的convertersToBpmnMap和convertersToXMLMap集合添加元素解析器,开发人员可以通过该方法添加自定义元素解析器从而替换引擎默认的元素解析器。该方法非常重要。

4.3.5 元素解析环境准备

接下来详细分析BpmnXMLConverter类中convertToBpmnModel方法的实现逻辑,如代码清单4-8所示。

代码清单4-8 BpmnXMLConverter.java

上文详细讲解了STAX解析XML的处理流程,有了前面的学习基础,相信可以很轻松地掌握convertToBpmnModel方法的执行逻辑,下面对该方法的执行逻辑加以总结。

(1)第2行实例化XMLInputFactory工厂。

(2)第3~6行为XMLInputFactory实例对象添加防护措施,防止外部DTD或者XSD文件入侵。

(3)第9行根据inputStreamProvider对象中的文件流实例化InputStreamReader类。

(4)第10行创建XMLStreamReader实例对象。

(5)第12行如果开启了Schema文件验证,则需要验证流程文档中定义的元素是否符合XSD文件约束要求。

(6)流程文档验证之后,第18~19行需要重新打开InputStreamReader流并实例化XMLStreamReader类,因为Schema文件验证完毕之后该流已经被关闭了,因此需要重新打开该流。

(7)以上所有步骤操作无误之后,第22行直接委托convertToBpmnModel(xtr)方法解析流程文档元素,配合时序图如图4-9所示,可能会更容易理解。

图4-9 调用BpmnXMLConverter类的convertToBpmnModel方法时序图

(8)第23~26行关闭文件流。

4.3.6 验证流程文档格式

从代码清单4-8的处理逻辑可以看出,只有validateSchema参数值为true的情况下才会开启流程文档元素的验证工作,开启流程文档元素验证之后,根据enableSafeBpmnXml参数值执行不同的逻辑,具体实现如代码清单4-9所示。

代码清单4-9 BpmnXMLConverter.javaschema验证XML

通过分析上面代码的处理逻辑可知不管使用什么方式验证Schema文件,首先都会调用createSchema方法创建Schema实例对象,然后基于该对象获取验证器,最后直接使用验证器进行流程文档的验证工作,唯一的区别就是第4行代码使用StreamSource实例对象,第10行代码使用StAXSource实例对象。下面分析createSchema方法的执行逻辑,如代码清单4-10所示。

代码清单4-10 BpmnXMLConverter.java创建Schema

创建schema对象的逻辑非常简单,首先第3行获取工厂类SchemaFactory,然后第6行或者第9行调用该工厂类中的newSchema方法创建schema对象,newSchema方法依赖BPMN_XSD资源文件,第5行判断当前类的类加载器classloader属性值是否存在,如果存在则直接通过该类加载器获取BPMN_XSD文件流,否则第8行判断schema是否为空,如果不为空第9行通过BpmnXMLConverter类获取类加载器,然后再通过该类加载器获取BPMN_XSD文件流,第11行判断schema是否为空,如果为空则程序报错。

通过分析上面代码的处理逻辑可以看出classloader的使用优先级最高,如果开发人员想要为classloader属性赋值,只需要自定义一个文档转换器并继承BpmnXMLConverter类,然后为其设置classloader属性值即可。

注意

BPMN_XSD资源文件对应的XSD文件位于activiti-bpmn-converter-5.21.0.jar包中。

在正式环境建议设置enableSafeBpmnXml参数值为true,这样Activiti引擎解析流程文档时会立即验证流程文档中定义的元素是否符合BPMN20.xsd文件的约束要求,方便及早发现错误信息。