疯狂Activiti6.0连载(25)BPMN结束事件

原创
2017/12/10 20:20
阅读数 6.4K

本文节选自《疯狂工作流讲义(第2版)》

京东购买地址:https://item.jd.com/12246565.html

疯狂Activiti电子书:https://my.oschina.net/JavaLaw/blog/1570397

工作流Activiti教学视频:https://my.oschina.net/JavaLaw/blog/1577577

 

BPMN结束事件

        结束事件表示流程的结束,因此结束事件并不允许有输出的顺序流,BPMN2.0规定没有顺序流可以从结束事件中输出。根据前面的章节所述,结束事件总是抛出事件,这些事件会自动执行并反馈结果,并不需要触发。BPMN2.0中定义了多种结束事件,包括:无指定(None)结束事件、消息(Message)结束事件、升级(Escalation)结束事件、错误(Error)取消事件、取消(Cancel)结束事件、补偿(Compensation)结束事件、信号(Signal)结束事件、终止(Terminate)结束事件和组合(Multiple)结束事件,目前Activiti支持无指定结束事件、错误结束事件、取消结束事件和终止结束事件。

无指定结束事件

        与无指定开始事件一样,无指定结束事件,是指流程在结束时,不会进行任何的额外操作,结束事件中不使用任何事件的定义。图11-5为结束事件的图形。

图11-5 结束事件的图形

        在流程描述文件中,使用endEvent元素定义一件结束事件,代码清单11-14为一个简单的流程配置内容,该段配置的粗体字代码,定义了一个无指定结束事件。

        代码清单11-14:codes\11\11.4\end-event\resource\bpmn\NoneEndEvent.bpmn

    <process id="myProcess" name="myProcess">
        <startEvent id="startevent1" name="Start"></startEvent>
        <userTask id="usertask1" name="User Task"></userTask>
        <endEvent id="endevent1" name="End"></endEvent>
        <sequenceFlow id="flow1" name="" sourceRef="startevent1"
            targetRef="usertask1"></sequenceFlow>
        <sequenceFlow id="flow2" name="" sourceRef="usertask1"
            targetRef="endevent1"></sequenceFlow>
    </process>

错误结束事件

        当执行流到达错误结束事件时,会结束该执行流并且抛出错误,该错误可以被“错误边界事件”捕获,如果没有定义任何的错误边界事件,那么将会被当作无指定错误事件执行,因此,错误结束事件一般使用在子流程当中。错误事件结束后,就会触发依附在该子流程上的错误边界事件。图11-6定义了一个含有错误结束事件和错误边界事件的流程。

图11-6 含有错误结束事件和错误边界事件的流程

        如图11-6所示,该流程一启动,就会进入一个嵌套子流程,当完成了“Sub Task”的用户任务后,会遇到单向网关,此时会产生两个分支,一个分支会正常结束该子流程,另外一个分支会触发错误结束事件,如果正常结束该子流程,流程会到达“End Task”任务,而触发错误结束事件后,流程会到达“Error Task”任务。该流程图对应的流程文件如代码清单11-15所示。

        代码清单11-15:codes\11\11.4\end-event\resource\bpmn\ErrorEndEvent.bpmn

    <error id="myError" errorCode="myError"></error>    
    <process id="errorEndProcess" name="errorEndProcess">
        <startEvent id="startevent1" name="Start"></startEvent>
        <subProcess id="subprocess1" name="Sub Process">
            <startEvent id="startevent2" name="Start"></startEvent>
            <userTask id="usertask1" name="Sub Task"></userTask>
            <endEvent id="endevent1" name="ErrorEnd">
                <errorEventDefinition errorRef="myError"></errorEventDefinition>
            </endEvent>
            <sequenceFlow id="flow2" name="" sourceRef="startevent2"
                targetRef="usertask1"></sequenceFlow>
            <exclusiveGateway id="exclusivegateway1" name="Exclusive Gateway"></exclusiveGateway>
            <endEvent id="endevent3" name="End"></endEvent>
            <sequenceFlow id="flow7" name="" sourceRef="usertask1"
                targetRef="exclusivegateway1"></sequenceFlow>
            <sequenceFlow id="flow8" name="" sourceRef="exclusivegateway1"
                targetRef="endevent3">
                <conditionExpression xsi:type="tFormalExpression">${success == true}
                </conditionExpression>
            </sequenceFlow>
            <sequenceFlow id="flow9" name="" sourceRef="exclusivegateway1"
                targetRef="endevent1">
                <conditionExpression xsi:type="tFormalExpression">${success ==
                    false}</conditionExpression>
            </sequenceFlow>
        </subProcess>
        <boundaryEvent id="boundaryerror1" cancelActivity="true"
            attachedToRef="subprocess1">
            <errorEventDefinition errorRef="myError"></errorEventDefinition>
        </boundaryEvent>
        <userTask id="usertask3" name="End Task"></userTask>
        <endEvent id="endevent2" name="End"></endEvent>
        <userTask id="usertask4" name="Error Task"></userTask>
        <sequenceFlow id="flow1" name="" sourceRef="startevent1"
            targetRef="subprocess1"></sequenceFlow>
        <sequenceFlow id="flow4" name="" sourceRef="subprocess1"
            targetRef="usertask3"></sequenceFlow>
        <sequenceFlow id="flow5" name="" sourceRef="usertask3"
            targetRef="endevent2"></sequenceFlow>
        <sequenceFlow id="flow6" name="" sourceRef="boundaryerror1"
            targetRef="usertask4"></sequenceFlow>
    </process>

        代码清单11-15中的endEvent和boundaryEvent,分别定义了一个错误结束事件和错误边界事件,这两个事件中的错误事件定义,均引用了“myError”的错误,需要注意的是,在代码清单11-15中,需要使用单向网关,当流程参数“success”的值为true时,将会正常完成子流程,如果流程参数“success”的值为false时(代码清单的粗体部分的表达式),将会触发错误结束事件。代码清单11-16加载该流程文件并执行相应的流程控制。

        代码清单11-16:codes\11\11.4\end-event\src\org\crazyit\activiti\ErrorEndEvent.java

       // 创建流程引擎
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
        // 得到流程存储服务组件
        RepositoryService repositoryService = engine.getRepositoryService();
        // 得到运行时服务组件
        RuntimeService runtimeService = engine.getRuntimeService();
        //获取流程任务组件
        TaskService taskService = engine.getTaskService();
        // 部署流程文件
        repositoryService.createDeployment()
            .addClasspathResource("bpmn/ErrorEndEvent.bpmn").deploy();
        // 启动流程
        runtimeService.startProcessInstanceByKey("errorEndProcess");        
        // 结束子流程中的任务,并设置结束参数
        Task subTask = taskService.createTaskQuery().singleResult();
        Map<String, Object> vars = new HashMap<String, Object>();
        //设置success参数为true
        vars.put("success", "true");
        taskService.complete(subTask.getId(), vars);
        // 查看到达的任务
        List<Task> tasks = taskService.createTaskQuery().list();
        for (Task task : tasks) {
            System.out.println(task.getName());
        }

        注意代码清单11-16的粗体字代码,该句代码主要用于设置“success”流程参数值,此处设置为“true”,即运行以上代码,将会正常完成子流程,流程会到达“End Task”的任务(不会触发错误结束事件),最终输出结果:End Task;如果将“success”的参数值设置为false,那么将会触发错误结束事件,此时依附在子流程中的错误边界事件将会捕获到错误,流程会到达“Error Task”任务,最终输出结果:Error Task。

        在Activiti中,如果触发了边界事件,那么将会产生新的执行流,如果不希望取消原来的执行流,那么可以设置边界事件的cancelActivity属性为false(本例为true),即使新产生的执行流结束,原来的执行流也不会中断,原来执行流的当前活动仍然为边界事件,如果cancelActivity设置为true,原来的执行流将会被取消。

取消结束事件和取消边界事件

        取消结束事件只能使用在事务子流程(Transaction Sub-Process)中,该事件表示事务将会取消,并且会触发依附在事务子流程上的取消边界事件,与错误结束事件类似,取消结束事件会被抛出,而取消边界事件则会捕获事件。除此之外,事务子流程的取消事件的触发,还会导致补偿的触发。

        在BPMN2.0中,对于已经完成的活动,可以使用补偿机制,而对于一些正在进行的(活跃的)活动,不能使用补偿机制,而可以使用取消机制。当取消边界事件被触发,则会将当前的执行流中断,然后会同步地进行补偿机制,取消边界事件在离开事件子流程前,会一直等待补偿的结束,当补偿结束后,执行流会从取消边界事件离开事务子流程。假设现在有一个汇款的流程,该汇款流程作为一个事务子流程使用在主流程中,当汇款完成后,需要用户进行最终确认,如果用户确认,则结束流程,否则将触发取消结束事件并进行补偿操作,图11-7为该业务流程的流程图。

图11-7 汇款业务的流程图

        图11-7的流程启动后,会马上进入事务子流,进入子流程后,会直接进行汇款操作,在此是一个ServiceTask,完成了汇款操作后,会进行用户的确认,用户确认完后会到达一个单向网关,判断用户确认的结果,如果用户确认的结果为false,则会进入取消结束事件,最后会触发依附在事务子流程中的取消边界事件(在此之前会触发事务子流程中的补偿事件)。图11-7对应的流程描述文件内容如代码清单11-17所示。

        代码清单11-17:codes\11\11.4\end-event\resource\bpmn\CancelEndEvent.bpmn

    <process id="cancelProcess" name="cancelProcess">
        <startEvent id="startevent1" name="Start"></startEvent>
        <transaction id="subprocess1" name="Sub Process">
            <serviceTask id="servicetask1" name="汇款操作"
                activiti:class="org.crazyit.activiti.RemitDelegate"></serviceTask>    ①
            <boundaryEvent id="boundarysignal1" attachedToRef="servicetask1">            ②
                <compensateEventDefinition></compensateEventDefinition>
            </boundaryEvent>
            <startEvent id="startevent2" name="Start"></startEvent>
            <serviceTask id="servicetask2" name="取消汇款"                            ③
                activiti:class="org.crazyit.activiti.RollbackRemitDelegate"
                isForCompensation="true"></serviceTask>
            <userTask id="usertask1" name="确认汇款"></userTask>
            <exclusiveGateway id="exclusivegateway1" name="Exclusive Gateway"></exclusiveGateway>
            <endEvent id="endevent1" name="End"></endEvent>
            <endEvent id="endevent2" name="End">                                    ④
                <cancelEventDefinition></cancelEventDefinition>
            </endEvent>
            <sequenceFlow id="flow3" name="" sourceRef="startevent2"
                targetRef="servicetask1"></sequenceFlow>
            <sequenceFlow id="flow4" name="" sourceRef="servicetask1"
                targetRef="usertask1"></sequenceFlow>
            <sequenceFlow id="flow5" name="" sourceRef="usertask1"
                targetRef="exclusivegateway1"></sequenceFlow>
            <sequenceFlow id="flow6" name="" sourceRef="exclusivegateway1"
                targetRef="endevent1">
                <conditionExpression xsi:type="tFormalExpression">${confirm == true}
                </conditionExpression>
            </sequenceFlow>
            <sequenceFlow id="flow7" name="" sourceRef="exclusivegateway1"
                targetRef="endevent2">
                <conditionExpression xsi:type="tFormalExpression">${confirm ==
                    false}
                </conditionExpression>
            </sequenceFlow>
            <association associationDirection="One" id="a1"
                sourceRef="boundarysignal1" targetRef="servicetask2" />
        </transaction>
        <boundaryEvent id="boundarysignal2" cancelActivity="true"                        ⑤
            attachedToRef="subprocess1">
            <cancelEventDefinition></cancelEventDefinition>
        </boundaryEvent>
        <serviceTask id="servicetask3" name="接收取消操作"                                ⑥
            activiti:class="org.crazyit.activiti.ReceiveCancelDelegate"></serviceTask>
        <endEvent id="endevent3" name="End"></endEvent>
        <sequenceFlow id="flow1" name="" sourceRef="startevent1"
            targetRef="subprocess1"></sequenceFlow>
        <sequenceFlow id="flow2" name="" sourceRef="subprocess1"
            targetRef="endevent3"></sequenceFlow>
        <sequenceFlow id="flow8" name="" sourceRef="boundarysignal2"
            targetRef="servicetask3"></sequenceFlow>
    </process>

        注意代码清单11-17的粗体字代码,其中①③⑥定义了三个ServiceTask,对应的是流程中的汇款操作、取消汇款操作和接收取消操作的ServiceTask,①对应的是RemitDelegate类(codes\11\11.4\end-event\src\org\crazyit\activiti\RemitDelegate.java),该类用于处理汇款操作,本例只输出“处理汇款业务”。

        代码清单11-17中的③定义的ServiceTask,对应的是RollbackRemitDelegate类(codes\11\11.4\end-event\src\org\crazyit\activiti\RollbackRemitDelegate.java),该ServiceTask会在补偿边界事件被触发后执行,RollbackRemitDelegate在业务上表示用于处理汇款的回滚(取消汇款)操作,此处只会输出“处理回滚汇款业务”。

        需要注意的是,在定义取消汇款的ServiceTask的时候,需要为serviceTask加入isForCompensation属性并将值设置为true,该属性表示这个ServiceTask是一个补偿处理者的角色。代码清单中的⑥为接收取消通知的ServiceTask,对应的类为ReceiveCancelDelegate类(codes\11\11.4\end-event\src\org\crazyit\activiti\ReceiveCancelDelegate.java),如果事务子流程被触发并且处理完全部的补偿事件后,则会执行该ServiceTask,本例中该类输出“处理子流程取消后的业务”。

        代码清单11-17中的②定义了一个补偿边界事件,如果该补偿边界事件所处的事务子流程被取消,则该补偿边界事件就会被触发,在本例中,如果②的补偿边界事件被触发,就会执行“取消汇款”的ServiceTask。

        代码清单11-17中的④定义了一个取消结束事件,当执行流到达取消结束事件时,就会抛出取消事件,而在代码清11-17中的⑤则会捕获该事件,⑤定义了一个取消边界事件,取消边界事件用于捕获取消事件,当捕获到事件后,则会中断当前的执行流,然后触发事务子流程中的补偿事件,最后流程离开取消边界事件,最后执行⑥所定义的ServiceTask,代码清单11-18加载该流程文件。

        代码清单11-18:codes\11\11.4\end-event\src\org\crazyit\activiti\CancelEndEvent.java

        // 创建流程引擎
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
        // 得到流程存储服务组件
        RepositoryService repositoryService = engine.getRepositoryService();
        // 得到运行时服务组件
        RuntimeService runtimeService = engine.getRuntimeService();
        // 获取流程任务组件
        TaskService taskService = engine.getTaskService();
        // 部署流程文件
        repositoryService.createDeployment()
                .addClasspathResource("bpmn/CancelEndEvent.bpmn").deploy();
        // 启动流程
        runtimeService.startProcessInstanceByKey("cancelProcess");
        // 初始化流程参数
        Map<String, Object> vars = new HashMap<String, Object>();
        vars.put("confirm", false);
        // 设置参数,完成用户确认的Task
        Task task = taskService.createTaskQuery().singleResult();
        taskService.complete(task.getId(), vars);

        当图11-7的流程启动后,会经过“汇款操作”的ServiceTask,此时会输出“处理汇款业务”,然后到达“用户确认”的Task,代码清单11-18中将“用户确认”的Task完成并设置“confirm”参数为false,这个时候流程会到达取消结束事件,取消结束事件被触发并抛出,此时取消边界事件会捕获该抛出事件,触发事务子流程里面的补偿事件(此处为代码清单11-17中的②)。事务子流程里面“取消汇款”(代码清单11-17中的③)是“汇款操作”(代码清单11-17中的①)的补偿,当补偿事件被触发后,“取消汇款”的ServiceTask被执行,输出“处理回滚汇款业务”。当整个事务子流程里面的补偿事件都处理完后,流程离开取消边界事件(代码清单11-17中的⑤),到达“接收取消操作”的ServiceTask,输出“处理子流程取消后的业务”。运行代码清单11-18,输出结果如下:

处理汇款业务
处理回滚汇款业务
处理子流程取消后的业务

终止结束事件

        当流程执行到终止结束事件,当前的流程将会被终结,该事件可以使用在嵌入子流程、调用子流程、事件子流程或者事务子流程中。终止结束事件使用terminateEventDefinition元素作为事件定义,如果将该元素的activiti:terminateAll属性设置为true,那么当终止结束事件被触发事时,流程实例的全部执行流均会被终结。图11-8是一个简单流程,对应的流程XML文件如代码清单11-19所示。

图11-8 测试终止结束事件流程

        代码清单11-19:

        codes\11\11.4\end-event\resource\bpmn\TerminateEndEvent_TerminateAll.bpmn

    <process id="terminateAll" name="terminateAll" isExecutable="true">
        <startEvent id="startevent1" name="Start"></startEvent>
        <subProcess id="subprocess1" name="Sub Process">
            <startEvent id="startevent2" name="Start"></startEvent>
            <serviceTask id="servicetask2" name="子流程的ServiceTask"
                activiti:class="org.crazyit.activiti.SubProcessDelegate"></serviceTask>
            <endEvent id="terminateendevent1" name="TerminateEndEvent">
                <terminateEventDefinition
                    activiti:terminateAll="true"></terminateEventDefinition>
            </endEvent>
            <sequenceFlow id="flow7" sourceRef="startevent2"
                targetRef="servicetask2"></sequenceFlow>
            <sequenceFlow id="flow8" sourceRef="servicetask2"
                targetRef="terminateendevent1"></sequenceFlow>
        </subProcess>
        <userTask id="servicetask1" name="第一个用户任务"></userTask>
        <parallelGateway id="parallelgateway1" name="Parallel Gateway"></parallelGateway>
        <sequenceFlow id="flow1" sourceRef="startevent1"
            targetRef="parallelgateway1"></sequenceFlow>
        <sequenceFlow id="flow2" sourceRef="parallelgateway1"
            targetRef="subprocess1"></sequenceFlow>
        <sequenceFlow id="flow3" sourceRef="parallelgateway1"
            targetRef="servicetask1"></sequenceFlow>
        <endEvent id="endevent1" name="End"></endEvent>
        <userTask id="usertask1" name="第二个用户任务"></userTask>
        <sequenceFlow id="flow4" sourceRef="subprocess1"
            targetRef="usertask1"></sequenceFlow>
        <sequenceFlow id="flow5" sourceRef="servicetask1"
            targetRef="usertask1"></sequenceFlow>
        <sequenceFlow id="flow6" sourceRef="usertask1" targetRef="endevent1"></sequenceFlow>
    </process>

        代码清单11-19中的粗体字代码,定义了终止结束事件,并将activiti:terminateAll属性设置为true,流程启动后,会分为两个执行流,一个到达子流程,一个到达“第一个用户任务”,子流程会自动执行ServictTask并触发终止结束事件,由于设置了activiti:terminateAll属性,因此当子流程中的结束事件触发后,整个流程实例会被结束。代码清单11-20为测试代码。

        代码清单11-20:codes\11\11.4\end-event\src\org\crazyit\activiti\TerminateEndEvent.java

       // 创建流程引擎
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
        // 得到流程存储服务组件
        RepositoryService repositoryService = engine.getRepositoryService();
        // 得到运行时服务组件
        RuntimeService runtimeService = engine.getRuntimeService();
        // 部署流程文件
        repositoryService
                .createDeployment()
                .addClasspathResource(
                        "bpmn/TerminateEndEvent_TerminateAll.bpmn")
                .addClasspathResource("bpmn/TerminateEndEvent.bpmn").deploy();
        // 启动含有terminateAll属性的流程
        ProcessInstance pi1 = runtimeService
                .startProcessInstanceByKey("terminateAll");
        // 查询执行流数量
        long exeCount = runtimeService.createExecutionQuery()
                .processInstanceId(pi1.getId()).count();
        System.out.println("含有 terminateAll 属性的流程,中断结束事件触发后执行流数量:" + exeCount);
        // 启动不含 有 terminateAll属性的流程
        ProcessInstance pi2 = runtimeService
                .startProcessInstanceByKey("terminateEvent");
        // 查询全部执行流数量
        exeCount = runtimeService.createExecutionQuery()
                .processInstanceId(pi2.getId()).count();
        System.out.println("不含有terminateAll 属性的流程,中断结束事件触发后执行流数量:" + exeCount);

        注意代码清单10-20中加载了两份流程文件,一份将activiti:terminateAll设置为true,而另外一份则没有设置该属性(默认值为false),两份流程文件的流程图与图11-8一致。代码清单10-20加载两份流程文件后,分别启动两个流程实例,然后再进行执行流查询。运行代码清单10-20,得到以下结果:

含有 terminateAll 属性的流程,中断结束事件触发后执行流数量:0
不含有terminateAll 属性的流程,中断结束事件触发后执行流数量:3

        根据输出结果可知,如果终止结束事件的activiti:terminateAll属性被设置为true,终止结束事件触发后,整个流程实例将会被终结,查询不到任何执行流。

本文节选自《疯狂工作流讲义(第2版)》

京东购买地址:https://item.jd.com/12246565.html

疯狂Activiti电子书:https://my.oschina.net/JavaLaw/blog/1570397

工作流Activiti教学视频:https://my.oschina.net/JavaLaw/blog/1577577

本书代码目录:https://gitee.com/yangenxiong/CrazyActiviti

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
0 评论
3 收藏
0
分享
返回顶部
顶部