一、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. Java序列化相关

    java类实现serializable有什么好处或意义 一个对象序列化的接口,一个类只有实现了Serializable接口,它的对象才是可序列化的.因此如果要序列化某些类的对象,这些类就必须实现Ser ...

  2. Synchronized总结

    一.synchronized加锁原理 synchronized可以保证方法或者代码块在运行时,同一时刻只有一个线程可以进入到临界区,同时它还可以保证共享变量的内存可见性. Java中每一个对象都可以作 ...

  3. redis+thinkphp5的注册、登陆、关注基础例子

    最近初步接触redis,结合thinkphp5与redis,写了一个用户注册的基础例子,用于学习. 这个例子是结合了兄弟连的redis视频,最后两节的内容写的:https://study.163.co ...

  4. GhostScript应用一例:使用GhostScript强行修改加密PDF

    GhostScript官方网站为:http://www.ghostscript.com/ 作为一个英文开源软件,发现国内用的人很少.尤其是在Windows环境下,Acrobat/Adobe/Foxit ...

  5. 使用百度ocr接口识别验证码

    #!/usr/bin/env python #created by Baird from aip import AipOcr def GetCaptchaV(filename): APP_ID = ' ...

  6. Mac 下安装nvm 后vscode 输入node -v 不起作用

    今天下午,我因为要安装不同的node版本,所有安装了nvm下载了两个不同版本的node,并且配置了环境变量. 在命令行窗口中使用起来没有任何问题,但是在vs code中敲的时候node -v 显示no ...

  7. 关于导入zepto出错的问题

    一.前言 webpack在配置多页面开发的时候 ,发现用 import 导入 Zepto 时,会报 Uncaught TypeError: Cannot read property 'createEl ...

  8. 小乌龟 coding 克隆、提交一直提示无权限

    因为之前设置过账号,但是网上各种命令行清除都没有用,进入小乌龟设置删除全局配置,系统配置,保存就可以克隆等操作了

  9. 富文本编辑器 CKeditor 配置使用 (带附件)

    Ckeditor下载地址:http://ckeditor.com/download 1.CKeditor的基本配置 var textval=CKEDITOR.instances.TextArea1.g ...

  10. JS 通过 navigator获取判断浏览器信息

    获取浏览器信息需要使用navigator.userAgent 对象 根据获取到的内容判断浏览器信息 亲身测试 navigator.userAgent IE 11  Mozilla/5.0 (Windo ...