activiti实战读书笔记——第十一章 事件

柔情痞子 提交于 2019-12-05 13:19:45

一、启动事件

每个流程都需要从启动事件开始,根据不同的需求有空启动、定时启动、异常启动和消息启动。

异常启动事件不能用于主流程,必须嵌入到事件子流程中。

1、定时启动事件

定时启动标签timerEventDefinition嵌套在启动事件startEvent中就构成了定时启动事件。定时启动事件可以有三种启动属性:

如下定义了一个定时启动事件,部署流程5分钟后启动:


<startEvent id="timerstartevent1" name="Timer start">
      <timerEventDefinition>
        <timeDuration>PT5M</timeDuration>
      </timerEventDefinition>
    </startEvent>

定时启动的测试代码如下:

@Deployment(resources = "chapter11/timerEvent/timerStartEvent.bpmn")
    public void testTriggerAutomatic() throws Exception {
        // 部署之后引擎会自动创建一个定时启动事件的Job
        JobQuery jobQuery = managementService.createJobQuery();
        assertEquals(1, jobQuery.count());

        // 模拟时间5分钟之后
        SimpleDateFormat sdf = new SimpleDateFormat("hh:mm:ss");
        System.out.println(".... start ...." + sdf.format(new Date()));
        Context.getProcessEngineConfiguration().getClock().setCurrentTime(new Date(System.currentTimeMillis() + ((50 * 60 * 1000) + 5000)));
        waitForJobExecutorToProcessAllJobs(5000L, 1L);
        System.out.println(".... end ...." + sdf.format(new Date()));

        assertEquals(0, jobQuery.count());

        // 检查是否启动了流程实例
        long count = runtimeService.createProcessInstanceQuery().processDefinitionKey("timerStartEvent").count();
        assertEquals(1, count);
    }



Context.getProcessEngineConfiguration().getClock().setCurrentTime(new Date(System.currentTimeMillis() + ((50 * 60 * 1000) + 5000)));
        waitForJobExecutorToProcessAllJobs(5000L, 1L);
这两行代码用activiti提供的测试工具用5s模拟5分钟,我自己运行的时候发现Context.getProcessEngineConfiguration()的结果是null,改用测试类继承的PluggableActivitiTestCase类的getProcessEngineConfiguration()后正常执行。


猜想是作者搞混了?Context.getProcessEngineConfiguration()是非测试时用的代码?

定时启动事件也可以手动触发,即API调用触发,测试代码如下:

@Deployment(resources = "chapter11/timerEvent/timerStartEvent.bpmn")
    public void testTriggerManual() throws Exception {
        // 部署之后引擎会自动创建一个定时启动事件的Job
        JobQuery jobQuery = managementService.createJobQuery();
        assertEquals(1, jobQuery.count());

        // 手动触发作业的执行
        Job job = jobQuery.singleResult();
        managementService.executeJob(job.getId());

        assertEquals(0, jobQuery.count());

        // 检查是否启动了流程实例
        long count = runtimeService.createProcessInstanceQuery().processDefinitionKey("timerStartEvent").count();
        assertEquals(1, count);
    }

定时启动的job呗记录在ACT_RU_JOB表中,以下是该表的一些主要字段:

2、消息启动事件

通过一个消息标志触发启动事件。

消息启动事件的相关代码:

<message id="MSG_001" name="启动XXX流程"></message>
  <process id="messageStartEvent" name="messageStartEvent" isExecutable="true">
    <startEvent id="messagestartevent1" name="Start">
      <messageEventDefinition messageRef="MSG_001"></messageEventDefinition>
    </startEvent>
    <userTask id="usertask1" name="用户任务" activiti:assignee="kermit"></userTask>
    <sequenceFlow id="flow1" sourceRef="messagestartevent1" targetRef="usertask1"></sequenceFlow>
    <endEvent id="endevent1" name="End"></endEvent>
    <sequenceFlow id="flow2" sourceRef="usertask1" targetRef="endevent1"></sequenceFlow>
  </process>



其中message标签定义了一个消息,id属性在流程定义文件内部引用,name属性是代码中启动事件的启动参数。

以消息启动事件的测试代码:

public void testStartMessageEvent(){
		ProcessInstance processInstance = this.runtimeService.startProcessInstanceByMessage("启动流程");
		Assert.assertNotNull(processInstance);
	}



也可以使用startProcessInstanceByKey方法来启动流程,但是这对于消息启动事件有一个限制:流程只能包含一个消息启动事件。测试代码如下:

public void testStartMessageEventByKey() throws Exception {
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("messageStartEvent");
        assertNotNull(processInstance);
    }



对于消息,在部署流程之后引擎会在初始化中处理消息事件,把消息的类型注册到ACT_RU_EVENT_SUBSCR表中,在流程执行过程中遇到了消息类型时间或通过API触发消息事件会从该表中读取数据,并且根据消息的属性调用消息处理器。表的主要字段如下:
  • EVENT_TYPE_:时间类型。message表示消息类型;
  • EVENT_NAME_:消息名称。触发消息时引擎会根据消息名称参数和这个字段进行匹配;
  • EXECUTION_ID_:实例执行ID。如果一个流程以消息启动事件引导,则此字段为空,如果在流程运行中注册了消息事件,则此字段保存流程执行ID;
  • PROC_INST_ID_:流程实例ID。通EXECUTION_ID_,保存流程实例ID(与EXECUTION是一对多的关系);
  • ACTIVITI_ID_:活动ID 。指示如果根据字段EVENT_NAME_匹配到消息就应该触发哪个活动;
  • CONFIGURATION_:消息的匹配信息。对于消息类型来说这里保存的是流程定义ID。

测试消息注册成功的代码:

@Test
	@Deployment(resources = "messageStartEvent.bpmn")
	public void testMessageSubcription(){
		EventSubscriptionQueryImpl query = new EventSubscriptionQueryImpl(this.processEngineConfiguration.getCommandExecutor());
		EventSubscriptionEntity entity = query.eventName("启动流程").singleResult();
		Assert.assertNotNull(entity);
	}



没写完,要出门了,晚上回来继续。BTW,今天北京天气不错,这会出门还能晒到太阳,下午安。

回来了,笔记继续。

二、终止结束事件

终止结束事件和空结束事件的区别在于终止结束事件可以终止整个流程实例的执行,而空结束事件只结束一条输出流的执行。

上图的流程中启动事件后通过一个并行网关得到一个主任务流程和一个子任务流程。如果主任务完成后到达终止结束事件则整个流程都将结束(包括进行中的子任务),但如果子任务先于主任务结束,则主任务还未结束,整个流程还处于运行状态。

终止结束事件的XML定义如下:


<endEvent id="terminateendevent1" name="End">
      <terminateEventDefinition></terminateEventDefinition>
    </endEvent>



在测试代码中使用了如下函数:


private void checkFinished(ProcessInstance processInstance) {
        // 验证流程已结束
        HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
                .processInstanceId(processInstance.getProcessInstanceId()).singleResult();
        assertNotNull(historicProcessInstance.getEndTime());

        // 查询历史任务
        List<HistoricTaskInstance> list = historyService.createHistoricTaskInstanceQuery().list();
        for (HistoricTaskInstance hti : list) {
            System.out.println(hti.getName() + "  " + hti.getDeleteReason());
        }

        // 流程结束后校验监听器设置的变量
        HistoricVariableInstance variableInstance = historyService.createHistoricVariableInstanceQuery().variableName("settedOnEnd").singleResult();
        assertEquals(true, variableInstance.getValue());
    }

其中HistoricTaskInstance.getDeleteReason()得到任务的删除原因属性,如果任务正常完成则属性为complete,如果异常终止为delete。


三、边界事件

1、异常边界事件

下面的xml定义中在一个java service添加了异常边界事件


<process id="throwErrorManual" name="throwErrorManual" isExecutable="true">
    <startEvent id="startevent1" name="Start"></startEvent>
    <serviceTask id="servicetask1" name="自动执行系统任务" activiti:class="me.kafeitu.activiti.chapter11.listener.ThrowErrorManaualService"></serviceTask>
    <sequenceFlow id="flow1" sourceRef="startevent1" targetRef="servicetask1"></sequenceFlow>
    <endEvent id="endevent1" name="End"></endEvent>
    <sequenceFlow id="flow2" sourceRef="servicetask1" targetRef="endevent1"></sequenceFlow>
    <boundaryEvent id="boundaryerror1" name="Error" attachedToRef="servicetask1">
      <errorEventDefinition errorRef="AIA_ERROR_99"></errorEventDefinition>
    </boundaryEvent>
    <userTask id="usertask1" name="处理异常"></userTask>
    <sequenceFlow id="flow3" sourceRef="boundaryerror1" targetRef="usertask1"></sequenceFlow>
    <sequenceFlow id="flow4" sourceRef="usertask1" targetRef="servicetask1"></sequenceFlow>
    <textAnnotation id="textannotation1">
      <text>异常代码:AIA_ERROR_99</text>
    </textAnnotation>
    <association id="association1" sourceRef="textannotation1" targetRef="boundaryerror1"></association>
  </process>



其中java service的实现类代码如下:
public class ThrowErrorManaualService implements JavaDelegate {
    @Override
    public void execute(DelegateExecution execution) throws Exception {
        if (execution.getVariable("pass") == null) {
            throw new BpmnError("AIA_ERROR_99");
        }
    }
}

2、消息边界事件

定义消息边界事件的代码如下:


<process id="messageBoundaryEvent" name="messageBoundaryEvent" isExecutable="true">
    <startEvent id="startevent1" name="Start"></startEvent>
    <userTask id="usertask1" name="审核文件"></userTask>
    <sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
    <endEvent id="endevent1" name="End"></endEvent>
    <sequenceFlow id="flow2" sourceRef="usertask1" targetRef="endevent1"></sequenceFlow>
    <boundaryEvent id="boundarymessage1" name="Message" attachedToRef="usertask1" cancelActivity="true">
      <messageEventDefinition messageRef="MSG_HELP"></messageEventDefinition>
    </boundaryEvent>
    <userTask id="usertask2" name="协助处理"></userTask>
    <sequenceFlow id="flow3" sourceRef="boundarymessage1" targetRef="usertask2"></sequenceFlow>
    <sequenceFlow id="flow4" sourceRef="usertask2" targetRef="usertask1"></sequenceFlow>
  </process>



其中<boundaryEvent>中有一个cancelActiviti属性,在消息边界事件中属性为true表示消息边界事件触发后已注册的消息被删除;为false表示不删除,消息可以被重复触发。测试代码中:


ExecutionQuery executionQuery = runtimeService.createExecutionQuery().messageEventSubscriptionName("MSG_协助处理");
        Execution execution = executionQuery.singleResult();
        runtimeService.messageEventReceived("MSG_协助处理", execution.getId());



ExecutionQuery查询对象查询流程执行对象(Execution,与ProcessInstance是多对一的关系。引擎遇到消息边界事件的任务时创建一个执行对象监听消息事件的触发动作。

3、信号边界事件与消息边界事件的机制相似


<signal id="S_HELP" name="S_协助处理"></signal>
  <process id="signalBoundaryEvent" name="Signal Boundary Event" isExecutable="true">
    <startEvent id="startevent1" name="Start"></startEvent>
    <userTask id="usertask1" name="审核文件"></userTask>
    <sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
    <boundaryEvent id="boundarysignal1" name="Signal" attachedToRef="usertask1" cancelActivity="true">
      <signalEventDefinition signalRef="S_HELP"></signalEventDefinition>
    </boundaryEvent>
    <userTask id="usertask2" name="协助处理"></userTask>
    <sequenceFlow id="flow2" sourceRef="boundarysignal1" targetRef="usertask2"></sequenceFlow>
    <sequenceFlow id="flow3" sourceRef="usertask2" targetRef="usertask1"></sequenceFlow>
    <endEvent id="endevent1" name="End"></endEvent>
    <sequenceFlow id="flow4" sourceRef="usertask1" targetRef="endevent1"></sequenceFlow>
  </process>



四、中间事件

分为捕获型和抛出型两种,定义如下:


<process id="throwAlert" name="My process" isExecutable="true">
    <startEvent id="startevent1" name="Start"></startEvent>
    <intermediateThrowEvent id="signalintermediatethrowevent1" name="SignalThrowEvent">
      <signalEventDefinition signalRef="alert"></signalEventDefinition>
    </intermediateThrowEvent>
    <sequenceFlow id="flow1" sourceRef="startevent1" targetRef="signalintermediatethrowevent1"></sequenceFlow>
    <endEvent id="endevent1" name="End"></endEvent>
    <sequenceFlow id="flow2" sourceRef="signalintermediatethrowevent1" targetRef="endevent1"></sequenceFlow>
    <textAnnotation id="textannotation1">
      <text>抛出alert信号</text>
    </textAnnotation>
    <association id="association1" sourceRef="textannotation1" targetRef="signalintermediatethrowevent1"></association>
  </process>



<signal id="alert" name="alert"></signal>
  <signal id="abort" name="abort"></signal>
  <process id="catchSignal" name="My process" isExecutable="true">
    <startEvent id="startevent1" name="Start"></startEvent>
    <userTask id="usertask1" name="附加边界用户任务"></userTask>
    <sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
    <intermediateCatchEvent id="signalintermediatecatchevent1" name="SignalCatchEvent">
      <signalEventDefinition signalRef="abort"></signalEventDefinition>
    </intermediateCatchEvent>
    <sequenceFlow id="flow2" sourceRef="startevent1" targetRef="signalintermediatecatchevent1"></sequenceFlow>
    <userTask id="usertask2" name="被中间信号抛出事件触发"></userTask>
    <sequenceFlow id="flow3" name="捕获abort信号" sourceRef="signalintermediatecatchevent1" targetRef="usertask2"></sequenceFlow>
    <endEvent id="endevent1" name="End"></endEvent>
    <sequenceFlow id="flow4" sourceRef="usertask2" targetRef="endevent1"></sequenceFlow>
    <sequenceFlow id="flow5" sourceRef="usertask1" targetRef="endevent1"></sequenceFlow>
    <boundaryEvent id="boundarysignal1" name="Signal" attachedToRef="usertask1" cancelActivity="true">
      <signalEventDefinition signalRef="alert"></signalEventDefinition>
    </boundaryEvent>
    <sequenceFlow id="flow6" name="捕获alert信号" sourceRef="boundarysignal1" targetRef="usertask3"></sequenceFlow>
    <userTask id="usertask3" name="通过信号边界事件触发"></userTask>
    <sequenceFlow id="flow7" sourceRef="usertask3" targetRef="endevent1"></sequenceFlow>
  </process>





易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!