如何实现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!=null){ //取得分支条件列表 JsonNode configs=configsNode.get("conditions"); if(configs!=null){ 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==null) continue; Element conditionExpress=(Element)seqFlow.selectSingleNode("bpm:conditionExpression"); if(conditionExpress==null){ 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"/>
通过以上方式扩展后,节点的分支设置可以把条件表达式写成以下方式,同时条件存于流程定义的外部表中:
推荐阅读
-
详解如何在低版本的Spring中快速实现类似自动配置的功能
-
Intellij IDEA如何自定义注释模板的实现方法
-
vue 使用element-ui中的Notification自定义按钮并实现关闭功能以及如何处理多个通知
-
vue 使用element-ui中的Notification自定义按钮并实现关闭功能及如何处理多个通知
-
printf是如何实现可变参数的?自定义函数能否实现参数可变?【C语言】
-
Springboot如何获取配置文件application.yml中自定义的变量并使用
-
Vue 如何使用props、emit实现自定义双向绑定的实现
-
ThinkPHP6.0如何利用自定义验证规则规范的实现登陆
-
mybatis-plus QueryWrapper自定义查询条件的实现
-
Android如何自定义View实现横向的双水波纹进度条