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

转载 2018年04月17日 13:15:47

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

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

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

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

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

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

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

Q: 如何做到优雅?

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

  1. public class CreateAndTakeTransitionCmd implements Command<java.lang.Void>  
  2. {  
  3.     private ActivityImpl _activity;  
  4.   
  5.     private String _executionId;  
  6.   
  7.     public CreateAndTakeTransitionCmd(String executionId, ActivityImpl activity)  
  8.     {  
  9.         _executionId = executionId;  
  10.         _activity = activity;  
  11.     }  
  12.   
  13.     @Override  
  14.     public Void execute(CommandContext commandContext)  
  15.     {  
  16.         Logger.getLogger(TaskFlowControlService.class)  
  17.                 .debug(String.format(”executing activity: %s”, _activity.getId()));  
  18.   
  19.         ExecutionEntity execution = commandContext.getExecutionEntityManager().findExecutionById(_executionId);  
  20.         execution.setActivity(_activity);  
  21.         execution.performOperation(AtomicOperation.TRANSITION_CREATE_SCOPE);  
  22.   
  23.         return null;  
  24.     }  
  25. }  
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),我们往往可以以某个活动对象为模板来克隆一个新的活动,克隆的方法是分别拷贝各个字段的值:

  1. protected ActivityImpl cloneActivity(ProcessDefinitionEntity processDefinition, ActivityImpl prototypeActivity,  
  2.         String newActivityId, String… fieldNames)  
  3. {  
  4.     ActivityImpl clone = processDefinition.createActivity(newActivityId);  
  5.     CloneUtils.copyFields(prototypeActivity, clone, fieldNames);  
  6.   
  7.     return clone;  
  8. }  
 protected ActivityImpl cloneActivity(ProcessDefinitionEntity processDefinition, ActivityImpl prototypeActivity,
            String newActivityId, String... fieldNames)
    {
        ActivityImpl clone = processDefinition.createActivity(newActivityId);
        CloneUtils.copyFields(prototypeActivity, clone, fieldNames);

        return clone;
    }
拷贝字段的代码如下:

  1. import org.apache.commons.lang.reflect.FieldUtils;  
  2. import org.apache.log4j.Logger;  
  3. import org.junit.Assert;  
  4.   
  5. public abstract class CloneUtils  
  6. {  
  7.     public static void copyFields(Object source, Object target, String… fieldNames)  
  8.     {  
  9.         Assert.assertNotNull(source);  
  10.         Assert.assertNotNull(target);  
  11.         Assert.assertSame(source.getClass(), target.getClass());  
  12.   
  13.         for (String fieldName : fieldNames)  
  14.         {  
  15.             try  
  16.             {  
  17.                 Field field = FieldUtils.getField(source.getClass(), fieldName, true);  
  18.                 field.setAccessible(true);  
  19.                 field.set(target, field.get(source));  
  20.             }  
  21.             catch (Exception e)  
  22.             {  
  23.                 Logger.getLogger(CloneUtils.class).warn(e.getMessage());  
  24.             }  
  25.         }  
  26.     }  
  27. }  
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());
            }
        }
    }
}

一个示例的用法是:
  1. ActivityImpl clone = cloneActivity(processDefinition, prototypeActivity, cloneActivityId, “executionListeners”,  
  2.     ”properties”);  
      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个用户任务活动的例子:


  1. public class ChainedActivitiesCreator extends RuntimeActivityCreatorSupport implements RuntimeActivityCreator  
  2. {  
  3.     @Override  
  4.     public ActivityImpl[] createActivities(ProcessEngine processEngine, ProcessDefinitionEntity processDefinition,  
  5.             RuntimeActivityDefinition info)  
  6.     {  
  7.         info.setFactoryName(ChainedActivitiesCreator.class.getName());  
  8.   
  9.         if (info.getCloneActivityIds() == null)  
  10.         {  
  11.             info.setCloneActivityIds(CollectionUtils.arrayToList(new String[info.getAssignees().size()]));  
  12.         }  
  13.   
  14.         return createActivities(processEngine, processDefinition, info.getProcessInstanceId(),  
  15.             info.getPrototypeActivityId(), info.getNextActivityId(), info.getAssignees(), info.getCloneActivityIds());  
  16.     }  
  17.   
  18.     private ActivityImpl[] createActivities(ProcessEngine processEngine, ProcessDefinitionEntity processDefinition,  
  19.             String processInstanceId, String prototypeActivityId, String nextActivityId, List<String> assignees,  
  20.             List<String> activityIds)  
  21.     {  
  22.         ActivityImpl prototypeActivity = ProcessDefinitionUtils.getActivity(processEngine, processDefinition.getId(),  
  23.             prototypeActivityId);  
  24.   
  25.         List<ActivityImpl> activities = new ArrayList<ActivityImpl>();  
  26.         for (int i = 0; i < assignees.size(); i++)  
  27.         {  
  28.             if (activityIds.get(i) == null)  
  29.             {  
  30.                 String activityId = createUniqueActivityId(processInstanceId, prototypeActivityId);  
  31.                 activityIds.set(i, activityId);  
  32.             }  
  33.   
  34.             ActivityImpl clone = createActivity(processEngine, processDefinition, prototypeActivity,  
  35.                 activityIds.get(i), assignees.get(i));  
  36.             activities.add(clone);  
  37.         }  
  38.   
  39.         ActivityImpl nextActivity = ProcessDefinitionUtils.getActivity(processEngine, processDefinition.getId(),  
  40.             nextActivityId);  
  41.         createActivityChain(activities, nextActivity);  
  42.   
  43.         return activities.toArray(new ActivityImpl[0]);  
  44.     }  
  45. }  
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:

  1. public class RuntimeActivityDefinition  
  2. {  
  3.     String _factoryName;  
  4.   
  5.     String _processDefinitionId;  
  6.   
  7.     String _processInstanceId;  
  8.   
  9.     Map<String, Object> _properties = new HashMap<String, Object>();  
  10.   
  11.     String _propertiesText;  
  12.   
  13.     public void deserializeProperties() throws IOException  
  14.     {  
  15.         ObjectMapper objectMapper = new ObjectMapper();  
  16.         _properties = objectMapper.readValue(_propertiesText, Map.class);  
  17.     }  
  18.   
  19.     public List<String> getAssignees()  
  20.     {  
  21.         return getProperty(“assignees”);  
  22.     }  
  23.   
  24.     public String getCloneActivityId()  
  25.     {  
  26.         return getProperty(“cloneActivityId”);  
  27.     }  
  28.     //…  
  29. }  
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");
    }
    //...
}

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

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



                </div>

工作流后加签实现

1、流程重新定义 2、后加签环节及后加签属性设置 3、原始流程操作及后加签环节操作 4、流程场景遍历 5、流程图元体现...
  • simplemurrina
  • simplemurrina
  • 2017-10-25 21:54:43
  • 307

Activiti 自由跳转两种实现方式-自我总结

package com.zdc.test; import java.util.List; import org.activiti.engine.HistoryService; import org...
  • qq_18735553
  • qq_18735553
  • 2017-01-20 16:43:16
  • 2536

activiti动态创建流程图

27 May 2013 Comments 利用100行代码动态创建并部署流程 英文原文:Dynamic Process Creation and Deployment in 100 Lin...
  • qq279071978
  • qq279071978
  • 2017-07-04 10:50:41
  • 532

工作流Activiti的学习总结- 整合比较复杂的一个流程

这会部长可以驳回员工的请假申请,用户要调整,主任也可以驳回员工的申请 加入判断的变量 刚开始也是模拟提交一张。 @Test public void TestAddLeave...
  • lxyg06
  • lxyg06
  • 2014-02-11 11:52:02
  • 2603

activiti 多实例任务

activiti 多实例任务,activiti 会签实现,activiti加签实现,activiti任务节点,分享牛系列作品,我们在使用activiti 工作流引擎的时候,最常用的肯定是任务节点,因为...
  • qq_30739519
  • qq_30739519
  • 2016-04-25 09:15:18
  • 33150

BPM系列:加签减签论

BPM开发平台WEB跑流程时,‘更多操作’中多了加签配置,当节点的权限中点了允许加签,则流程走到这上步骤时,更多操作中的加签是可用的,加签的具体用法如下: 1)加签完成到下一步:如到步骤‘处理1’时...
  • moon66sun
  • moon66sun
  • 2013-03-15 11:04:19
  • 882

【BPM开发平台】关于流程加签功能说明_方正快速开发平台

BPM开发平台WEB跑流程时,‘更多操作’中多了加签配置,当节点的权限中点了允许加签,则流程走到这上步骤时,更多操作中的加签是可用的,加签的具体用法如下:1)加签完成到下一步:如到步骤‘处理1’时加签...
  • dongdongdang
  • dongdongdang
  • 2011-03-30 12:06:00
  • 594

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

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

动态工作流的设计

最近在做动态工作流的东西,感觉有不少心得体会,拿出来和大家交流一下。我们做动态工作流,主要是为了支持OA的加签、会签和动态的跳转。1概述(1)会签对应着动态的增加参与者: 如上图中,活动A本来有参与者...
  • JOHNCOOLS
  • JOHNCOOLS
  • 2006-03-28 12:05:00
  • 4105
收藏助手
不良信息举报
您举报文章:优雅的实现Activiti动态调整流程(自由跳转、前进、后退、分裂、前加签、后加签等),含范例代码!
举报原因:
原因补充:

(最多只允许输入30个字)