Activiti流程任务

5、其他任务

除了前面讲述的三种任务外,BPMN中还包括Send Task、Receive Task、Manual Task和Business Rule Task,其中Send Task在Activiti中可以体现为Email Task,Activiti中实现的Business Rule Task,目前只支持JBoss的Drools规则引擎。接下来将介绍User Task、Service Task
和Script Task之外的一些“非主流”任务。

5.1、手动任务和接收任务

手动任务表示不需要任何程序或者流程引擎的驱动会自动执行的任务,在Activiti的实现中,当执行流到达该任务时,会自动离开该任务,只是简单地记录相关的流程历史数据。以下的配置片断表示一个手动任务:

<manualTask id="manualtask1"name="Manual Task"></manualTask>

流程到达手动任务后,不需要显式的声明,流程自然会往下执行,手动任务表示一种会自动往下执行的流程角色。与手动任务类似是,BPMN中有接收任务,其同样表示一种特定的流程角色,但是与手动任务不一样的是,接收任务总是等待外界的通知,告诉其消息已经接收,流程才可以继续向前执行,目前Activiti只为其提供了Java实现,对应Activiti中的Java ReceiveTask。而在Activiti的实现中,当流程到达接收任务时,并不会往任务表中写入数据,任务的状态只会在执行流的数据表中体现。以下为一个接收任务的配置片断:

<receiveTask id="receivetaskl"name="Receive Task"></receiveTask>

图所示为一个既有手动任务也有接收任务的流程。

在这里插入图片描述

<process id="process1" name="process1" isExecutable="true">
  <startEvent id="startevent1" name="Start"></startEvent>
  <manualTask id="manualtask1" name="Manual Task"></manualTask>
  <endEvent id="endevent1" name="End"></endEvent>
  <sequenceFlow id="flow1" sourceRef="startevent1" targetRef="manualtask1"></sequenceFlow>
  <receiveTask id="receivetask1" name="Receive Task"></receiveTask>
  <sequenceFlow id="flow2" sourceRef="manualtask1" targetRef="receivetask1"></sequenceFlow>
  <sequenceFlow id="flow3" sourceRef="receivetask1" targetRef="endevent1"></sequenceFlow>
</process>
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = engine.getRepositoryService();
RuntimeService runtimeService = engine.getRuntimeService();
TaskService taskService = engine.getTaskService();
repositoryService.createDeployment().addClasspathResource("demo12/ManualTask.bpmn").deploy();
// 启动流程
ProcessInstance pi = runtimeService.startProcessInstanceByKey("process1");
// 查询当前任务
List<Task> tasks = taskService.createTaskQuery().list();
System.out.println("执行手工任务之后、接收任务之前的任务数量:" + tasks.size());
List<ProcessInstance> pis = runtimeService.createProcessInstanceQuery().list();
System.out.println("执行手工任务之后、接收任务之前的流程实例数量:" + pis.size());
Execution exe = runtimeService.createExecutionQuery()
        .processInstanceId(pi.getId()).onlyChildExecutions()
        .singleResult();
// 让流程向前执行
runtimeService.trigger(exe.getId());
// 查询流程
pis = runtimeService.createProcessInstanceQuery().list();
System.out.println("执行接收任务后的流程实例数量:" + pis.size());
执行手工任务之后、接收任务之前的任务数量:6
执行手工任务之后、接收任务之前的流程实例数量:7
执行接收任务后的流程实例数量:6

5.2、邮件任务

邮件任务与Shell Task一样,并非BPMN中定义的任务,该任务是Activiti提供的自动发送邮件的任务,当前支持邮件抄送、密送和HTML格式等。与Shell Task类似,Activiti对其的实现中也使用了ActivityBehavior作为该任务的执行类,对应MailActivityBehavior类,在ActivityBehavior实现类中,同样内置了多个属性供使用者配置使用,这些属性的注入与JavaDelegate的属性注入一样。邮件任务包含以下可配置属性。

  • to:收件人地址,必选项,多个收件人之间以逗号隔开。
  • from:发件人地址,可选项。
  • subject:邮件主题,可选项。
  • cc:抄送,可选项,多个抄送人之间以逗号隔开。
  • bcc:密送,可选项,多个密送人之间以逗号隔开
  • charset:邮件字符集,可选项,配置该属性可以进行中文邮件的发送。
  • html:HTML格式的邮件正文,可选项。
  • text:文本格式的邮件正文,可选项,当html属性和text属性都没有提供时,将抛出异常,异常信息为:Text or html field should be provided.
<process id="process1" name="process1" isExecutable="true">
	<startEvent id="startevent1" name="Start"></startEvent>
	<sendTask id="mailtask1" name="Mail Task" activiti:type="mail">
		<extensionElements>
			<activiti:field name="from">
				<activiti:string><![CDATA[发送人@163.com]]></activiti:string>
			</activiti:field>
			<activiti:field name="to">
				<activiti:string><![CDATA[收件人@163.com]]></activiti:string>
			</activiti:field>
			<activiti:field name="subject">
				<activiti:string><![CDATA[这是Activiti的测试邮件]]></activiti:string>
			</activiti:field>
			<activiti:field name="html">
				<activiti:expression><![CDATA[<html>
			            <body>
			              <table border="1">
			              	<tr>
			              		<td>Angus Young</td>
			              		<td>30</td>
			              	</tr>
			              </table>
			            </body>
			          </html>]]></activiti:expression>
			</activiti:field>
		</extensionElements>
	</sendTask>
	<endEvent id="endevent1" name="End"></endEvent>
	<sequenceFlow id="flow1" sourceRef="startevent1"
		targetRef="mailtask1"></sequenceFlow>
	<sequenceFlow id="flow2" sourceRef="mailtask1" targetRef="endevent1"></sequenceFlow>
</process>
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = engine.getRepositoryService();
RuntimeService runtimeService = engine.getRuntimeService();
TaskService taskService = engine.getTaskService();
repositoryService.createDeployment().addClasspathResource("demo12/EmailTask.bpmn").deploy();
runtimeService.startProcessInstanceByKey("process1");
<bean id="processEngineConfiguration"
	class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
	<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/act" />
	<property name="jdbcDriver" value="com.mysql.jdbc.Driver" />
	<property name="jdbcUsername" value="root" />
	<property name="jdbcPassword" value="123456" />
	<property name="databaseSchemaUpdate" value="true" />
	<property name="history" value="full"></property>
	<property name="mailServerHost" value="smtp.163.com"></property>
	<property name="mailServerPort" value="25"></property>
	<property name="mailServerUsername" value="163邮箱账号"></property>
	<property name="mailServerPassword" value="163邮箱密码"></property>
</bean>

5.3、Mule任务和业务规则任务

Mule是一个支持多种协议的ESB容器,ESB是企业服务总线(Enterprise Service Bus)的简称。ESB可以充当企业中各个业务系统之间的连接中枢,当一个企业拥有多个业务系统,并且这些系统需要彼此访问时,可以使用ESB作为连接中枢,降低系统与系统之间的耦合性。

目前Activiti实现的业务规则任务支持JBoss的Drools规则引擎,规则引擎是一种可以嵌入到业务系统的组件,主要用于实现业务规则的管理,并且将这些业务规则从业务系统中独立出来。Drools是JBoss下的一个著名的规则引擎。

对于Mule和Drools这两种在各自领域流行的开源框架,Activiti自然也提供了支持。在Activiti中与Mule进行交互有两种方式,第一种就是直接向Mule发送Web Service请求,第二种就是Activiti与Mule进行整合,让流程引擎也拥有Mule的功能。为了减少框架间的耦合,笔者建议直接调用Mule的服务,本书不涉及Mule的相关内容。而对Drools进行支持,可以向流程引擎中加入相应的规则文件(.drl文件),Activiti中businessRuleTask的实现,是直接调用Drools的API对规则文件进行解析并执行。

6、任务监听器

Activiti提供了任务监听器,从而允许在任务执行的过程中执行特定的Java程序或者表达式,目前任务监听器只能在User Task中使用,为BPMN2.0元素extensionElements添加activiti:taskListener元素来定义一个任务监听器。任务监听器并不属于BPMN规范的内容,属于Activiti对BPMN规范扩展的部分。

<process id="process1" name="process1">
	<startEvent id="startevent1" name="Start"></startEvent>
	<userTask id="usertask1" name="User Task">
		<extensionElements>
			<activiti:taskListener event="create"
				class="pers.zhang.listener.PropertyConfigListener" />
		</extensionElements>
	</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>
public class PropertyConfigListener implements TaskListener {
    @Override
    public void notify(DelegateTask delegateTask) {
        System.out.println("执行任务监听器");
    }
}
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = engine.getRepositoryService();
RuntimeService runtimeService = engine.getRuntimeService();
repositoryService.createDeployment().addClasspathResource("demo12/ClassTaskListener.bpmn").deploy();
// 启动流程
ProcessInstance pi = runtimeService.startProcessInstanceByKey("process1");
执行任务监听器

6.2、使用expression指定监听器

除了可以使用class属性指定监听器外,还可以使用expression属性指定监听器。对于任务监听器可以
使用同样的方式,配置相应的表达式来指定监听器的JavaBean及执行方法,这个JavaBean需要是流程变量(如果整合了Spring,也可以是Spring容器中的bean),因此还需要实现序列化接口。

<process id="process1" name="process1">
	<startEvent id="startevent1" name="Start"></startEvent>
	<userTask id="usertask1" name="User Task">
		<extensionElements>
			<activiti:taskListener event="create" expression="${myBean.testBean(task)}"/>
		</extensionElements>
	</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>
public class ExpressionBean implements Serializable {
    
    public void testBean(DelegateTask task) {
        System.out.println("执行ExpressionBean的testBean方法:" + task.getId());
    }
}
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = engine.getRepositoryService();
RuntimeService runtimeService = engine.getRuntimeService();
repositoryService.createDeployment().addClasspathResource("demo12/ExpressionTaskListener.bpmn").deploy();
// 初始化参数
Map<String, Object> vars = new HashMap<String, Object>();
vars.put("myBean", new ExpressionBean());
// 启动流程
ProcessInstance pi = runtimeService.startProcessInstanceByKey("process1", vars);
执行ExpressionBean的testBean方法:32512

6.3、使用delegateExpression指定监听器

也可以使用delegateExpression配合JUEL指定任务监听器。使用delegateExpression配合JUEL指定的监听器,必须要实现TaskListener和Serializable接口(序列化接口),如${my TaskListener}。Activiti会从流程中查找名称为“my TaskListener’”的流程变量,并直接执行notify方法。

<process id="process1" name="process1" isExecutable="true">
	<startEvent id="startevent1" name="Start"></startEvent>
	<userTask id="usertask1" name="User Task">
		<extensionElements>
			<activiti:taskListener event="create"
				delegateExpression="${myDelegate}"></activiti:taskListener>
		</extensionElements>
	</userTask>
	<endEvent id="endevent1" name="End"></endEvent>
	<sequenceFlow id="flow1" sourceRef="startevent1"
		targetRef="usertask1"></sequenceFlow>
	<sequenceFlow id="flow2" sourceRef="usertask1" targetRef="endevent1"></sequenceFlow>
</process>
public class DelegateBean implements TaskListener, Serializable {
	public void notify(DelegateTask delegateTask) {
		System.out.println("使用DelegateBean");
	}
}
// 创建流程引擎
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
// 得到流程存储服务组件
RepositoryService repositoryService = engine.getRepositoryService();
// 得到运行时服务组件
RuntimeService runtimeService = engine.getRuntimeService();
// 部署流程文件
repositoryService
		.createDeployment()
		.addClasspathResource(
				"bpmn/DelegateExpressionTaskListener.bpmn")
		.deploy();
// 初始化参数
Map<String, Object> vars = new HashMap<String, Object>();
vars.put("myDelegate", new DelegateBean());
// 启动流程
ProcessInstance pi = runtimeService.startProcessInstanceByKey(
		"process1", vars);

6.4、监听器的触发

任务监听器会在任务的不同事件中被触发,包括任务创建事件(create)、指定任务代理人事件(assignment)和任务完成事件(complete)。如果既提供了create事件的监听器,也提供了assignment事件的监听器,会先执行后者,任务创建事件(create)监听器会在任务完成创建的最后才执行,而指定任务代理人,也是属于任务创建的一部分。

<process id="process1" name="process1" isExecutable="true">
	<startEvent id="startevent1" name="Start"></startEvent>
	<userTask id="usertask1" name="User Task" activiti:assignee="crazyit">
		<extensionElements>
			<activiti:taskListener event="create"
				class="pers.zhang.listener.TaskListenerA"></activiti:taskListener>
			<activiti:taskListener event="assignment"
				class="pers.zhang.listener.TaskListenerB"></activiti:taskListener>
			<activiti:taskListener event="complete"
				class="pers.zhang.listener.TaskListenerC"></activiti:taskListener>
		</extensionElements>
	</userTask>
	<endEvent id="endevent1" name="End"></endEvent>
	<sequenceFlow id="flow1" sourceRef="startevent1"
		targetRef="usertask1"></sequenceFlow>
	<sequenceFlow id="flow2" sourceRef="usertask1" targetRef="endevent1"></sequenceFlow>
</process>
public class TaskListenerA implements TaskListener {

	public void notify(DelegateTask delegateTask) {
		System.out.println("任务监听器A");
	}

}

public class TaskListenerB implements TaskListener {

	public void notify(DelegateTask delegateTask) {
		System.out.println("任务监听器B");
	}

}

public class TaskListenerC implements TaskListener {

	public void notify(DelegateTask delegateTask) {
		System.out.println("任务监听器C");
	}

}
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = engine.getRepositoryService();
RuntimeService runtimeService = engine.getRuntimeService();
TaskService taskService = engine.getTaskService();
repositoryService.createDeployment().addClasspathResource("demo12/ListenerFire.bpmn").deploy();
// 启动流程
ProcessInstance pi = runtimeService.startProcessInstanceByKey("process1");
// 查询并完成任务
Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
taskService.complete(task.getId());
任务监听器B
任务监听器A
任务监听器C

6.5、属性注入

向任务监听器注入属性,实现方式与JavaDelegate的属性注入类似,使用activiti:field元素即可,同样支持两种注入方式:字符串注入和UEL表达式注入。使用以下的代码片断可以为一个TaskListener进行字符串注入:

<process id="process1" name="process1">
	<startEvent id="startevent1" name="Start"></startEvent>
	<userTask id="usertask1" name="User Task">
		<extensionElements>
			<activiti:taskListener event="create"
				class="org.crazyit.activiti.PropertyInjection">
				<activiti:field name="userName" stringValue="crazyit" />
			</activiti:taskListener>
		</extensionElements>
	</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>

7、流程监听器

除了可以对用户任务进行监听外,还可以对流程进行监听。如果需要在流程中执行Java逻辑或者表达式,则可以在不同的流程阶段加入流程监听器,同样,流程监听器属于Activiti对BPMN规范的扩展。对流程进行监听,与对任务监听类似,也是在extensionElements元素中加入activiti:executionListener来指定一个流程监听器。

7.1、配置流程监听器

使用activiti:executionListener元素配置一个流程监听器,与配置任务监听器类似,也可以为该元素添加class、expression或者delegateExpression属性来指定监听器类,只是任务监听器实现的是TaskListener接口,而流程监听器则实现org,activiti…engine.delegate.ExecutionListener
接口。实现ExecutionListener接口只需要实现notify方法,实现该方法,可以使用DelegateExecution实例,DelegateExecution实例实际上是一个ExecutionEntity,即执行流对象。可以使用以下的代码片断来配置流程监听器:

<extensionElements>
	<activiti:executionListener class="pers.zhang.listener.MyExecutionListener" event="start">
	</activiti:executionListener>
	<activiti:executionListener expression="${javaBean.doSomething(execution)}" event="start">
	</activiti:executionListener>
	<activiti:executionListener delegateExpression="${myDelegate}" event="start">
	</activiti:executionListener>
</extensionElements>

在以上的代码片断中,分别使用了class、expression和delegateExpression属性来指定监听器,其中使用class属性时,配置的Java类需要实现ExecutionListener接口。使用expression属性时,需要配置相应的UEL表达式,本例中UEL表达式为
“${javaBean.doSomething(execution)}”,表示会从流程变量(或者Spring容器)中查找名称为“javaBean”的变量(bean),执行doSomething方法,并传入执行流的实例,“execution”为内置的表达式变量。使用delegateExpression属性时,同样需要提供相应的JUEL表达式,本例中表达式为“${myDelegate}”,表示会到流程变量(或者Spring容器)中查找名称为“myDelegate"的变量(bean),并且该变量的类型必须为ExecutionListener。使用这3个属性指定Java类的方法与任务监听器中的指定方法类似,在此不再赘述。除配置与任务监听器类似外,流程监听器同样支持属性注入,可以使用以下的代码片断来向一个流程监听器注入属性:

<activiti:executionListener class="pers.zhang.listener.MyExecutionListener" event="end">
	<activiti:field name="info" stringValue="结束流程" />
	<activiti:expression>${javaBean}</activiti:expression>
	</activiti:field>
</activiti:executionListener>

在以上的代码片断中,为一个流程监听器注入了名称为ifo的字符串常量,注入了名称为javaBean的变量。需要注意的是,在流程监听器中,需要提供相应的setter方法,如此处需要提供setInfo(Expression e)和setResult(Expression e)方法。

7.2、触发流程监听器的事件

流程监听器会在流程的某些事件中被触发,会触发流程监听器的主要事件有:

  • 流程的开始和结束
  • 活动之间的过渡
  • 流程网关的开始
  • 中间事件的开始和结束
  • 开始事件的结束和结束事件的开始

图所示是一个含有流程网关、用户任务和中间事件的流程,该流程对应的流程文件内容如代码所示。

<process id="process1" name="process1" isExecutable="true">
	<extensionElements>
		<activiti:executionListener event="end"
			class="pers.zhang.listener.ExecutionListenerInvocation">
			<activiti:field name="message">
				<activiti:string><![CDATA[流程结束]]></activiti:string>
			</activiti:field>
		</activiti:executionListener>
		<activiti:executionListener event="start"
			class="pers.zhang.listener.ExecutionListenerInvocation">
			<activiti:field name="message">
				<activiti:string><![CDATA[流程开始]]></activiti:string>
			</activiti:field>
		</activiti:executionListener>
	</extensionElements>
	<startEvent id="startevent1" name="Start"></startEvent>
	<userTask id="usertask1" name="Task 1">
		<extensionElements>
			<activiti:executionListener event="end"
				class="pers.zhang.listener.ExecutionListenerInvocation">
				<activiti:field name="message">
					<activiti:string><![CDATA[用户任务结束]]></activiti:string>
				</activiti:field>
			</activiti:executionListener>
			<activiti:executionListener event="start"
				class="pers.zhang.listener.ExecutionListenerInvocation">
				<activiti:field name="message">
					<activiti:string><![CDATA[用户任务开始]]></activiti:string>
				</activiti:field>
			</activiti:executionListener>
		</extensionElements>
	</userTask>
	<exclusiveGateway id="exclusivegateway1" name="Exclusive Gateway">
		<extensionElements>
			<activiti:executionListener event="start"
				class="pers.zhang.listener.ExecutionListenerInvocation">
				<activiti:field name="message">
					<activiti:string><![CDATA[网关开始]]></activiti:string>
				</activiti:field>
			</activiti:executionListener>
		</extensionElements>
	</exclusiveGateway>
	<intermediateThrowEvent id="signalintermediatethrowevent1"
		name="SignalThrowEvent">
		<extensionElements>
			<activiti:executionListener event="end"
				class="pers.zhang.listener.ExecutionListenerInvocation">
				<activiti:field name="message">
					<activiti:string><![CDATA[中间事件结束]]></activiti:string>
				</activiti:field>
			</activiti:executionListener>
			<activiti:executionListener event="start"
				class="pers.zhang.listener.ExecutionListenerInvocation">
				<activiti:field name="message">
					<activiti:string><![CDATA[中间事件开始]]></activiti:string>
				</activiti:field>
			</activiti:executionListener>
		</extensionElements>
	</intermediateThrowEvent>
	<endEvent id="endevent1" name="End"></endEvent>
	<sequenceFlow id="flow1" sourceRef="startevent1"
		targetRef="exclusivegateway1">
		<extensionElements>
			<activiti:executionListener 
				class="pers.zhang.listener.ExecutionListenerInvocation">
				<activiti:field name="message" stringValue="从开始事件到网关的顺序流"/>
			</activiti:executionListener>
		</extensionElements>	
	</sequenceFlow>
	<sequenceFlow id="flow3" sourceRef="usertask1"
		targetRef="signalintermediatethrowevent1"></sequenceFlow>
	<sequenceFlow id="flow4" sourceRef="signalintermediatethrowevent1"
		targetRef="endevent1"></sequenceFlow>
	<sequenceFlow id="flow5" sourceRef="exclusivegateway1"
		targetRef="usertask1"></sequenceFlow>
</process>
public class ExecutionListenerInvocation implements ExecutionListener {

    private Expression message;

    public void setMessage(Expression message) {
        this.message = message;
    }

    public void notify(DelegateExecution execution) {
        System.out.println("流程监听器:" + message.getValue(execution));
    }

    public static void main(String[] args) {
        // 创建流程引擎
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
        // 得到流程存储服务组件
        RepositoryService repositoryService = engine.getRepositoryService();
        // 得到运行时服务组件
        RuntimeService runtimeService = engine.getRuntimeService();
        // 得到任务服务组件
        TaskService taskService = engine.getTaskService();
        // 部署流程文件
        repositoryService
                .createDeployment()
                .addClasspathResource("demo12/ExecutionListenerInvocation.bpmn")
                .deploy();
        // 启动流程
        ProcessInstance pi = runtimeService
                .startProcessInstanceByKey("process1");
        // 查找并完成任务
        Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
        taskService.complete(task.getId());
    }

}
流程监听器:流程开始
流程监听器:从开始事件到网关的顺序流
流程监听器:网关开始
流程监听器:用户任务开始
流程监听器:用户任务结束
流程监听器:中间事件开始
流程监听器:中间事件结束
流程监听器:流程结束
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐