上一篇博文分析了對象解析器的原理。本文來編寫一個自定義對象解析器。考慮這樣的場景:生產中我們常常在流程的上一環節選擇下環節的處理人,然後再提交流程。之後流程運轉到下一個環節後,會在對應的處理人名下,即userTask的candidate或assignee是上環節選中的處理人。
上述的實現方式可以是這樣,對userTask添加自定義任務監聽器,該監聽器主要作用是把流程實例中的變量設置爲userTask的candidate或assignee。這樣在流程執行的時候,上一環節選擇了處理人,寫入流程變量中,待下一環節的任務監聽器把變量讀出來設置到userTask的candidate或assignee。但是在bpmn圖上爲每個userTask上添加這樣的監聽器,隨着流程和userTask增多,維護起來非常繁瑣。生產系統常常有十幾個不同的流程,每個流程數個或十數個節點,而且隨着業務變化,流程圖也要時不時進行修改,手動對每個userTask添加監聽器不現實。有沒有辦法爲任務節點統一添加監聽器呢?答案是有的!我們可以自定義一個userTask的對象解析器,在解析每個userTask時,爲其添加監聽器,這樣我們就沒必要在bpmn圖上手動添加了。不僅省時省力,而且方便統一管理。
首先我們自定義一個監聽器AssignmentTaskListener.java
public class AssignmentTaskListener implements TaskListener{
public void notify(DelegateTask delegateTask) {
String assignee = (String)delegateTask.getVariable("assignee");
delegateTask.setAssignee(assignee);
}
}
改監聽器主要作用就是在觸發的時候,把流程變量"assignee"中的值設置到userTask的assignee中。
接着我們擴展userTask的解析器,自定義MyUserTaskParseHandler.java
public class MyUserTaskParseHandler extends UserTaskParseHandler{
private List<TaskListener> taskListenerList;
@Override
protected void executeParse(BpmnParse bpmnParse, UserTask userTask) {
//由父類完成元素解析
super.executeParse(bpmnParse, userTask);
for(TaskListener taskListener : taskListenerList) {
TaskDefinition taskDefinition = (TaskDefinition)bpmnParse.getCurrentActivity().getProperty(UserTaskParseHandler.PROPERTY_TASK_DEFINITION);
taskDefinition.addTaskListener(TaskListener.EVENTNAME_CREATE, taskListener);
}
}
public List<TaskListener> getTaskListenerList() {
return taskListenerList;
}
public void setTaskListenerList(List<TaskListener> taskListenerList) {
this.taskListenerList = taskListenerList;
}
}
第8行先調用父類進行元素解析。10-12行對當前userTask添加create事件的任務監聽器。爲何不在assignment事件觸發時添加任務監聽器呢?因爲如果bpmn圖中userTask沒有設置assignee的話,流程引擎就不會觸發assignment事件,自然就不會調用任務監聽器了。
接着我們新建一個流程引擎配置文件,命名爲activitiParseHandler.cfg.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="processEngineConfiguration"
class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<property name="jdbcUrl"
value="jdbc:mysql://localhost:3306/db_activiti?useUnicode=true&&characterEncoding=utf8&serverTimezone=UTC" />
<property name="jdbcDriver" value="com.mysql.jdbc.Driver" />
<property name="jdbcUsername" value="root" />
<property name="jdbcPassword" value="" />
<property name="databaseSchemaUpdate" value="true" />
<property name="customDefaultBpmnParseHandlers">
<list>
<bean class="parseHandler.MyUserTaskParseHandler">
<property name="taskListenerList">
<list>
<bean class="parseHandler.AssignmentTaskListener"></bean>
</list>
</property>
</bean>
</list>
</property>
</bean>
</beans>
17-27爲自定義對象解析器。在初始化流程引擎時,對象解析器通過map方式管理,自定義添加的對象解析器會替換getHandledType()返回值相同的默認對象解析器。MyUserTaskParseHandler繼承了UserTaskParseHandler,getHandledType()返回的都是UserTask.class,因此前者會替換後者。20-24行通過xml注入監聽器,增加配置靈活度。
然後我們部署流程:
private ProcessEngine pe;
public void getFromProcessEngineConfiguration() {
ProcessEngineConfiguration pec = ProcessEngineConfiguration
.createProcessEngineConfigurationFromResource("activitiParseHandler.cfg.xml");
pe = pec.buildProcessEngine();
}
public void deploy() {
RepositoryService repositoryService = pe.getRepositoryService();
DeploymentBuilder deploymentBuilder = repositoryService.createDeployment();
InputStream inputStream = null;
try {
inputStream = App.class.getClassLoader().getResource("bpmn/parseHandlerBPM.bpmn").openStream();
deploymentBuilder.addInputStream("parseHandler.bpmn", inputStream);
deploymentBuilder.name("parseHandlerDeployment");
Deployment deployment = deploymentBuilder.deploy();
System.out.println("流程部署ID:" + deployment.getId());
System.out.println("流程部署名:" + deployment.getName());
} catch (IOException e) {
e.printStackTrace();
}
}
查看act_ru_procdef表:
接下來我們啓動parseHandlerBPM:1:4流程,啓動時加入流程變量:
public void startProcessById() {
RuntimeService runtimeService = pe.getRuntimeService();
Map<String, Object> var = new HashMap<String, Object>();
var.put("assignee", "張三");
ProcessInstance pi = runtimeService.startProcessInstanceById("parseHandlerBPM:1:4", var);
}
啓動成功後,此時流程應該運行到userTask1節點上,觀察act_ru_task:
ASSIGNEE屬性值爲“張三”,證明自定義對象解析器成功爲userTask1添加監聽器,並且監聽器根據流程變量爲userTask1設置了ASSIGNEE。接下來嘗試把流程提交到userTask2節點:
public void completeTask() {
TaskService taskService = pe.getTaskService();
Map<String, Object> var = new HashMap<String, Object>();
var.put("assignee", "李四");
taskService.complete("2505", var);
}
提交完成後,觀察act_ru_task:
通過截圖看到“李四”也成功設置爲userTask2的ASSIGNEE。
在企業流程應用中,設置環節處理人的方法很多,上述僅僅是一個示例。除了把下環節處理人寫到流程變量,也可以寫到數據庫表中,或者通過其他方式去實現,這裏不再細述。使用自定義對象解析器,我們除了可以實現本文提到的爲節點添加監聽器的功能外,還可以實現自定義擴展屬性的讀取。例如在bpmn圖中,我們爲某個元素添加了自定義擴展屬性。在解析元素時,讀取這些自定義擴展屬性,可以擴展元素的功能。