Activiti6.0工作流学习笔记之BPMN2.0—流程任务

  流程任务是工作流中非常重要的元素,流程任务包括五种,每种元素都有特定的属性和功能。
  (1)服务任务(ServiceTask):用户调用外部服务和自动执行程序。
  (2)发送任务(SendTask):向外部流程参与者发送消息。
  (3)接收任务(ReceiveTask):等待外部流程参与者发送消息。
  (4)用户任务(UserTask):最重要的流程元素,表示是流程参与者要亲自参与任务。
  (5)脚本任务(ScriptTask):自动执行脚本的任务,当流程走到该节点时会自动执行指定的脚本。

  1. 用户任务
流程执行图

  当流程运行到该节点时,会被阻塞,直到用户对该任务进行处理并进行提交。我们可以给用户任务指定任务处理的候选人和候选组、执行的时间等相关信息。
  流程定义文件如下所示:

       <userTask id="someTask" activiti:candidateUsers="user1, user2" activiti:assignee="user1" 
            activiti:dueDate="${dataVariable}">
......

  除了在xml方式配置相关属性,我们还可以通过监听器配置用户任务的相关属性,节点的事件类型包括四种 create、delete、complete、assigment,除此之外,我们还可以通过流程定义文件向监听器的指定字段进行赋值。
  创建流程定义文件。

    <process id="my-process">
        <startEvent id="start" />
        <sequenceFlow id="flow1" sourceRef="start" targetRef="someTask"/>
        <userTask id="someTask" name="someTask">
            <extensionElements>
                <activiti:taskListener event="create"  class="activiti.MyCustomListner">
                    <activiti:field name="name" stringValue="测试属性"></activiti:field>
                </activiti:taskListener>
            </extensionElements>
        </userTask>
        <sequenceFlow id="flow2" sourceRef="someTask" targetRef="end" />
        <endEvent id="end" />
    </process>

  编写任务监听器监听用户任务节点的创建。

public class MyCustomListner implements TaskListener {
    private static final org.slf4j.Logger logger = LoggerFactory.getLogger(MyCustomListner.class);
    private FixedValue name;
    @Override
    public void notify(DelegateTask delegateTask) {
        delegateTask.addCandidateUsers(Arrays.asList("user1", "user2"));
        delegateTask.addCandidateGroup("g1");
        logger.info("流程定义文件中的name = {}", name.getValue(delegateTask));
    }
}

  编写测试类,查看user1用户的待办任务,看监听器的配置是否生效。

    @Rule
    public  ActivitiRule activitiRule = new ActivitiRule();

    @Test
    @Deployment(resources = "my-process.bpmn20.xml")
    public void test() throws InterruptedException {
        activitiRule.getRuntimeService().startProcessInstanceByKey("my-process");
        List<Task> tasks = activitiRule.getTaskService().createTaskQuery()
                .taskCandidateUser("user1").listPage(0, 100);
        for (Task task : tasks) {
            logger.info("task = {}", task.getName());
        }
    }
  1. 脚本任务
执行流程

  activiti默认支持的脚本类型包括三种:JUEL脚本(默认,也就是el表达式)、Groovy脚本(需要加入对应的依赖)、JavaScript脚本。

        <scriptTask id="st" name="脚本任务" activiti:resultVariable="myVar" scriptFormat="groovy">
            <script><![CDATA[]]></script>
        </scriptTask>

  在编写脚本的过程中,activiti提供了一些内置变量,我们可以直接使用这些内置变量,包括executionxxxServiceprocessEngineConfigutaion
  由于流程定义文件中不会有提示,所以为了节省时间可以现将脚本放到自己的环境中运行,没有异常在复制到文件中。我们也可以使用脚本引擎来帮助我们测试编写的脚本是否返回预期的结果。

    @Test
    public void testScript() throws ScriptException {
        ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
        ScriptEngine juel = scriptEngineManager.getEngineByName("juel");
        System.out.println(juel.eval("${2+2}"));
    }
  1. 服务任务
执行流程

  服务任务执行java程序的方式包括三种。

  • 执行实现JavaDelegateActivityBehavior的类,这两个接口唯一的不同是是否阻塞流程,前者的执行不会阻塞流程,当程序经过节点该节点时,会执行java程序,执行完成会进入下一个节点,而后者会阻塞当前的流程,直到手动推动流程的进行。
  • 执行一个对象表达式,通常是spring配置的bean,也可以是通过参数传进来的bean。
  • 执行调用方法表达式和值表达式。
      我们通过代码来演示以上三种调用方式。
      (1)首先创建四个Bean用来测试,其中后两个Bean交由spring容器管理。
public class MyActivityBehavior implements ActivityBehavior {
    private static final Logger logger = LoggerFactory.getLogger(ConfigMDCTest.class);
    @Override
    public void execute(DelegateExecution delegateExecution) {
        logger.info("执行activityBehavior");
    }
}

public class MyJavaDelegate implements JavaDelegate {
    private static final Logger logger = LoggerFactory.getLogger(ConfigMDCTest.class);
    @Override
    public void execute(DelegateExecution delegateExecution) {
        logger.info("执行自定义的 javaDelegate ");
    }
}

public class MySpringJavaDelegate implements JavaDelegate {
    private static final Logger logger = LoggerFactory.getLogger(ConfigMDCTest.class);
    @Override
    public void execute(DelegateExecution delegateExecution) {
        logger.info("通过对象表达式执行的 MySpringJavaDelegate ");
    }
}

public class SpringHelloBean {
    private static final Logger logger = LoggerFactory.getLogger(ConfigMDCTest.class);
    private String name;

    public void sayHello(){
        logger.info("调用方法表达式执行sayHello()");
    }
    public String getName() {
        logger.info("调用值表达式执行getName");
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

  (2)创建流程定义文件,创建多个服务节点,分别使用以上三种方式执行java程序,通过占位符不止可以获取spring工厂的bean,还可以获取启动流程时传入的参数中指定的bean。

    <process id="my-process">
        <startEvent id="start" />
        <serviceTask id="myJavaDelegate" name="测试javaDelegate" activiti:class="activiti.servicedemo.MyJavaDelegate">
        </serviceTask>
        <serviceTask id="mySpringJavaDelegate" name="测试对象表达式" activiti:delegateExpression="${springJavaDelegate}">
        </serviceTask>
        <serviceTask id="myIvokeExpression" name="测试调用方法表达式" activiti:expression="${springHelloBean.sayHello()}">
        </serviceTask>
        <serviceTask id="myValueExpress" name="测试值表达式" activiti:expression="${springHelloBean.name}">
        </serviceTask>
        <serviceTask id="myActivityBeHavior" name="测试ActivityBehavior" activiti:class="activiti.servicedemo.MyActivityBehavior">
        </serviceTask>
        <sequenceFlow id="flow1" sourceRef="start" targetRef="myJavaDelegate"/>
        <sequenceFlow id="flow2" sourceRef="myJavaDelegate" targetRef="mySpringJavaDelegate"/>
        <sequenceFlow id="flow3" sourceRef="mySpringJavaDelegate" targetRef="myIvokeExpression"/>
        <sequenceFlow id="flow4" sourceRef="myIvokeExpression" targetRef="myValueExpress"/>
        <sequenceFlow id="flow5" sourceRef="myValueExpress" targetRef="myActivityBeHavior"/>
        <sequenceFlow id="flow6" sourceRef="myActivityBeHavior" targetRef="end" />
        <endEvent id="end" />
    </process>

  (3)编写测试类。

    @Test
    @Deployment(resources = {"my-process.bpmn20.xml"})
    public void test() throws Exception {

        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("my-process");
        //1. 程序并没有出现意象中的阻塞。
        Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();
        logger.info("当前的task = {}", task.getName());
        //2. 阻塞,手动推动流程的进行
        activitiRule.getManagementService().executeCommand(new Command<Object>() {
            @Override
            public Object execute(CommandContext commandContext) {
                commandContext.getAgenda().planTakeOutgoingSequenceFlowsOperation((ExecutionEntity) commandContext.getException(), false);
                return null;
            }
        });
        task = taskService.createTaskQuery().singleResult();
        logger.info("当前的task = {}", task.getName());
    }
  1. 顺序流和网关
      顺序流包括条件顺序流和默认顺序流。
      网关包括四种:排他网关(只有一条路)、并行网关(多条路都可以进行、会湖忽略你的条件)、包容性网关(只要满足条件的路都可以走)、基于事件网关(每个分支都会捕获相应的事件,只有对应分支的事件被捕获,流程才会进入该分支)
      (1)单一网关
单一网关

  编写流程定义文件:

    <process id="my-process">
        <startEvent id="start"/>
        <userTask id="fine" name="优秀"/>
        <userTask id="pass" name="及格"/>
        <userTask id="fail" name="不及格"/>
        <endEvent id="end"/>

        <exclusiveGateway id="classify"></exclusiveGateway>
        <sequenceFlow sourceRef="start" targetRef="classify"/>
        <sequenceFlow sourceRef="classify" targetRef="fine">
            <conditionExpression xsi:type="tFormalExpression">
                <![CDATA[ ${score >= 90}]]>

            </conditionExpression>
        </sequenceFlow>
        <sequenceFlow sourceRef="classify" targetRef="pass">
            <conditionExpression xsi:type="tFormalExpression">
                <![CDATA[ ${score < 90 && score >= 60}]]>
            </conditionExpression>
        </sequenceFlow>
        <sequenceFlow sourceRef="classify" targetRef="fail"></sequenceFlow>

    </process>

  进行编码测试

    @Test
    @Deployment(resources = "my-process.bpmn20.xml")
    public void test() throws InterruptedException {
        Map<String, Object> params = Maps.newHashMap();
        params.put("score", 90);
        activitiRule.getRuntimeService().startProcessInstanceByKey("my-process", params);
        Task task = activitiRule.getTaskService().createTaskQuery().singleResult();
        logger.info("task = {}", task.getName());

    }

  (2)并行网关

并行网关

  特征:流程并发,通过并行网关可以将流程执行流入多个分支,然后在最后汇总到并行网关中,只有当这些分支都汇入网关中,流程才会继续往下执行。另外并行网关是不支持条件判断的,它会忽略你对并行网关分支中的条件。

    <process id="my-process">
        <startEvent id="start"/>
        <userTask id="pay" name="确认支付"/>
        <userTask id="receive" name="确认收货"/>
        <userTask id="complete" name="订单完成"/>
        <endEvent id="end"/>

        <parallelGateway id="startgateway"></parallelGateway>
        <parallelGateway id="endgateway"></parallelGateway>
        <sequenceFlow sourceRef="start" targetRef="startgateway"></sequenceFlow>
        <sequenceFlow sourceRef="startgateway" targetRef="pay"></sequenceFlow>
        <sequenceFlow sourceRef="startgateway" targetRef="receive"></sequenceFlow>
        <sequenceFlow sourceRef="pay" targetRef="endgateway"></sequenceFlow>
        <sequenceFlow sourceRef="receive" targetRef="endgateway"></sequenceFlow>
        <sequenceFlow sourceRef="endgateway" targetRef="complete"></sequenceFlow>
        <sequenceFlow sourceRef="complete" targetRef="end"></sequenceFlow>

    </process>

  进行编码测试

    @Test
    @Deployment(resources = "my-process.bpmn20.xml")
    public void test() throws InterruptedException {

        ProcessInstance processInstance = activitiRule.getRuntimeService().startProcessInstanceByKey("my-process");
        List<Task> tasks = activitiRule.getTaskService().createTaskQuery().processInstanceId(processInstance.getId())
                .listPage(0, 100);
        //1. 其中会阻塞两个任务确认支付和确认收货
        for (Task task : tasks) {
            logger.info("task = {}", task.getName());
            activitiRule.getTaskService().complete(task.getId());

        }
        tasks = activitiRule.getTaskService().createTaskQuery().processInstanceId(processInstance.getId())
                .listPage(0, 100);
        for (Task task : tasks) {
            logger.info("task = {}", task.getName());
        }
    }

  (3)包容性网关


执行流程

  特征:既可以走多个分支,也可以走单个分支,流程会进入满足条件的分支。

  (4)基于事件网关

执行流程

  特征:当流程进入事件网关时,会被阻塞,直到某个分支对应的事件被触发,流程将会继续进行,其他分支的事件订阅清理,意思是只要该网关有一个分支被执行,就算其他分支感兴趣的事件被触发也不会再执行。

  1. 子流程
      子流程包括四种:子流程、事件子流程、事务子流程、调用子流程(相当于引用了另一个流程定义文件)。
    (1)普通子流程
子流程实例

  特征:分层建模、数据隔离、事件范围、边界事件。

  编写流程定义文件(为了简便,子流程没有使用并行网关,只有一个确认支付节点)

    <process id="my-process">
        <startEvent id="start"/>
        <userTask id="complete" name="订单完成"/>
        <userTask id="errorProcess" name="异常处理"/>
        <endEvent id="end"/>
        <subProcess id="subProcess">
            <startEvent id="startSub"/>
            <serviceTask id="pay" activiti:class="activiti.servicedemo.MyPayService"/>
            <endEvent id="endSub"/>

            <sequenceFlow sourceRef="startSub" targetRef="pay"/>
            <sequenceFlow sourceRef="pay" targetRef="endSub"/>
        </subProcess>

        <boundaryEvent attachedToRef="subProcess" id="errorEvent">
            <errorEventDefinition errorRef="notEnoughMoney" />
        </boundaryEvent>

            <sequenceFlow sourceRef="start" targetRef="subProcess"/>
            <sequenceFlow sourceRef="subProcess" targetRef="complete"/>
            <sequenceFlow sourceRef="errorEvent" targetRef="errorProcess"/>
            <sequenceFlow sourceRef="complete" targetRef="end"/>
            <sequenceFlow sourceRef="errorProcess" targetRef="end"/>
    </process>

  编写java程序类,执行确认支付流程。

public class MyPayService implements JavaDelegate {
    private static final Logger logger = LoggerFactory.getLogger(ConfigMDCTest.class);

    @Override
    public void execute(DelegateExecution delegateExecution) {
        logger.info("确认支付");
        double money = (double)delegateExecution.getVariable("money");
        if(money < 10){
            throw new BpmnError("notEnoughMoney");
        }
    }
}

  进行测试

    @Test
    @Deployment(resources = "my-process.bpmn20.xml")
    public void test() throws InterruptedException {

        Map<String ,Object> params = Maps.newHashMap();
        params.put("money", 9.0);
        ProcessInstance processInstance = activitiRule.getRuntimeService().startProcessInstanceByKey("my-process", params);
        Task task = activitiRule.getTaskService().createTaskQuery().singleResult();
        logger.info("task = {}", task.getName());

    }

(2)事件子流程

执行流程

  特征:事件触发、业务独立、中断事件流程、错误开始事件。普通子流程和事件子流程的区别是:后者可以获取子流程的本地变量,而前者不可以。
  修改流程定义文件

    <process id="my-process">
        <startEvent id="start"/>
        <userTask id="complete" name="订单完成"/>
        <serviceTask id="pay" activiti:class="activiti.servicedemo.MyPayService"/>
        <endEvent id="end"/>
        <subProcess id="subProcess" triggeredByEvent="true">
            <startEvent id="startSub">
                <errorEventDefinition errorRef="notEnoughMoney"/>
            </startEvent>
            <endEvent id="endSub"/>
            <userTask id="errorProcess" name="异常处理"/>
            <sequenceFlow sourceRef="startSub" targetRef="errorProcess"/>
            <sequenceFlow sourceRef="errorProcess" targetRef="endSub"/>
        </subProcess>
        <sequenceFlow sourceRef="start" targetRef="pay"/>
        <sequenceFlow sourceRef="pay" targetRef="complete"/>
        <sequenceFlow sourceRef="complete" targetRef="end"/>
    </process>

  使用之前的测试类进行测试。
(3)事务子流程(试验阶段,很少用)


事务子流程

(4)调用式子流程


调用式子流程

  定义父类的流程定义文件

    <process id="my-process">

        <callActivity id="callActiviti" calledElement="my-process-sub">
            <extensionElements>
                <!--将父流程属性supKey对应的值传入到子流程的subkey属性中-->
                <activiti:in target="subKey" source="supKey"/>
                <activiti:in target="money" source="money"/>
              <!--将子流程属性subError对应的值传入到父流程的supError属性中,如果子类抛出异常,则无法传出-->
                <activiti:out target="supError" source="subError"/>
            </extensionElements>
        </callActivity>


        <startEvent id="start"/>
        <userTask id="complete" name="订单完成"/>
        <userTask id="errorProcess" name="异常处理"/>
        <endEvent id="end"/>

        <boundaryEvent attachedToRef="callActiviti" id="errorEvent">
            <errorEventDefinition errorRef="notEnoughMoney" />
        </boundaryEvent>

        <sequenceFlow sourceRef="start" targetRef="callActiviti"/>
        <sequenceFlow sourceRef="callActiviti" targetRef="complete"/>
        <sequenceFlow sourceRef="complete" targetRef="end"/>
        <sequenceFlow sourceRef="errorEvent" targetRef="errorProcess"/>
        <sequenceFlow sourceRef="errorProcess" targetRef="end"/>
    </process>

  定义子流程的流程定义文件

    <process id="my-process-sub">
            <startEvent id="startSub"/>
            <serviceTask id="pay" activiti:class="activiti.servicedemo.MyPayService"/>
            <endEvent id="endSub"/>
            <sequenceFlow sourceRef="startSub" targetRef="pay"/>
            <sequenceFlow sourceRef="pay" targetRef="endSub"/>

    </process>

  编写子流程调用的java程序

public class MyPayService implements JavaDelegate {
    private static final Logger logger = LoggerFactory.getLogger(ConfigMDCTest.class);

    @Override
    public void execute(DelegateExecution delegateExecution) {
        logger.info("variables = {}",delegateExecution.getVariables());
        logger.info("确认支付");
        delegateExecution.setVariable("subError","子类的值");
        double money = (double)delegateExecution.getVariable("money");
        if(money < 10){
            throw new BpmnError("notEnoughMoney");
        }
    }
}

  编写测试类进行测试:

    @Test
    @Deployment(resources = {"my-process.bpmn20.xml", "my-process-sub.bpmn20.xml"})
    public void test() throws InterruptedException {

        Map<String ,Object> params = Maps.newHashMap();
        params.put("money", 11.0);
        params.put("supKey", "父类的值");
        ProcessInstance processInstance = activitiRule.getRuntimeService().startProcessInstanceByKey("my-process", params);
        Task task = activitiRule.getTaskService().createTaskQuery().singleResult();
        logger.info("task = {}", task.getName());
        Map<String, Object> variables = activitiRule.getRuntimeService().getVariables(processInstance.getId());
        logger.info("variables = {}", variables);
    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,616评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,020评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,078评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,040评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,154评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,265评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,298评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,072评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,491评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,795评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,970评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,654评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,272评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,985评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,815评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,852评论 2 351

推荐阅读更多精彩内容