優雅的實現Activiti動態調整流程(自由跳轉、前進、後退、分裂、前加簽、後加籤等),含範例代碼!

最近對Activiti作了一些深刻的研究,對Activiti的流程機制有了些理解,對動態調整流程也有了一些實踐方法。java

如今好好總結一下,一來是對這段時間本身辛苦探索的一個記錄,二來也是爲後來者指指路~~~數據庫

以下內容準備採用QA的方式寫,不少問題都是當初本身極疑惑的問題,但願能爲你們解惑!apache

Q:能夠動態調整流程嗎?

A:能夠!能夠動態更改流程指向,或者建立新的節點,等等。。。數組

Q: 更改流程還須要注意什麼?

A: 必需要實現持久化!不然一旦應用重啓,你的流程就犯糊塗了!譬如,你建立了一個新節點,但因爲沒有持久化,重啓以後流程引擎找不到那個新節點了。。。app

Q: 如何作到優雅?

A: 除了持久化以外,還記住儘可能不要由於臨時調整直接更改現有活動(沒準這個活動後面還要照常使用呢!),這種狀況能夠考慮克隆。第三,不要直接操做數據庫,或者SqlSession,記住本身寫Command!參見我前面的另一篇文章。以下代碼示出執行某個activity後續流程的Cmd:ide

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中註冊這些活動。spa


但還有一種更好的辦法,即只持久化「活動工廠」的信息。譬如,咱們根據step2活動建立一個step21活動,全部的信息都同樣,這個時候只要持久化工廠類型(活動克隆)、模板活動ID(step2)、新活動ID(step21),這種方法是極其節省空間的,並且簡化了代碼。比較複雜的例子,是將某個活動分裂成N個串行的會籤活動,這種狀況只須要記錄模板活動ID、新活動ID數組就能夠了,不須要記錄更多的信息。以下示出一個建立N個用戶任務活動的例子:.net


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:prototype

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"}
相關文章
相關標籤/搜索