自jBPM创始人Tom离开之后,jBPM和Activiti就开始大相径庭,jBPM*使用新的LGPL协议,而Activiti则使用一种更为宽松的Apache License 2.0协议。不管使用jBPM还是Activiti,两者都遵循BPMN 2.0规范,都可满足项目的一般需求,相比于jBPM,Activiti使用起来不会进行太大的二次改动,但jBPM则是使用Drools较为强大的规则引擎作为后盾,至于两者谁优谁劣,需要在实际项目中权衡利弊。
BPMN 2.0
BPMN最初由业务流程倡议组织(BPMI)定案,现在BPMI并入到OMG(Object Management Group)了,则由OMG建立规范和维护。
BPMN 2.0正式更名为(Business Process Model And Notation)业务流程符号和模型,也有人继续称呼为业务流程建模标记法(Business Process Modeling Notaion),不过无所谓,不管是jBPM、Activiti还是国人开发的FixFlow,都遵循BPMN规范。
Maven配置
JBoss的开源框架都是比较庞大的,不过相对Activiti体积要小一点。下面为配置的依赖项。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
<!--Junit-->
< dependency >
< groupId >junit</ groupId >
< artifactId >junit</ artifactId >
< version >${junit.version}</ version >
</ dependency >
<!--activiti-->
< dependency >
< groupId >org.activiti</ groupId >
< artifactId >activiti-engine</ artifactId >
< version >${activiti.version}</ version >
</ dependency >
< dependency >
< groupId >org.activiti</ groupId >
< artifactId >activiti-bpmn-layout</ artifactId >
< version >${activiti.version}</ version >
</ dependency >
< dependency >
< groupId >org.activiti</ groupId >
< artifactId >activiti-spring</ artifactId >
< version >${activiti.version}</ version >
</ dependency >
<!--apache组件-->
...
|
整合数据库
为什么要整合数据库?如果不整合数据库,我们大可以使用Quartz这些框架来做流程任务。实际上,Work Flow是用于一种长周期的、几乎异步的项目运行环境中,并且我们时刻需要将工作流程的状态记录下来,就是一种既注重结果,又注重过程的事务中,因此,整合数据库很有必要。
下面为配置源数据的XML文件,并且将databaseSchemaUpdate属性配置为drop-create,即在运行前删除原有的数据内容。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<? 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 = "databaseSchemaUpdate"
value = "drop-create" />
< property
name = "jdbcUrl"
value = "jdbc:mysql://localhost:3306/db_activiti?useUnicode=true&characterEncoding=utf-8" />
< property
name = "jdbcDriver"
value = "com.mysql.jdbc.Driver" />
< property
name = "jdbcUsername"
value = "root" />
< property
name = "jdbcPassword"
value = "****" />
< property
name = "jobExecutorActivate"
value = "true" />
</ bean >
</ beans >
|
默认配置文件名为activiti.cfg.xml,可以在源文件中找到。
另外,如果有必要,请将MySQL设置为区分大小写,即lower_case_table_names = 0。数据内容会在运行前自动创建,详细表结构内容可参考官网完整信息。
创建工作流文件
由于BPMN规范的作用,一些高级的IDE会自动识别后缀为*.bpmn的文件,不过这些都无所谓,bpmn文件实际上就是XML文件,只是加上了一些图形的标记,如width、height、x和y的坐标,下面为一个招聘面试流程,只包含流程节点,不包含位置标记节点。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
<? xml
version = "1.0"
encoding = "UTF-8" ?>
< definitions
xmlns = "http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti = "http://activiti.org/bpmn"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
targetNamespace = "Examples"
xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL
http://www.omg.org/spec/BPMN/2.0/20100501/BPMN20.xsd">
< process
id = "Interview"
name = "某公司2012年实习生招聘流程" >
< documentation >招聘工作流程</ documentation >
< startEvent
id = "start"
name = "实习生招聘启动" />
< userTask
id = "bishi"
name = "笔试"
activiti:candidateGroups = "人力资源部" />
< sequenceFlow
id = "flow1"
name = ""
sourceRef = "start"
targetRef = "bishi" />
< userTask
id = "yimian"
name = "技术一面"
activiti:candidateGroups = "技术部" />
< sequenceFlow
id = "flow2"
name = ""
sourceRef = "bishi"
targetRef = "yimian" />
< userTask
id = "ermian"
name = "技术二面"
activiti:candidateGroups = "技术部" />
< sequenceFlow
id = "flow3"
name = ""
sourceRef = "yimian"
targetRef = "ermian" />
< userTask
id = "hrmian"
name = "HR面"
activiti:candidateGroups = "人力资源部" />
< sequenceFlow
id = "flow4"
name = ""
sourceRef = "ermian"
targetRef = "hrmian" />
< userTask
id = "luyong"
name = "录用,发放Offer"
activiti:candidateGroups = "人力资源部" />
< sequenceFlow
id = "flow5"
name = ""
sourceRef = "hrmian"
targetRef = "luyong" />
< endEvent
id = "end"
name = "实习生招聘结束" />
< sequenceFlow
id = "flow6"
name = ""
sourceRef = "luyong"
targetRef = "end" />
</ process >
</ definitions >
|
为了便于阅读,一些高级IDE可以转化为图形符号,如下图:
测试运行
有了流程引擎的配置文件和流程文件后,就可以编写代码启动流程引擎并加载该流程文件了。测试清单如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
|
@Test
public
void
processTests(){
//
加载配置文件
ProcessEngine
processEngine = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource( "activiti.cfg.xml" ).buildProcessEngine();
RepositoryService
repositoryService = processEngine.getRepositoryService();
RuntimeService
runtimeService = processEngine.getRuntimeService();
repositoryService.createDeployment().addClasspathResource( "Interview.bpmn" ).deploy();
String
processId = runtimeService.startProcessInstanceByKey( "Interview" ).getId();
TaskService
taskService = processEngine.getTaskService();
//得到笔试的流程
System.out.println( "\n***************笔试流程开始***************" );
List<Task>
tasks = taskService.createTaskQuery().taskCandidateGroup( "人力资源部" ).list();
for
(Task task : tasks) {
System.out.println( "人力资源部的任务:name:" +task.getName()+ ",id:" +task.getId());
taskService.claim(task.getId(),
"张三" );
}
System.out.println( "张三的任务数量:" +taskService.createTaskQuery().taskAssignee( "张三" ).count());
tasks
= taskService.createTaskQuery().taskAssignee( "张三" ).list();
for
(Task task : tasks) {
System.out.println( "张三的任务:name:" +task.getName()+ ",id:" +task.getId());
taskService.complete(task.getId());
}
System.out.println( "张三的任务数量:" +taskService.createTaskQuery().taskAssignee( "张三" ).count());
System.out.println( "***************笔试流程结束***************" );
System.out.println( "\n***************一面流程开始***************" );
tasks
= taskService.createTaskQuery().taskCandidateGroup( "技术部" ).list();
for
(Task task : tasks) {
System.out.println( "技术部的任务:name:" +task.getName()+ ",id:" +task.getId());
taskService.claim(task.getId(),
"李四" );
}
System.out.println( "李四的任务数量:" +taskService.createTaskQuery().taskAssignee( "李四" ).count());
for
(Task task : tasks) {
System.out.println( "李四的任务:name:" +task.getName()+ ",id:" +task.getId());
taskService.complete(task.getId());
}
System.out.println( "李四的任务数量:" +taskService.createTaskQuery().taskAssignee( "李四" ).count());
System.out.println( "***************一面流程结束***************" );
System.out.println( "\n***************二面流程开始***************" );
tasks
= taskService.createTaskQuery().taskCandidateGroup( "技术部" ).list();
for
(Task task : tasks) {
System.out.println( "技术部的任务:name:" +task.getName()+ ",id:" +task.getId());
taskService.claim(task.getId(),
"李四" );
}
System.out.println( "李四的任务数量:" +taskService.createTaskQuery().taskAssignee( "李四" ).count());
for
(Task task : tasks) {
System.out.println( "李四的任务:name:" +task.getName()+ ",id:" +task.getId());
taskService.complete(task.getId());
}
System.out.println( "李四的任务数量:" +taskService.createTaskQuery().taskAssignee( "李四" ).count());
System.out.println( "***************二面流程结束***************" );
System.out.println( "***************HR面流程开始***************" );
tasks
= taskService.createTaskQuery().taskCandidateGroup( "人力资源部" ).list();
for
(Task task : tasks) {
System.out.println( "技术部的任务:name:" +task.getName()+ ",id:" +task.getId());
taskService.claim(task.getId(),
"李四" );
}
System.out.println( "李四的任务数量:" +taskService.createTaskQuery().taskAssignee( "李四" ).count());
for
(Task task : tasks) {
System.out.println( "李四的任务:name:" +task.getName()+ ",id:" +task.getId());
taskService.complete(task.getId());
}
System.out.println( "李四的任务数量:" +taskService.createTaskQuery().taskAssignee( "李四" ).count());
System.out.println( "***************HR面流程结束***************" );
System.out.println( "\n***************录用流程开始***************" );
tasks
= taskService.createTaskQuery().taskCandidateGroup( "人力资源部" ).list();
for
(Task task : tasks) {
System.out.println( "技术部的任务:name:" +task.getName()+ ",id:" +task.getId());
taskService.claim(task.getId(),
"李四" );
}
System.out.println( "李四的任务数量:" +taskService.createTaskQuery().taskAssignee( "李四" ).count());
for
(Task task : tasks) {
System.out.println( "李四的任务:name:" +task.getName()+ ",id:" +task.getId());
taskService.complete(task.getId());
}
System.out.println( "李四的任务数量:" +taskService.createTaskQuery().taskAssignee( "李四" ).count());
System.out.println( "***************录用流程结束***************" );
HistoryService
historyService = processEngine.getHistoryService();
HistoricProcessInstance
historicProcessInstance = historyService
.createHistoricProcessInstanceQuery()
.processInstanceId(processId).singleResult();
System.out.println( "\n流程结束时间:" +historicProcessInstance.getEndTime());
}
|
代码清单中使用 ProcessEngines类加载默认的流程配置文件(activiti.cfg.xml),再获取各个服务组件的实例。 RepositoryService主要用于管理流程的资源, RuntimeService主要用于流程运行时的流程管理,TaskService主要用于管理流程任务。最后, HistoricProcessInstance会将工作的流程历史记录下来。