工作流引擎的核心价值在于将业务流程中的重复性操作标准化,而ServiceTask正是实现自动化决策的关键组件。与传统UserTask不同,ServiceTask不需要人工干预,它能够在流程执行时自动调用预先定义好的业务逻辑。想象一下公司请假流程:当员工提交申请后,系统需要自动判断申请人的职级,如果是部门经理则直接跳转到总经理审批,普通员工则需要经过部门经理审批。这种智能路由正是ServiceTask的典型应用场景。
在实际项目中,我发现很多团队还在用人工审批节点做条件判断,这不仅增加了不必要的操作步骤,还容易产生人为错误。通过ServiceTask配合表达式语言,我们可以将这类业务规则抽象为可复用的自动化服务。比如判断用户角色、计算审批阈值、验证数据完整性等场景,都是ServiceTask大显身手的地方。
在.bpmn文件中配置ServiceTask时,这几个参数需要特别注意:
${beanId.methodName(execution)}这里有个实际配置示例:
xml复制<serviceTask id="judgeManagerTask"
name="判断申请人职级"
activiti:expression="${leaveService.checkPosition(execution)}"/>
我曾经遇到过表达式配置错误导致流程卡死的情况,后来总结出几个排查要点:
对应的Spring Bean实现需要遵循特定规范:
java复制@Service("leaveService")
public class LeaveApprovalService {
public void checkPosition(DelegateExecution execution) {
String applicant = (String) execution.getVariable("applicant");
String department = getDepartment(applicant);
// 判断是否部门负责人
boolean isManager = departmentManagerService.isDepartmentManager(applicant);
execution.setVariable("isManager", isManager);
// 设置下一节点审批人
if(isManager){
execution.setVariable("nextApprover", "generalManager");
}else{
execution.setVariable("nextApprover", department+"Manager");
}
}
}
这段代码展示了几个最佳实践:
流程变量是连接ServiceTask与网关决策的桥梁。在请假流程示例中,我们通过isManager变量控制流程走向。这里特别要注意变量作用域的问题:
我曾经踩过一个坑:在并行网关分支中修改了同名变量,导致流程状态混乱。后来通过给变量添加分支前缀解决了这个问题。比如:
java复制execution.setVariable("branchA_isManager", true);
让我们通过一个端到端的例子来串联所有知识点。假设要实现这样的请假流程:
对应的BPMN主要结构应该是:
code复制开始事件 → 提交申请(UserTask) → 职级判断(ServiceTask) →
[是经理?] → 排他网关 →
(是)总经理审批(UserTask)
(否)部门经理审批(UserTask)
→ 人事归档 → 结束事件
流程启动时需要初始化申请人信息:
java复制public void startLeaveProcess(String applicant) {
Map<String, Object> vars = new HashMap<>();
vars.put("applicant", applicant);
vars.put("applyTime", new Date());
runtimeService.startProcessInstanceByKey(
"smartLeaveProcess",
vars
);
}
测试用例要覆盖各种职级场景:
java复制@Test
void testManagerFlow() {
// 模拟部门经理申请
startLeaveProcess("managerZhang");
Task currentTask = taskService.createTaskQuery()
.taskAssignee("generalManager")
.singleResult();
assertNotNull("总经理应有审批任务", currentTask);
}
@Test
void testEmployeeFlow() {
// 模拟普通员工申请
startLeaveProcess("staffLi");
Task currentTask = taskService.createTaskQuery()
.taskAssignee("devManager")
.singleResult();
assertNotNull("部门经理应有审批任务", currentTask);
}
当审批量较大时,ServiceTask的实现要注意:
xml复制<serviceTask id="asyncTask"
activiti:async="true"
activiti:expression="${longRunningService.process(execution)}"/>
这是新手最常遇到的问题,表现是流程执行时报错"Unable to find bean"。可以从这几个方面检查:
有个小技巧:在测试类中注入该bean试试能否成功:
java复制@Autowired
private LeaveApprovalService leaveService; // 应该能正常注入
当发现流程变量没有按预期传递时:
java复制HistoricVariableInstance variable = historyService
.createHistoricVariableInstanceQuery()
.processInstanceId(processInstanceId)
.variableName("isManager")
.singleResult();
ServiceTask执行过程中如果抛出异常,会导致流程回滚。建议:
我曾经遇到过一个生产问题:因为邮件发送失败导致整个审批流程回滚。后来将通知类操作改为异步处理解决了这个问题。
通过表达式可以实现更灵活的调用方式:
xml复制<serviceTask activiti:expression="${beanMap[serviceName].handle(execution)}"/>
对应的Java代码:
java复制@Autowired
private Map<String, ApprovalHandler> beanMap;
// 根据流程变量决定使用哪个handler
execution.setVariable("serviceName", "financeApproval");
复杂决策可以组合多个ServiceTask:
code复制ServiceTask1(计算考勤) →
ServiceTask2(计算剩余假期) →
ServiceTask3(综合评估) →
排他网关
每个Task专注于单一职责,最后通过流程变量汇总结果。
对于特别复杂的业务规则,可以集成Drools等规则引擎:
java复制public void complexDecision(DelegateExecution execution) {
KieSession kieSession = kieContainer.newKieSession();
kieSession.insert(execution.getVariables());
kieSession.fireAllRules();
Map<String, Object> results = ...;
execution.setVariables(results);
}
这种架构将易变的业务规则从流程定义中解耦出来,当审批规则变化时只需要更新规则文件,不需要修改流程定义。