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

最近对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"}

Q: 说好的代码呢?

A: 参见我在github上的开源项目OpenWebFlowTaskFlowControlService.java是一个主要的类,它会调用一些creator和persistor来实现对象创建和持久化。该代码已做过测试,测试用例参见AbstractProcessEngineToolTest。测试用例的方法说明如下:

  • testInsertTasksBefore:测试前加签
  • testInsertTasksAfter:测试后加签
  • testInsertTasksWithPersistence:测试加签功能的持久化
  • testSplit:测试节点分裂
  • testMultiInstancesLoop:测试多实例节点(可用来实现串行、并行会签)
  • testModelDeployment:测试流程模型部署
  • testMove:测试自由跳转
  • testAcl:测试流程动态授权
  • testDelegation:测试代办功能
  • testAlarm:测试催办功能

AbstractProcessEngineToolTest有两个派生类,分别是MemProcessEngineToolTest和SqlProcessEngineToolTest,它们分别测试基于内存和持久化的方案。持久化需要用到SQL数据库,脚本见src\test\java\openwebflow.sql。Activiti本身需要的SQL脚本为src\test\java\activitidb.sql。

测试用例采用了如下的一个流程配合测试:

时间: 2024-10-25 20:46:24

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

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

最近在搞openwebflow的工作流节点自由跳转功能,在网上看了一些资料,感觉不是很好,总结原因如下: 直接手动调用SqlSession的操作,感觉会漏掉一些重要的初始化操作(如:启动新节点之后加载其用户授权策略,等): 只有往前(往已执行过的节点)跳转的功能,没有往后节点(往还没有执行的节点)跳转的功能: 新任务不是追加到已有执行路径上,而是覆盖老任务: 那么就自己动手吧!操作流程其实也简单,大概如下: 按照目标节点(activity)定义创建一个新的任务(task),这个创建过程必须和正常

Qt 实现动态调整流程指令顺序(通过鼠标事件实现)

// 事件 bool CTestCfgWidget::eventFilter(QObject *, QEvent *evt) { //获取鼠标坐标和窗口坐标 static QPoint lastPnt; static bool isHover = false; static bool ismove = false; // 鼠标按下 if (evt->type() == QEvent::MouseButtonPress) { QMouseEvent* e = static_cast<QMouse

Activiti动态设置办理人扩展

作者:邓家海 扩展是要求对Activiti基础有一定的功底的 我们一直在努力,不是为了改变世界,只是不让世界去改变我们. 关键词:Assignee.Candidate users.Candidate groups:setAssignee.taskCandidateUser.taskCandidateGroup 主要解决问题:Activiti动态给任务节点设置办理人. 情景: 我们在做工作流开发,学习的时候一般都有这么一个过程: 第一阶段:最开始学习的时候,喜欢在设计流程的时候写死人名(即)办理人

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

注:环境配置:activiti自己定义流程之自己定义表单(一):环境配置 在上一节自己定义表单环境搭建好以后,我就正式開始尝试自己创建表单,在后台的处理就比較常规,主要是针对ueditor插件的功能在前端进行改动. 因为自己的前端相关技术太渣.因此好多东西都不会用,导致改动实现的过程也是破费了一番功夫.头皮发麻了好几天. 既然是用别人的插件进行改动,那么我想假设仅仅是单独的贴出我改动后的代码,可能没有前后进行对照好理解,因此这里就把原代码和改动后的同一时候对照着贴出,以便于朋友们能从对照中更快的

c++设计成员变量可动态调整的动态类结构

本文主要介绍一下如何使用c++设计成员变量可动态调整的抽象动态类结构.首先介绍一下项目中以前使用的一种类结构:静态类结构 1.静态类结构 很多时候,在项目开发中设计类结构时,我们往往有一种简单.直接的惯性思维:原始数据是什么样子,设计中类成员就包含相应的成员变量,这样的类我把它称之为静态类结构.静态类结构有两个特点. 1.1特点一:类结构抽象能力不足. 静态类结构强依赖于原始数据,是针对具体编程而不是针对抽象编程.一旦原始资源新增或者删除字段,类结构就要相应的调整类成员变量,费事费力,而且是重复

写个js动态调整图片宽高 (原创)

<body style="TEXT-ALIGN: center;"> <div id="testID" style="background:red;MARGIN-RIGHT: auto; MARGIN-LEFT: auto; width:173;height:184"> <img src="http://e.hiphotos.baidu.com/image/pic/item/024f78f0f736afc3

Log4cpp配置文件及动态调整日志级别的方法

一.log4cpp概述 Log4cpp是一个开源的C++类库,它提供了C++程序中使用日志和跟踪调试的功能,它的优点如下: 提供应用程序运行上下文,方便跟踪调试: 可扩展的.多种方式记录日志,包括命令行.文件.回卷文件.内存.syslog服务器.Win事件日志等: 可以动态控制日志记录级别,在效率和功能中进行调整: 所有配置可以通过配置文件进行动态调整: 多语言支持,包括Java(log4j),C++(log4cpp.log4cplus),C(log4c),python(log4p)等: 二.原

[转] iOS TableViewCell 动态调整高度

原文: http://blog.csdn.net/crayondeng/article/details/8899577 最近遇到了一个cell高度变化的问题,在找解决办法的时候,参考了这篇文章,觉得不错 在写sina 微博的显示微博内容时,用到cell进行显示,那么就要考虑到不同微博内容导致的cell高度问题.在微博显示的内容中包括了文字和图片,那么就要计算文字部分的高度和图片部分的高度.这篇博文就记录一下如何处理cell高度的动态调整问题吧! 一.传统的方法 在 tableview的deleg

下压栈(LIFO) (能动态调整数组大小的实现)

import java.util.*; // 下压栈(LIFO) 能动态调整数组大小的实现 public class ResizeArrayStack<Item> implements Iterable<Item> { @SuppressWarnings("unchecked") private Item[] a=(Item[]) new Object[1]; private int N; public boolean isEmpty() { return N=