
4.5 子元素解析
4.5.1 初始化子元素解析器
在上述代码中,BpmnXMLUtil.addXMLLocation()方法用于获取元素坐标信息,因为addXMLLocation()方法为BpmnXMLUtil类中的静态方法,所以该方法被调用的同时会触发该类的静态代码块,如代码清单4-17所示。
代码清单4-17 BpmnXMLUti.java子元素解析器初始化

该静态代码块会加载所有的通用子元素解析器,并且只会加载一次,这里所说的通用子元素均指流程元素中的子元素信息,例如所有的流程元素都有文档描述元素"documentation"、"extensionElements"元素(可以定义执行监听器等)、条件表达式"conditionExpression"、多实例"multiInstanceLoopCharacteristics"等,因为这些子元素比较通用,所以将其单独抽取出来进行定义和解析,降低程序维护成本,所有的子元素解析器均需继承BaseChildElementParser类。上述代码中涉及了addGenericParser方法的调用,该方法是BpmnXMLUtil类的有私有静态方法,通过这里的设计可以看出,客户端不能通过该方法添加自定义子元素解析器,也不能入侵和干预子元素的解析工作,有没有其他办法可以替换默认子元素解析器呢?带着这个疑问,分析子元素的解析过程,期望从中找到答案。
注意
genericChildParserMap是Map数据结构,key为流程文档中对应的子元素名称, value为子元素对应的解析器。
4.5.2 解析子元素
在代码清单4-15中,调用了parseChildElements方法来解析当前元素的子元素,但是SequenceFlowXMLConverter类中并没有该方法的实现,既然该类中没有提供实现,那么其父类肯定提供了默认实现,所以找到当前类的父类BaseBpmnXMLConverter,相关实现如代码清单4-18所示。
代码清单4-18 BaseBpmnXMLConverter.java

在上述代码中,第2行直接委托第4行定义的方法解析子元素,并且设置additionalParsers参数值为null,该参数值为客户端自定义的子元素解析器集合,通过该步骤可以看出程序默认不加载用户自定义的子元素解析器,如果客户端需要添加自定义子元素解析器可以直接设置additionalParsers参数值。
第6行判断additionalParsers集合是否为空,如果不为空,则第7行将用户自定义的子元素解析器集合添加到childParsers集合中,然后第9行委托BpmnXMLUtil类中的parseChildElements方法解析子元素,parseChildElements方法的相关实现如代码清单4-19所示。
代码清单4-19 BpmnXMLUtil.java解析子元素

子元素的解析流程可以大致总结如下。
(1)第2~5行初始化子元素解析器集合localParserMap。
该集合的初始化工作分如下两步:
① 获取内置子元素解析器集合genericChildParserMap的值并将其添加到localParserMap集合中,genericChildParserMap集合的初始化过程可以参考该类的静态代码块;
② 获取childParsers参数值,如果该参数值不为空,则将该值添加到子元素解析器集合localParserMap中,因为localParserMap是Map数据结构,所以用户自定义的子元素解析器可以覆盖Activiti默认的子元素解析器,该参数值的有无非常重要,直接影响子元素解析时所需要使用的解析器。
(2)扩展元素判断。
第16行childParser.accepts(parentElement)方法的处理至关重要,如果父类元素不能识别文档中定义的子元素,那么引擎就认为这个元素为扩展元素并在第18~20行对其进行处理。
(3)解析扩展元素。
子元素的解析可以分为通用子元素解析(如"documentation"元素)和用户自定义扩展元素解析,其处理逻辑为:如果经过判断发现子元素为通用子元素,第24行根据子元素的名称获取子元素解析器进行子元素的解析工作;如果为用户自定义扩展元素则直接委托BpmnXMLUtil.parseExtensionElement(xtr)方法进行解析,该过程可以参考第5.1节的讲解。不管解析何种类型的子元素,均需要将元素解析之后的结果添加到父元素parentElement中。
看到这里可能有些疑问:parentElement是哪一个对象呢?其实也不难理解,不妨换一个思路思考这个问题,parseChildElements方法是由哪一个对象调用的呢?很显然该方法由具体解析类的实例对象进行调用,比如现在开始解析任务节点,则任务节点的解析类UserTaskXMLConverter解析任务节点时会调用parseChildElements方法进行子元素的解析工作,这时parentElement对象就对应UserTaskXMLConverter实例对象。
注意
childParser.accepts()方法的处理过程,可以参考FieldExtensionParser或者FormPropertyParser类的实现。
4.5.3 解析扩展元素
下面以process元素的执行监听器为例,详细分析扩展元素的解析过程,在讲解执行监听器的解析之前,先了解监听器是如何定义的,如代码清单4-20所示。
代码清单4-20 process元素定义监听器和ShareniuListener.java

流程文档中的三大要素都可以定义执行监听器和任务监听器(仅限于在任务节点中进行定义)。监听器通常作为扩展元素extensionElements的子元素进行定义,因为监听器可以很方便的让客户使用和扩展,所以本节详细讲解扩展元素的解析处理流程。执行监听器以及任务监听器的解析器分别为ExecutionListenerParser和TaskListenerParser,两者均继承了BaseChildElementParser类,并在父类中进行统一调度。既然监听器通常作为extensionElements的子元素存在,所以首先找到extensionElements元素的解析器ExtensionElementsParser,该类的相关定义如代码清单4-21所示。
代码清单4-21 ExtensionElementsParser.java解析监听器元素

ExtensionElementsParser类中parse方法的处理逻辑如下:
(1)第3~6行确定父级元素的类型,在解析extensionElements元素之前,首先要确定extensionElements的父级元素类型。
(2)第11行开始根据extensionElements元素中的子元素类型执行不同的逻辑,如果子元素为executionListener类型(执行监听器)则执行第13行代码;如果子元素为eventListener类型(事件监听器)则执行第15行代码,事件监听器的配置形如代码清单4-22第2~4行,如果子元素为potentialStarter类型(流程启动人)则执行第17行代码,流程启动人的配置参考代码清单4-22中第6~16行,如果子元素不是以上三种类型中的任意一个则执行19行代码解析用户自定义元素。
代码清单4-22流程事件监听器以及流程启动人的配置

在上述代码中,第10行user(shareniu)是引用了用户shareniu, group(shareniuGroup2)引用了组shareniuGroup2,如果定义时没有显式设置,例如shareniul,则引擎默认会将其作为组进行处理。关于potentialStarter元素的解析处理逻辑可以参考PotentialStarterParser类中的parse方法,对于流程启动人的配置也可以使用process元素中的属性进行配置如第15行代码所示,其中candidateStarterUsers用来配置启动人,candidateStarterGroups用来配置组,如果存在多个人或组则用“, ”进行分割。
任务监听器同样也是作为扩展元素extensionElements的子元素存在,但是这里并没有发现对任务监听器的处理踪迹,其实不难理解,因为流程三大要素中的节点都可以使用执行监听器,而任务监听器只可以在任务节点进行定义和使用,因此任务监听器的解析工作只需要在任务节点解析时处理即可,相关实现可以查看任务节点的解析器UserTaskXMLConverter。接下来分析process元素中执行监听器的处理逻辑,相关实现如代码清单4-23所示。
代码清单4-23 ActivitiListenerParser.java

通过上面的代码可以看出执行监听器的解析处理流程非常清晰。
(1)第2行实例化ActivitiListener类。
listener对象承载了执行监听器和任务监听器的存取工作,这两种类型的监听器仅是事件以及类型不同而已,其他的属性大体相同。
(2)第4~12行填充对象属性。
根据监听器的创建方式执行不同的处理逻辑,关于这里一点可以参考第11章。
(3)第15行将监听器添加到父级元素对象中。
因为监听器作为子元素存在,所以需要将ActivitiListener实例对象通过addListenerToParent方法添加到监听器的父级元素中,因为addListenerToParent方法是当前类的一个抽象方法,所以下面以任务监听器的实现为例详细分析该方法的处理逻辑如代码清单4-24所示。执行监听器ExecutionListenerParser类中关于该方法的实现逻辑可参考该案例自行分析。
代码清单4-24 TaskListenerParser.java

上面代码的处理逻辑非常简单,直接判断当前元素是否为任务节点,如果是则通过getTaskListeners方法获取存储当前监听器的集合,然后将listener元素添加到该集合中;否则不予添加操作。
(4)解析子元素。
看到这里可能有个疑问:监听器也存在子元素吗?答案是肯定的,监听器中可以配置变量,变量可以是具体值,也可以是表达式。ActivitiListenerParser类中的parseChildElements方法主要负责解析监听器中field元素,field元素的配置形如<activiti:field name="shareniu"expression="${shareniu}"stringValue="shareniu"></activiti:field>,通过该方法中的输入参数类型可以知道field元素的解析工作交给FieldExtensionParser类完成,接下来分析该解析器的核心处理逻辑,如代码清单4-25所示。
代码清单4-25 FieldExtensionParser.java

field元素的解析处理逻辑如下。
(1)第6行调用accepts方法验证field元素是否可以进行解析。
(2)通过accepts方法可以看出目前只有ActivitiListener、ServiceTask、SendTask可以定义field元素。
(3)第7行实例化field元素的属性承载类FieldExtension。
(4)第9~13行获取field元素中的name、stringValue、expression属性值并将其填充到FieldExtension实例对象中。
(5)第17~26行循环遍历field元素中的子元素string、expression并将其填充到FieldExtension实例对象中。看到这里可能会有疑问:步骤(3)不是已经完成属性填充工作了吗?怎么这里还需要进行属性填充呢?步骤(3)解析的是field元素中的属性形如<activiti:fieldname="a"expression=""stringValue=""></activiti:field,步骤(4)解析的是field元素中的子元素,形如:<activiti:fieldname="a"><activiti:string>a</activiti:string><activiti:expression>s< /activiti:expression></activiti:field,如果掌握了field元素是如何进行配置的,相信该处理逻辑也很容易理解。
(6)第30~36行根据parentElement类型进行区分处理并将extension对象添加到父类中。