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