一、Activiti的流程分支条件的局限

Activiti的流程分支条件目前是采用脚本判断方式,并且需要在流程定义中进行分支条件的设定,如下图所示:

  1. <sequenceFlow id="flow2" sourceRef="exclusiveGw" targetRef="theTask1">
  2. <conditionExpression xsi:type="tFormalExpression">${input == 1}</conditionExpression>
  3. </sequenceFlow>
  4. <sequenceFlow id="flow3" sourceRef="exclusiveGw" targetRef="theTask2">
  5. <conditionExpression xsi:type="tFormalExpression">${input == 2}</conditionExpression>
  6. </sequenceFlow>
  7. <sequenceFlow id="flow4" sourceRef="exclusiveGw" targetRef="theTask3">
  8. <conditionExpression xsi:type="tFormalExpression">${input == 3}</conditionExpression>
  9. </sequenceFlow>

从上面的定义可以看到,流程的分支条件存在以下两个致命的局限性:

1.分支条件需要在流程定义(XML)中设定,这要求流程定义必须由开发人员来设计及编写

2.分支条件比较简单,一般为boolean表达式,表达式里的为单变量的判断处理。

以上两个局限性限制了流程的分支判断处理必须由开发人员来设定,而国内的大部分的流程应用都要求是普通的业务人员即可处理,或者是由有一定计算机基础的人员来设置处理。这要求我们对流程的条件设置提出了更高的要求,上一节我们通过修改Activiti的流程定义的XML中的分支条件表达式,同时刷新流程定义的引擎缓存,如下的代码就是基于这种方式:

  1. JsonNode jsonObject=objectMapper.readTree(configJson);
  2. JsonNode configsNode=jsonObject.get("configs");
  3. BpmSolution bpmSolution=bpmSolutionManager.get(solId);
  4. BpmDef bpmDef=bpmDefManager.getLatestBpmByKey(bpmSolution.getDefKey(), ContextUtil.getCurrentTenantId());
  5. ActProcessDef processDef=actRepService.getProcessDef(bpmDef.getActDefId());
  6. String processDefXml=actRepService.getBpmnXmlByDeployId(bpmDef.getActDepId());
  7. System.out.println("xml:"+processDefXml);
  8. ActNodeDef sourceNode=processDef.getNodesMap().get(nodeId);
  9. ByteArrayInputStream is=new ByteArrayInputStream(processDefXml.getBytes());
  10. Map<String,String> map = new HashMap<String,String>();
  11. map.put("bpm","http://www.omg.org/spec/BPMN/20100524/MODEL");
  12. map.put("xsi","http://www.omg.org/spec/BPMN/20100524/MODEL");
  13. SAXReader saxReader = new SAXReader();
  14. saxReader.getDocumentFactory().setXPathNamespaceURIs(map);
  15. Document doc = saxReader.read(is);
  16. //Document doc=Dom4jUtil.load(is, "UTF-8");
  17. Element rootEl=doc.getRootElement();
  18. if(configsNode!=){
  19. //取得分支条件列表
  20. JsonNode configs=configsNode.get("conditions");
  21. if(configs!=){
  22. Iterator<JsonNode> it=configs.elements();
  23. while(it.hasNext()){
  24. ObjectNode config=(ObjectNode)it.next();
  25. String tmpNodeId=config.get("nodeId").textValue();
  26. String tmpCondition=config.get("condition").textValue();
  27. Element seqFlow=(Element)rootEl.selectSingleNode("/bpm:definitions/bpm:process/bpm:sequenceFlow[@sourceRef='"
  28. +sourceNode.getNodeId()+"' and @targetRef='"+tmpNodeId+"']");
  29. if(seqFlow==) continue;
  30. Element conditionExpress=(Element)seqFlow.selectSingleNode("bpm:conditionExpression");
  31. if(conditionExpress==){
  32. conditionExpress=seqFlow.addElement("conditionExpression");
  33. conditionExpress.addAttribute("xsi:type", "tFormalExpression");
  34. }else{
  35. conditionExpress.clearContent();
  36. }
  37. conditionExpress.addCDATA(tmpCondition);
  38. }
  39. }
  40. }
  41. //修改流程定义的XML,并且清空该流程定义的缓存
  42. actRepService.doModifyXmlAndClearCache(bpmDef.getActDefId(),bpmDef.getActDepId(), doc.asXML());

【说明】

1.基于这种方式容易出错,因为流程的分支条件写回流程定义的XML是比较容易出问题的,同时不清楚人员填写什么条件回XML文件中。

2.对于Jsaas中的一个流程定义可用于多个流程解决方案中使用配置不同的条件不太适合,因为一个流程定义是一样,但可能会分支的条件设置不一样。

基于以上的要求,为此我们对Activiti进行扩展,以使得我们可以允许流程引擎在分支判断处理中,执行我们的条件设置,其原理如下:

当流程引擎跳至分支条件判断处理时,可以让它执行我们的脚本设置条件,条件满足时,则跳至我们的设置的目标节点,从而实现干预流程引擎本身的执行方式,为了不影响Activiti的原的运行机制,我们还是保留其旧的执行判断方式。

二、Activiti的扩展点

Activiti的流程扩展是比较灵活的,我们通过改写这个ExclusiveGateway的节点的行为方法即可,其实现方法如下:

  1. package com.redxun.bpm.activiti.ext;
  2. import java.util.Iterator;
  3. import javax.annotation.Resource;
  4. import org.activiti.engine.impl.bpmn.behavior.ExclusiveGatewayActivityBehavior;
  5. import org.activiti.engine.impl.pvm.PvmTransition;
  6. import org.activiti.engine.impl.pvm.delegate.ActivityExecution;
  7. import org.apache.commons.lang.StringUtils;
  8. import org.slf4j.Logger;
  9. import org.slf4j.LoggerFactory;
  10. import com.redxun.bpm.core.entity.config.ExclusiveGatewayConfig;
  11. import com.redxun.bpm.core.entity.config.NodeExecuteScript;
  12. import com.redxun.bpm.core.manager.BpmNodeSetManager;
  13. import com.redxun.core.script.GroovyEngine;
  14. import com.sun.star.uno.RuntimeException;
  15. /**
  16. * 对网关的条件判断,优先使用扩展的配置
  17. * @author keitch
  18. *
  19. */
  20. @SuppressWarnings("serial")
  21. public class ExclusiveGatewayActivityBehaviorExt extends ExclusiveGatewayActivityBehavior{
  22. protected static Logger log = LoggerFactory.getLogger(ExclusiveGatewayActivityBehaviorExt.class);
  23. //节点的设置管理器
  24. @Resource
  25. BpmNodeSetManager bpmNodeSetManager;
  26. //脚本引擎
  27. @Resource GroovyEngine groovyEngine;
  28. @Override
  29. protected void leave(ActivityExecution execution) {
  30. log.debug("enter ExclusiveGatewayActivityBehaviorExt=======================");
  31. if (log.isDebugEnabled()) {
  32. log.debug("Leaving activity '{}'", execution.getActivity().getId());
  33. }
  34. String solId=(String)execution.getVariable("solId");
  35. String nodeId=execution.getActivity().getId();
  36. log.debug("solid is {} and nodeId is {}",solId,nodeId);
  37. if(StringUtils.isNotEmpty(solId)&& StringUtils.isNotBlank(nodeId)){
  38. ExclusiveGatewayConfig configs=bpmNodeSetManager.getExclusiveGatewayConfig(solId, nodeId);
  39. for(NodeExecuteScript script:configs.getConditions()){
  40. String destNodeId=script.getNodeId();
  41. String condition=script.getCondition();
  42. log.debug("dest node:{}, condition is {}",destNodeId,condition);
  43. //执行脚本引擎
  44. Object boolVal=groovyEngine.executeScripts(condition, execution.getVariables());
  45. if(boolVal instanceof Boolean){
  46. Boolean returnVal=(Boolean)boolVal;//符合条件
  47. if(returnVal==true){
  48. //找到符合条件的目标节点并且进行跳转
  49. Iterator<PvmTransition> transitionIterator = execution.getActivity().getOutgoingTransitions().iterator();
  50. while (transitionIterator.hasNext()) {
  51. PvmTransition seqFlow = transitionIterator.next();
  52. if(destNodeId.equals(seqFlow.getDestination().getId())){
  53. execution.take(seqFlow);
  54. return;
  55. }
  56. }
  57. }
  58. }else{
  59. throw new RuntimeException("表达式:\n "+condition+"\n返回值不为布尔值(true or false)");
  60. }
  61. }
  62. }
  63. //执行父类的写法,以使其还是可以支持旧式的在跳出线上写条件的做法
  64. super.leave(execution);
  65. }
  66. }

我们通过继续改写了这个分支节点的跳出机制,并且通过脚本引擎来执行其条件分支的判断处理,但流程引擎并不了解我们扩展的类,这时我们需要配置Activiti流程引擎的行为动作工厂类,如下所示:

  1. package com.redxun.bpm.activiti.ext;
  2. import org.activiti.bpmn.model.ExclusiveGateway;
  3. import org.activiti.engine.impl.bpmn.behavior.ExclusiveGatewayActivityBehavior;
  4. import org.activiti.engine.impl.bpmn.parser.factory.DefaultActivityBehaviorFactory;
  5. /**
  6. * 扩展缺省的流程节点默认工厂类,实现对Activiti节点的执行的默认行为的更改
  7. * @author keitch
  8. *
  9. */
  10. public class ActivityBehaviorFactoryExt extends DefaultActivityBehaviorFactory {
  11. private ExclusiveGatewayActivityBehaviorExt exclusiveGatewayActivityBehaviorExt;
  12. /**
  13. * 通过Spring容器注入新的分支条件行为执行类
  14. * @param exclusiveGatewayActivityBehaviorExt
  15. */
  16. public void setExclusiveGatewayActivityBehaviorExt(ExclusiveGatewayActivityBehaviorExt exclusiveGatewayActivityBehaviorExt) {
  17. this.exclusiveGatewayActivityBehaviorExt = exclusiveGatewayActivityBehaviorExt;
  18. }
  19. //重写父类中的分支条件行为执行类
  20. @Override
  21. public ExclusiveGatewayActivityBehavior createExclusiveGatewayActivityBehavior(ExclusiveGateway exclusiveGateway) {
  22. return exclusiveGatewayActivityBehaviorExt;
  23. }
  24. }

三、Activiti的Spring配置的更改

在Activiti的流程引擎配置中加入新的流程行为动作执行工厂类。配置如下所示,注意activityBehaviorFactory的属性配置:

  1. <bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
  2. <property name="dataSource" ref="dataSource" />
  3. <property name="transactionManager" ref="transactionManager" />
  4. <property name="databaseSchemaUpdate" value="true" />
  5. <property name="jobExecutorActivate" value="false" />
  6. <property name="enableDatabaseEventLogging" value="false" />
  7. <property name="databaseType" value="${db.type}" />
  8. <property name="idGenerator" ref="actIdGenerator"/>
  9. <property name="eventListeners">
  10. <list>
  11. <ref bean="globalEventListener"/>
  12. </list>
  13. </property>
  14. <property name="activityFontName" value="宋体"/>
  15. <property name="labelFontName" value="宋体"/>
  16. <!-- 用于更改流程节点的执行行为 -->
  17. <property name="activityBehaviorFactory" ref="activityBehaviorFactoryExt"/>
  18. </bean>
  19. <bean id="activityBehaviorFactoryExt" class="com.redxun.bpm.activiti.ext.ActivityBehaviorFactoryExt">
  20. <property name="exclusiveGatewayActivityBehaviorExt" ref="exclusiveGatewayActivityBehaviorExt"/>
  21. </bean>
  22. <bean id="exclusiveGatewayActivityBehaviorExt" class="com.redxun.bpm.activiti.ext.ExclusiveGatewayActivityBehaviorExt"/>

通过以上方式扩展后,节点的分支设置可以把条件表达式写成以下方式,同时条件存于流程定义的外部表中:

如何实现Activiti的分支条件的自定义配置(转)的更多相关文章

  1. Activiti第一篇【介绍、配置开发环境、快速入门】

    Activiti介绍 什么是Activiti? Activiti5是由Alfresco软件在2010年5月17日发布的业务流程管理(BPM)框架,它是覆盖了业务流程管理.工作流.服务协作等领域的一个开 ...

  2. 让Spring Boot项目启动时可以根据自定义配置决定初始化哪些Bean

    让Spring Boot项目启动时可以根据自定义配置决定初始化哪些Bean 问题描述 实现思路 思路一 [不符合要求] 思路二[满足要求] 思路三[未试验] 问题描述 目前我工作环境下,后端主要的框架 ...

  3. git学习 分支特殊处理和配置03

    Bug分支: 当在一个分支上工作的时候:突然到其它分支修复bug,当前分支工作还没到要提交的程度:这时候可以使用git stash来将工作分支暂时存储起来: 用git stash list查看stas ...

  4. SpringBoot入门(五)——自定义配置

    本文来自网易云社区 大部分比萨店也提供某种形式的自动配置.你可以点荤比萨.素比萨.香辣意大利比萨,或者是自动配置比萨中的极品--至尊比萨.在下单时,你并没有指定具体的辅料,你所点的比萨种类决定了所用的 ...

  5. Spring MVC内容协商实现原理及自定义配置【享学Spring MVC】

    每篇一句 在绝对力量面前,一切技巧都是浮云 前言 上文 介绍了Http内容协商的一些概念,以及Spring MVC内置的4种协商方式使用介绍.本文主要针对Spring MVC内容协商方式:从步骤.原理 ...

  6. Java之SpringBoot自定义配置与整合Druid

    Java之SpringBoot自定义配置与整合Druid SpringBoot配置文件 优先级 前面SpringBoot基础有提到,关于SpringBoot配置文件可以是properties或者是ya ...

  7. 前台主页搭建、后台主页轮播图接口设计、跨域问题详解、前后端互通、后端自定义配置、git软件的初步介绍

    今日内容概要 前台主页 后台主页轮播图接口 跨域问题详解 前后端打通 后端自定义配置 git介绍和安装 内容详细 1.前台主页 Homeviwe.vue <template> <di ...

  8. ASP.NET 5 入门 (2) – 自定义配置

    ASP.NET 5 入门 (2) – 自定义配置 ASP.NET 5 理解和入门 建立和开发ASP.NET 5 项目 初步理解ASP.NET5的配置 正如我的第一篇文章ASP.NET 5 (vNext ...

  9. 基于Spring的可扩展Schema进行开发自定义配置标签支持

    一.背景 最近和朋友一起想开发一个类似alibaba dubbo的功能的工具,其中就用到了基于Spring的可扩展Schema进行开发自定义配置标签支持,通过上网查资料自己写了一个demo.今天在这里 ...

随机推荐

  1. python远程执行dos命令

    https://blog.csdn.net/huaihuaidexiao/article/details/5543240 https://blog.csdn.net/bcbobo21cn/articl ...

  2. java序列化和反序列化中的serialVersionUID有啥用

     1.什么是序列化和反序列化 序列化就是将java对象转成字节序列的过程:反序列化就是将字节序列转成java对象的过程. java中,序列化的目的一种是需要将对象保存到硬盘上,一种是对象需要在网络中传 ...

  3. swoole结合支持thinkphp 5.0版本

    安装swoole pecl install swoole 修改PHP配置文件php.ini加入 extension=swoole.so 有可能不需要人工去加,安装时自动加入进来了, 查看swoole扩 ...

  4. 搭建rancher节点

    1.centos 7.5 64 2.安装docker systemctl restart docker.service 注意:重启后才有 /etc/docker/文件夹 切换到这文件夹下再增加对应的d ...

  5. Caused by: java.lang.IllegalArgumentException: argument type mismatch

    下面是我的报错信息 at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java: ...

  6. 6993: Dominoes(纯bfs)

    题目描述Orz likes to play dominoes. Now giving an n*m chessboard and k dominoes whose size are 1*2, Orz ...

  7. js前台计算两个日期的间隔时间

    js前台计算两个日期的间隔时间(时间差)原创 2017年08月28日 16:09:43 标签:javascript 1144在后台传来两个时间字段,从中解析出两个字符串类型的日期格式 需要在前台解析出 ...

  8. Windows 7升级1月更新汇总后导致SMBv2网络无法正常工作

    在本月的补丁星期二活动日中,微软面向Windows 7.Windows 2008 R2服务器系统推出了KB4480970的月度更新汇总.然而根据部分用户反馈,在安装该更新之后导致系统的网络设置无法正常 ...

  9. promise.then, setTimeout,await执行顺序问题

    promise.then VS setTimeout 在chrome和node环境环境中均输出2, 3, 1, 先输出2没什么好说的,3和1顺序让人有些意外 原因: 有一个事件循环,但是任务队列可以有 ...

  10. SQL Server中与IO相关的等待类型:IO_COMPLETION和PAGEIOLATCH_*

    一个大的SQL语句操作,执行计划中包含了一个merge join操作,观察到SQL长时间处于IO_COMPLETION等待状态,如果是读取相关的表的数据,服务器应该全力为其服务,但是服务器的物理IO又 ...