最近对Activiti做了一些深入的研究,对Activiti的流程机制有了些理解,对动态调整流程也有了一些实践方法。

现在好好总结一下,一来是对这段时间自己辛苦探索的一个记录,二来也是为后来者指指路~~~

如下内容准备采用QA的方式写,很多问题都是当初自己极疑惑的问题,希望能为大家解惑!

Q:可以动态调整流程吗?

A:可以!可以动态更改流程指向,或者创建新的节点,等等。。。

Q: 更改流程还需要注意什么?

A: 必须要实现持久化!否则一旦应用重启,你的流程就犯糊涂了!譬如,你创建了一个新节点,但由于没有持久化,重启之后流程引擎找不到那个新节点了。。。

Q: 如何做到优雅?

A: 除了持久化之外,还记住尽量不要因为临时调整直接更改现有活动(没准这个活动后面还要照常使用呢!),这种情况可以考虑克隆。第三,不要直接操作数据库,或者SqlSession,记住自己写Command!参见我前面的另外一篇文章。如下代码示出执行某个activity后续流程的Cmd:

public class CreateAndTakeTransitionCmd implements Command<java.lang.Void>
{
private ActivityImpl _activity; private String _executionId; public CreateAndTakeTransitionCmd(String executionId, ActivityImpl activity)
{
_executionId = executionId;
_activity = activity;
} @Override
public Void execute(CommandContext commandContext)
{
Logger.getLogger(TaskFlowControlService.class)
.debug(String.format("executing activity: %s", _activity.getId())); ExecutionEntity execution = commandContext.getExecutionEntityManager().findExecutionById(_executionId);
execution.setActivity(_activity);
execution.performOperation(AtomicOperation.TRANSITION_CREATE_SCOPE); return null;
}
}

Q: 如何新建一个活动?

A: 新建活动可以调用processDefinition.createActivity(newActivityId),我们往往可以以某个活动对象为模板来克隆一个新的活动,克隆的方法是分别拷贝各个字段的值:

	protected ActivityImpl cloneActivity(ProcessDefinitionEntity processDefinition, ActivityImpl prototypeActivity,
String newActivityId, String... fieldNames)
{
ActivityImpl clone = processDefinition.createActivity(newActivityId);
CloneUtils.copyFields(prototypeActivity, clone, fieldNames); return clone;
}

拷贝字段的代码如下:

import org.apache.commons.lang.reflect.FieldUtils;
import org.apache.log4j.Logger;
import org.junit.Assert; public abstract class CloneUtils
{
public static void copyFields(Object source, Object target, String... fieldNames)
{
Assert.assertNotNull(source);
Assert.assertNotNull(target);
Assert.assertSame(source.getClass(), target.getClass()); for (String fieldName : fieldNames)
{
try
{
Field field = FieldUtils.getField(source.getClass(), fieldName, true);
field.setAccessible(true);
field.set(target, field.get(source));
}
catch (Exception e)
{
Logger.getLogger(CloneUtils.class).warn(e.getMessage());
}
}
}
}

一个示例的用法是:

		ActivityImpl clone = cloneActivity(processDefinition, prototypeActivity, cloneActivityId, "executionListeners",
"properties");

这个语句的意思是克隆prototypeActivity对象的executionListeners和properties字段。

Q: 如何实现新建活动的持久化?

A: 一个办法是将新建活动的类型、活动ID(activityId)、incomingTransitions、outgoingTransitions等信息保存起来,然后在ProcessEngine启动的时候,在ProcessDefinition中注册这些活动。

但还有一种更好的办法,即只持久化“活动工厂”的信息。譬如,我们根据step2活动创建一个step21活动,所有的信息都一样,这个时候只要持久化工厂类型(活动克隆)、模板活动ID(step2)、新活动ID(step21),这种方法是极其节省空间的,而且简化了代码。比较复杂的例子,是将某个活动分裂成N个串行的会签活动,这种情况只需要记录模板活动ID、新活动ID数组就可以了,不需要记录更多的信息。如下示出一个创建N个用户任务活动的例子:

public class ChainedActivitiesCreator extends RuntimeActivityCreatorSupport implements RuntimeActivityCreator
{
@Override
public ActivityImpl[] createActivities(ProcessEngine processEngine, ProcessDefinitionEntity processDefinition,
RuntimeActivityDefinition info)
{
info.setFactoryName(ChainedActivitiesCreator.class.getName()); if (info.getCloneActivityIds() == null)
{
info.setCloneActivityIds(CollectionUtils.arrayToList(new String[info.getAssignees().size()]));
} return createActivities(processEngine, processDefinition, info.getProcessInstanceId(),
info.getPrototypeActivityId(), info.getNextActivityId(), info.getAssignees(), info.getCloneActivityIds());
} private ActivityImpl[] createActivities(ProcessEngine processEngine, ProcessDefinitionEntity processDefinition,
String processInstanceId, String prototypeActivityId, String nextActivityId, List<String> assignees,
List<String> activityIds)
{
ActivityImpl prototypeActivity = ProcessDefinitionUtils.getActivity(processEngine, processDefinition.getId(),
prototypeActivityId); List<ActivityImpl> activities = new ArrayList<ActivityImpl>();
for (int i = 0; i < assignees.size(); i++)
{
if (activityIds.get(i) == null)
{
String activityId = createUniqueActivityId(processInstanceId, prototypeActivityId);
activityIds.set(i, activityId);
} ActivityImpl clone = createActivity(processEngine, processDefinition, prototypeActivity,
activityIds.get(i), assignees.get(i));
activities.add(clone);
} ActivityImpl nextActivity = ProcessDefinitionUtils.getActivity(processEngine, processDefinition.getId(),
nextActivityId);
createActivityChain(activities, nextActivity); return activities.toArray(new ActivityImpl[0]);
}
}

这里,RuntimeActivityDefinition代表一个工厂信息,为了方便,不同工厂的个性化信息存成了一个JSON字符串,并会在加载的时候解析成一个Map:

public class RuntimeActivityDefinition
{
String _factoryName; String _processDefinitionId; String _processInstanceId; Map<String, Object> _properties = new HashMap<String, Object>(); String _propertiesText; public void deserializeProperties() throws IOException
{
ObjectMapper objectMapper = new ObjectMapper();
_properties = objectMapper.readValue(_propertiesText, Map.class);
} public List<String> getAssignees()
{
return getProperty("assignees");
} public String getCloneActivityId()
{
return getProperty("cloneActivityId");
}
//...
}

一个节点分裂的工厂属性:

{"sequential":true,"assignees":["bluejoe","alex"],"cloneActivityId":"2520001:step2:1419823449424-8","prototypeActivityId":"step2"}

优雅的实现Activiti动态调整流程(自由跳转、前进、后退、分裂、前加签、后加签等),含范例代码!的更多相关文章

  1. 也谈一下Activiti工作流节点的自由跳转

    最近在搞openwebflow的工作流节点自由跳转功能,在网上看了一些资料,感觉不是很好,总结原因如下: 直接手动调用SqlSession的操作,感觉会漏掉一些重要的初始化操作(如:启动新节点之后加载 ...

  2. activiti 动态自定义流程(包含会签流程)

    后台加入工作流步骤(这个不重要,自己实现) package com.blk.integrated.pojo; import java.io.Serializable; import java.util ...

  3. Activiti实现流程自由跳转

    import org.activiti.engine.ProcessEngine; import org.activiti.engine.TaskService; import org.activit ...

  4. Activiti动态设置办理人扩展

    关键词:Assignee.Candidate users.Candidate groups:setAssignee.taskCandidateUser.taskCandidateGroup 主要解决问 ...

  5. 在 Web 级集群中动态调整 Pod 资源限制

    作者阿里云容器平台技术专家 王程阿里云容器平台技术专家 张晓宇(衷源) ## 引子 不知道大家有没有过这样的经历,当我们拥有了一套 Kubernetes 集群,然后开始部署应用的时候,我们应该给容器分 ...

  6. activiti 动态配置 activiti 监听引擎启动和初始化(高级源码篇)

    1.1.1. 前言 用户故事:现在有这样一个需求,第一个需求:公司的开发环境,测试环境以及线上环境,我们使用的数据库是不一样的,我们必须能够任意的切换数据库进行测试和发布,对数据库连接字符串我们需要加 ...

  7. activiti自己定义流程之自己定义表单(二):创建表单

    注:环境配置:activiti自己定义流程之自己定义表单(一):环境配置 在上一节自己定义表单环境搭建好以后,我就正式開始尝试自己创建表单,在后台的处理就比較常规,主要是针对ueditor插件的功能在 ...

  8. 动态线程池(DynamicTp)之动态调整Tomcat、Jetty、Undertow线程池参数篇

    大家好,这篇文章我们来介绍下动态线程池框架(DynamicTp)的adapter模块,上篇文章也大概介绍过了,该模块主要是用来适配一些第三方组件的线程池管理,让第三方组件内置的线程池也能享受到动态参数 ...

  9. 如何实现可动态调整隐藏header的listview

    (转自:http://blog.sina.com.cn/s/blog_70b9730f01014sgm.html) 需求:根据某种需要,可能需要动态调整listview的页眉页脚,譬如将header作 ...

随机推荐

  1. 题解西电OJ (Problem 1003 -最喜欢的数字)--动态规划

    Description zyf最喜欢的数字是1!所以他经常会使用一些手段,把一些非1的数字变 成1,并为此得意不已.他会且仅会的两种手段是: 1.把某个数m除以某个质数p——当然p必须能整除这个数,即 ...

  2. FTP被动模式连接及超时问题解决

    问题: 1.FTPClient.listFiles()或者FTPClient.retrieveFile()方法时,就停止在那里,什么反应都没有,出现假死状态. 2.连接FTP服务器,长时间进行数据操作 ...

  3. Java流操作之转换流

    流的操作规律: 1.明确流和目的. 数据源(源头):就是需要读取,可以使用两个体系:InputStream.Reader 数据汇(目的地):就是需要写入,可以使用两个体系:OutputStream.W ...

  4. 基于redis的IP地址快速查询

    在一些大数据处理中,我们需要用到IP地址查询,一般为了查询一个IP属于哪个地址,我们通常需要根据一个IP数据库来查询,网络上比较常用的IP库是纯真IP数据库.IP数据库里面的记录一般存储方式为IP的开 ...

  5. nyoj 79 拦截导弹

    拦截导弹 时间限制:3000 ms  |  内存限制:65535 KB 难度:3   描述 某国为了防御敌国的导弹袭击,发展中一种导弹拦截系统.但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到 ...

  6. JBOss 端口没占用!

    打开exlipse ,启动服务器 后,报如下错误:

  7. 多路径配置vlome group共享存储,VG的更新。

    1.  PV的概念: a)        一块物理磁盘一块物理硬盘在被LVM管理时被称为“物理卷”. b)        在LVM能对其进行管理之前需要在硬盘上产生一些特殊的数据结构,这个过程就是建立 ...

  8. .NET世界各成员之间的关系

    相信看到这篇文章的人,心中肯定有这样的想法:ODBC.OLEDB.ADO.ADO.NET貌似都是访问数据库的东东,那么他们之间有什么区别,又有什么联系呢?不要着急,待我慢慢道来. 先说ODBC,官方的 ...

  9. android飞机游戏敌机移动路径

    基础android的飞机类游戏,与前人一样,由surfaceView绘制游戏画面,另起线程控制绘制时间间隔达到动态效果.这里附上最近自己写的敌机自动飞行路径代码.请大家给点意见. 在敌机管理模块,加入 ...

  10. Asp.Net转换Html加号显示为空格的字符!(自已备用)

    Asp.Net转换Html显示为空格的字符!(自已备用) 显示+(加号),须要替换一下!