如何实现Activiti的分支条件的自定义配置(转)
一、Activiti的流程分支条件的局限
Activiti的流程分支条件目前是采用脚本判断方式,并且需要在流程定义中进行分支条件的设定,如下图所示:

- <sequenceFlow id="flow2" sourceRef="exclusiveGw" targetRef="theTask1">
- <conditionExpression xsi:type="tFormalExpression">${input == 1}</conditionExpression>
- </sequenceFlow>
- <sequenceFlow id="flow3" sourceRef="exclusiveGw" targetRef="theTask2">
- <conditionExpression xsi:type="tFormalExpression">${input == 2}</conditionExpression>
- </sequenceFlow>
- <sequenceFlow id="flow4" sourceRef="exclusiveGw" targetRef="theTask3">
- <conditionExpression xsi:type="tFormalExpression">${input == 3}</conditionExpression>
- </sequenceFlow>
从上面的定义可以看到,流程的分支条件存在以下两个致命的局限性:
1.分支条件需要在流程定义(XML)中设定,这要求流程定义必须由开发人员来设计及编写
2.分支条件比较简单,一般为boolean表达式,表达式里的为单变量的判断处理。
以上两个局限性限制了流程的分支判断处理必须由开发人员来设定,而国内的大部分的流程应用都要求是普通的业务人员即可处理,或者是由有一定计算机基础的人员来设置处理。这要求我们对流程的条件设置提出了更高的要求,上一节我们通过修改Activiti的流程定义的XML中的分支条件表达式,同时刷新流程定义的引擎缓存,如下的代码就是基于这种方式:
- JsonNode jsonObject=objectMapper.readTree(configJson);
- JsonNode configsNode=jsonObject.get("configs");
- BpmSolution bpmSolution=bpmSolutionManager.get(solId);
- BpmDef bpmDef=bpmDefManager.getLatestBpmByKey(bpmSolution.getDefKey(), ContextUtil.getCurrentTenantId());
- ActProcessDef processDef=actRepService.getProcessDef(bpmDef.getActDefId());
- String processDefXml=actRepService.getBpmnXmlByDeployId(bpmDef.getActDepId());
- System.out.println("xml:"+processDefXml);
- ActNodeDef sourceNode=processDef.getNodesMap().get(nodeId);
- ByteArrayInputStream is=new ByteArrayInputStream(processDefXml.getBytes());
- Map<String,String> map = new HashMap<String,String>();
- map.put("bpm","http://www.omg.org/spec/BPMN/20100524/MODEL");
- map.put("xsi","http://www.omg.org/spec/BPMN/20100524/MODEL");
- SAXReader saxReader = new SAXReader();
- saxReader.getDocumentFactory().setXPathNamespaceURIs(map);
- Document doc = saxReader.read(is);
- //Document doc=Dom4jUtil.load(is, "UTF-8");
- Element rootEl=doc.getRootElement();
- if(configsNode!=){
- //取得分支条件列表
- JsonNode configs=configsNode.get("conditions");
- if(configs!=){
- Iterator<JsonNode> it=configs.elements();
- while(it.hasNext()){
- ObjectNode config=(ObjectNode)it.next();
- String tmpNodeId=config.get("nodeId").textValue();
- String tmpCondition=config.get("condition").textValue();
- Element seqFlow=(Element)rootEl.selectSingleNode("/bpm:definitions/bpm:process/bpm:sequenceFlow[@sourceRef='"
- +sourceNode.getNodeId()+"' and @targetRef='"+tmpNodeId+"']");
- if(seqFlow==) continue;
- Element conditionExpress=(Element)seqFlow.selectSingleNode("bpm:conditionExpression");
- if(conditionExpress==){
- conditionExpress=seqFlow.addElement("conditionExpression");
- conditionExpress.addAttribute("xsi:type", "tFormalExpression");
- }else{
- conditionExpress.clearContent();
- }
- conditionExpress.addCDATA(tmpCondition);
- }
- }
- }
- //修改流程定义的XML,并且清空该流程定义的缓存
- actRepService.doModifyXmlAndClearCache(bpmDef.getActDefId(),bpmDef.getActDepId(), doc.asXML());
【说明】
1.基于这种方式容易出错,因为流程的分支条件写回流程定义的XML是比较容易出问题的,同时不清楚人员填写什么条件回XML文件中。
2.对于Jsaas中的一个流程定义可用于多个流程解决方案中使用配置不同的条件不太适合,因为一个流程定义是一样,但可能会分支的条件设置不一样。
基于以上的要求,为此我们对Activiti进行扩展,以使得我们可以允许流程引擎在分支判断处理中,执行我们的条件设置,其原理如下:

当流程引擎跳至分支条件判断处理时,可以让它执行我们的脚本设置条件,条件满足时,则跳至我们的设置的目标节点,从而实现干预流程引擎本身的执行方式,为了不影响Activiti的原的运行机制,我们还是保留其旧的执行判断方式。
二、Activiti的扩展点
Activiti的流程扩展是比较灵活的,我们通过改写这个ExclusiveGateway的节点的行为方法即可,其实现方法如下:
- package com.redxun.bpm.activiti.ext;
- import java.util.Iterator;
- import javax.annotation.Resource;
- import org.activiti.engine.impl.bpmn.behavior.ExclusiveGatewayActivityBehavior;
- import org.activiti.engine.impl.pvm.PvmTransition;
- import org.activiti.engine.impl.pvm.delegate.ActivityExecution;
- import org.apache.commons.lang.StringUtils;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import com.redxun.bpm.core.entity.config.ExclusiveGatewayConfig;
- import com.redxun.bpm.core.entity.config.NodeExecuteScript;
- import com.redxun.bpm.core.manager.BpmNodeSetManager;
- import com.redxun.core.script.GroovyEngine;
- import com.sun.star.uno.RuntimeException;
- /**
- * 对网关的条件判断,优先使用扩展的配置
- * @author keitch
- *
- */
- @SuppressWarnings("serial")
- public class ExclusiveGatewayActivityBehaviorExt extends ExclusiveGatewayActivityBehavior{
- protected static Logger log = LoggerFactory.getLogger(ExclusiveGatewayActivityBehaviorExt.class);
- //节点的设置管理器
- @Resource
- BpmNodeSetManager bpmNodeSetManager;
- //脚本引擎
- @Resource GroovyEngine groovyEngine;
- @Override
- protected void leave(ActivityExecution execution) {
- log.debug("enter ExclusiveGatewayActivityBehaviorExt=======================");
- if (log.isDebugEnabled()) {
- log.debug("Leaving activity '{}'", execution.getActivity().getId());
- }
- String solId=(String)execution.getVariable("solId");
- String nodeId=execution.getActivity().getId();
- log.debug("solid is {} and nodeId is {}",solId,nodeId);
- if(StringUtils.isNotEmpty(solId)&& StringUtils.isNotBlank(nodeId)){
- ExclusiveGatewayConfig configs=bpmNodeSetManager.getExclusiveGatewayConfig(solId, nodeId);
- for(NodeExecuteScript script:configs.getConditions()){
- String destNodeId=script.getNodeId();
- String condition=script.getCondition();
- log.debug("dest node:{}, condition is {}",destNodeId,condition);
- //执行脚本引擎
- Object boolVal=groovyEngine.executeScripts(condition, execution.getVariables());
- if(boolVal instanceof Boolean){
- Boolean returnVal=(Boolean)boolVal;//符合条件
- if(returnVal==true){
- //找到符合条件的目标节点并且进行跳转
- Iterator<PvmTransition> transitionIterator = execution.getActivity().getOutgoingTransitions().iterator();
- while (transitionIterator.hasNext()) {
- PvmTransition seqFlow = transitionIterator.next();
- if(destNodeId.equals(seqFlow.getDestination().getId())){
- execution.take(seqFlow);
- return;
- }
- }
- }
- }else{
- throw new RuntimeException("表达式:\n "+condition+"\n返回值不为布尔值(true or false)");
- }
- }
- }
- //执行父类的写法,以使其还是可以支持旧式的在跳出线上写条件的做法
- super.leave(execution);
- }
- }
我们通过继续改写了这个分支节点的跳出机制,并且通过脚本引擎来执行其条件分支的判断处理,但流程引擎并不了解我们扩展的类,这时我们需要配置Activiti流程引擎的行为动作工厂类,如下所示:
- package com.redxun.bpm.activiti.ext;
- import org.activiti.bpmn.model.ExclusiveGateway;
- import org.activiti.engine.impl.bpmn.behavior.ExclusiveGatewayActivityBehavior;
- import org.activiti.engine.impl.bpmn.parser.factory.DefaultActivityBehaviorFactory;
- /**
- * 扩展缺省的流程节点默认工厂类,实现对Activiti节点的执行的默认行为的更改
- * @author keitch
- *
- */
- public class ActivityBehaviorFactoryExt extends DefaultActivityBehaviorFactory {
- private ExclusiveGatewayActivityBehaviorExt exclusiveGatewayActivityBehaviorExt;
- /**
- * 通过Spring容器注入新的分支条件行为执行类
- * @param exclusiveGatewayActivityBehaviorExt
- */
- public void setExclusiveGatewayActivityBehaviorExt(ExclusiveGatewayActivityBehaviorExt exclusiveGatewayActivityBehaviorExt) {
- this.exclusiveGatewayActivityBehaviorExt = exclusiveGatewayActivityBehaviorExt;
- }
- //重写父类中的分支条件行为执行类
- @Override
- public ExclusiveGatewayActivityBehavior createExclusiveGatewayActivityBehavior(ExclusiveGateway exclusiveGateway) {
- return exclusiveGatewayActivityBehaviorExt;
- }
- }
三、Activiti的Spring配置的更改
在Activiti的流程引擎配置中加入新的流程行为动作执行工厂类。配置如下所示,注意activityBehaviorFactory的属性配置:
- <bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
- <property name="dataSource" ref="dataSource" />
- <property name="transactionManager" ref="transactionManager" />
- <property name="databaseSchemaUpdate" value="true" />
- <property name="jobExecutorActivate" value="false" />
- <property name="enableDatabaseEventLogging" value="false" />
- <property name="databaseType" value="${db.type}" />
- <property name="idGenerator" ref="actIdGenerator"/>
- <property name="eventListeners">
- <list>
- <ref bean="globalEventListener"/>
- </list>
- </property>
- <property name="activityFontName" value="宋体"/>
- <property name="labelFontName" value="宋体"/>
- <!-- 用于更改流程节点的执行行为 -->
- <property name="activityBehaviorFactory" ref="activityBehaviorFactoryExt"/>
- </bean>
- <bean id="activityBehaviorFactoryExt" class="com.redxun.bpm.activiti.ext.ActivityBehaviorFactoryExt">
- <property name="exclusiveGatewayActivityBehaviorExt" ref="exclusiveGatewayActivityBehaviorExt"/>
- </bean>
- <bean id="exclusiveGatewayActivityBehaviorExt" class="com.redxun.bpm.activiti.ext.ExclusiveGatewayActivityBehaviorExt"/>
通过以上方式扩展后,节点的分支设置可以把条件表达式写成以下方式,同时条件存于流程定义的外部表中:

如何实现Activiti的分支条件的自定义配置(转)的更多相关文章
- Activiti第一篇【介绍、配置开发环境、快速入门】
Activiti介绍 什么是Activiti? Activiti5是由Alfresco软件在2010年5月17日发布的业务流程管理(BPM)框架,它是覆盖了业务流程管理.工作流.服务协作等领域的一个开 ...
- 让Spring Boot项目启动时可以根据自定义配置决定初始化哪些Bean
让Spring Boot项目启动时可以根据自定义配置决定初始化哪些Bean 问题描述 实现思路 思路一 [不符合要求] 思路二[满足要求] 思路三[未试验] 问题描述 目前我工作环境下,后端主要的框架 ...
- git学习 分支特殊处理和配置03
Bug分支: 当在一个分支上工作的时候:突然到其它分支修复bug,当前分支工作还没到要提交的程度:这时候可以使用git stash来将工作分支暂时存储起来: 用git stash list查看stas ...
- SpringBoot入门(五)——自定义配置
本文来自网易云社区 大部分比萨店也提供某种形式的自动配置.你可以点荤比萨.素比萨.香辣意大利比萨,或者是自动配置比萨中的极品--至尊比萨.在下单时,你并没有指定具体的辅料,你所点的比萨种类决定了所用的 ...
- Spring MVC内容协商实现原理及自定义配置【享学Spring MVC】
每篇一句 在绝对力量面前,一切技巧都是浮云 前言 上文 介绍了Http内容协商的一些概念,以及Spring MVC内置的4种协商方式使用介绍.本文主要针对Spring MVC内容协商方式:从步骤.原理 ...
- Java之SpringBoot自定义配置与整合Druid
Java之SpringBoot自定义配置与整合Druid SpringBoot配置文件 优先级 前面SpringBoot基础有提到,关于SpringBoot配置文件可以是properties或者是ya ...
- 前台主页搭建、后台主页轮播图接口设计、跨域问题详解、前后端互通、后端自定义配置、git软件的初步介绍
今日内容概要 前台主页 后台主页轮播图接口 跨域问题详解 前后端打通 后端自定义配置 git介绍和安装 内容详细 1.前台主页 Homeviwe.vue <template> <di ...
- ASP.NET 5 入门 (2) – 自定义配置
ASP.NET 5 入门 (2) – 自定义配置 ASP.NET 5 理解和入门 建立和开发ASP.NET 5 项目 初步理解ASP.NET5的配置 正如我的第一篇文章ASP.NET 5 (vNext ...
- 基于Spring的可扩展Schema进行开发自定义配置标签支持
一.背景 最近和朋友一起想开发一个类似alibaba dubbo的功能的工具,其中就用到了基于Spring的可扩展Schema进行开发自定义配置标签支持,通过上网查资料自己写了一个demo.今天在这里 ...
随机推荐
- wdk1703+vs2015编译的诡异问题
最近将wdk升级到1703(10.0.15063.0)版本,编译一个新建的minifiter项目居然出现了失败 提示错误为 WindowsDriver.common.targets(460,5): e ...
- tomcat启动成功后访问却404
1.检查是否把项目添加进tomcat,好久不用tomcat这次就犯了这种低级错误 2.检查路径,tomcat中的访问路径与项目中设置的路径是否一样,因为这次有些配置文件直接复制的源码,但源码中项目名称 ...
- azkaban架构介绍
转自:https://blog.csdn.net/huoji1990/article/details/81911904 官网:https://azkaban.readthedocs.io/en/lat ...
- swift-UIPickerView(选择控件)
import UIKit //UIPickerView 的委托协议是 UIPickerViewDelegate,数据源是 UIPickerViewDataSource.我们需要在视图控制器中声明实现 ...
- oracle数据库用户删除及表空间删除
以system用户登录,查找需要删除的用户: --查找用户 select * from dba_users; --查找工作空间的路径select * from dba_data_files; --删 ...
- vue中less文件全局引用
1.先安装sass-resources-loader npm install sass-resources-loader 2.然后在build->utils.js修改less配置 在less ...
- tensorflow 升级到1.9-rc0,tensorboard 报错:TypeError: GetNext() takes exactly 1 argument (2 given)
Exception in thread Reloader:Traceback (most recent call last): File "/usr/lib/python2.7/threa ...
- Taro之使用百度地图
适配h5的时候要使用地图功能获取位置,选取了百度地图.首先在index.html文件引入. <script type="text/javascript" src=" ...
- layui-xtree 设置单选框,只能选一个
以下是js代码,首先获取所有节点,再设置只有当前点击的节点状态为选中状态 $.ajax({ type: 'get', url: url, error: function(err){ layer.ale ...
- Linux系统中无iptables文件的解决
在RHEL 7 / CentOS 7中,firewalld被引入来管理iptables,CentOS7开始,默认是没有iptables的,而是使用firewall防火墙.本文将屏蔽掉firewall, ...