jBPM4 能够很好地支持多种自动活动,所谓自动活动指的是在执行过程中完全无须人工干预。
jBPM4 默认支持的自动活动类型有:
* java - Java 程序活动
* script - 脚本活动
* hql - Hibernate 查询语言活动
* SQL - SQL 查询语言活动
* mail - 邮件活动
1 Java 程序活动
java 活动可以指定一个 Java 类的方法,当流程执行到此活动时,便会自动执行此 Java 方法 。
java 活动的属性:
属性 | 类型 | 默认值 | 是否必需 | 描述 |
---|---|---|---|---|
class | 类名字符串 | 无 | class 或 expr 二者必选其一 | 带有包路径的完整 Java 类名 。 此 Java 类会被 “用户代码类加载器” 加载到 JVM。此类的对象在活动执行时被 “ 延迟创建 ” (注意:需要提供无参构造方法),即不随 jBPM 工作流引擎启动而创建 。 当这些对象创建后,将作为流程定义的一部分被缓存 。 |
expr | 表达式字符串 | 无 | class 或 expr 二者必选其一 | 此表达式返回一个 Java 类对象,必须包含 下面 method 属性指定的方法。 |
method | 方法名字符串 | 无 | 必需 | 调用的方法名称。 |
var | 流程变量名字符串 | 无 | 可选 | 存储方法返回结果的流程变量名称。 |
java 活动支持的元素:
元素 | 数目 | 描述 |
---|---|---|
field | 0..* | 在方法被调用之前给指定的类成员域注入指定的值。 |
arg | 0..* | 给被调用的方法提供参数。 |
jPDL:
<?xml version="1.0" encoding="UTF-8"?>
<process name="Java" xmlns="http://jbpm.org/4.4/jpdl">
<start g="197,164,48,48" name="start1">
<transition g="-52,-22" name=" " to="问候"/>
</start>
<!-- 定义 java 活动,调用类对象的方法,并把结果存储在流程实例的 answer 变量中-->
<java class="net.deniro.jbpm.java.Deniro" g="321,159,92,52" method="hello" name="问候" var="answer">
<!-- 为调用的方法提供参数 -->
<arg><string value="早,饭吃了吗?"/></arg>
<!-- 为成员域注入值 -->
<field name="state"><string value="吃咯"/></field>
<transition to="握手"/>
</java>
<!-- 定义 java 活动,调用类对象(通过表达式调用流程变量对象)的方法,并把结果存储在流程实例的 hand 变量中-->
<java expr="#{hand}" g="466,158,92,52" method="shake" name="握手" var="hand">
<!-- 使用表达式引用流程变量,为 shake 方法提供参数(按顺序) -->
<arg><object expr="#{lily.handshakes.force}"/></arg>
<arg><object expr="#{lily.handshakes.duration}"/></arg>
<transition to="等待"/>
</java>
<state g="619,160,92,52" name="等待"/>
</process>
在【问候】活动中,类 net.deniro.jbpm.java.Deniro 会被实例化为一个对象,然后调用 hello 方法,并将调用返回的对象存入名为 answer 的流程变量中。
下面是类 Deniro 的代码:
public class Deniro {
/**
* 在流程定义中注入值
*/
String state;
public String hello(String msg) {
if (msg.indexOf("早,饭吃了吗?") != -1) {
return "我已经" + state + ",你呢?";
} else {
return null;
}
}
}
接着会调用第二个 Java 活动(握手),流程会处理 #{hand}
表达式,根据此表达式获取名为 hand 的流程变量对象,这个对象具有名为 shake 的方法,下面是类 Hand 的代码:
public class Hand implements Serializable {
private boolean isShaken;
public Hand shake(Integer force, Integer duration) {
if (force > 3 && duration > 7) {
isShaken = true;
}
return this;
}
public boolean isShaken() {
return isShaken;
}
}
要调用的 shake 方法需要两个参数,这两个参数被定义为表达式 #{lily.handshakes.force}
与 #{lily.handshakes.duration}
,这里的 lily 是 Lily 类的实例,它具有类型为 Map 名为 handshakes 的成员对象,这个成员对象有两个键,一个名为 force,另一个名为 duration:
public class Lily implements Serializable {
static Map<String,Integer> handshakes=new HashMap<>();
{
handshakes.put("force",5);
handshakes.put("duration",12);
}
/**
* 可以在 EL 表达式中引用(#{lily.handshakes})
* @return
*/
public Map<String,Integer> getHandshakes(){
return handshakes;
}
}
测试代码:
//设置流程变量
Map<String, Object> variables = new HashMap<>();
variables.put("hand", new Hand());
variables.put("lily", new Lily());
//发起实例
ProcessInstance processInstance = executionService.startProcessInstanceByKey
("Java", variables);
String pId = processInstance.getId();
//获取流程变量 answer
String answer = (String) executionService.getVariable(pId, "answer");
//断言 Java 活动 greet 的结果
assertEquals("我已经吃咯,你呢?", answer);
//获取流程变量 hand
Hand hand = (Hand) executionService.getVariable(pId, "hand");
assertTrue(hand.isShaken());
2 script 脚本活动
我们可以在 script 活动中定义一段 EL 表达式脚本,工作流引擎执行到此活动时就会解析这段脚本 。 不仅是 EL 表达式,任何一种符合 JSR-223 规范的脚本语言都可以在这里使用,这样做的前提是在配置文件 jbpm.default.scriptmanager.xml 中定义要使用的脚本语言。
jBPM4 默认的脚本语言是 juel ,即 EL 表达式 。
2.1 脚本表达式(script expression)方式
script 活动的属性:
属性 | 类型 | 默认值 | 是否必需 | 描述 |
---|---|---|---|---|
expr | 字符串 | 无 | 必需 | 需要执行的脚本表达式 |
lang | 字符串 | EL | 可选 | 脚本语言 |
var | 字符串 | 无 | 可选 | 流程变量,用于存放脚本执行后的返回值 |
jPDL:
<?xml version="1.0" encoding="UTF-8"?>
<process name="ScriptExpression" xmlns="http://jbpm.org/4.4/jpdl">
<start g="216,232,48,48" name="start1">
<transition to="触发脚本"/>
</start>
<!-- 脚本表达式返回一个字符串值,这个值被存储在名为 text 的流程变量中-->
<script expr="发送包裹给 #{order.owner}" g="336,229,104,52" name="触发脚本" var="text">
<transition to="等待"/>
</script>
<state g="486,232,92,52" name="等待"/>
</process>
流程定义中,我们引用了 Order 类:
public class Order implements Serializable{
/**
* 用户 ID
*/
String owner;
public Order(String owner) {
this.owner = owner;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
}
测试代码:
Map<String,Object> variables=new HashMap<>();
variables.put("order",new Order("Deniro"));
//发起实例
ProcessInstance processInstance = executionService.startProcessInstanceByKey
("ScriptExpression",variables);
String pId=processInstance.getId();
//获取流程变量 text
String text=(String) executionService.getVariable(pId,"text");
assertTextPresent("发送包裹给 Deniro",text);
2.2 脚本文本(script text)方式
如果脚本很长,可能有多行,那么建议使用脚本文本的方式来定义脚本。
script 活动中 text 元素的属性:
属性 | 类型 | 默认值 | 是否必需 | 描述 |
---|---|---|---|---|
lang | 字符串 | EL | 可选 | 脚本语言 |
var | 字符串 | 无 | 可选 | 流程变量,用于存放脚本执行后的返回值 |
text 元素的内容:
元素 | 个数 | 描述 |
---|---|---|
文本 | 1 | 脚本文本,支持多行 |
jPDL:
<?xml version="1.0" encoding="UTF-8"?>
<process name="ScriptText" xmlns="http://jbpm.org/4.4/jpdl">
<start g="215,232,48,48" name="start1">
<transition to="触发脚本"/>
</start>
<script g="336,229,104,52" name="触发脚本" var="text">
<text>发送包裹给 #{order.owner}</text>
<!-- text 元素可以编写多行脚本 -->
<transition to="等待"/>
</script>
<state g="486,232,92,52" name="等待"/>
</process>
测试代码:
Map<String,Object> variables=new HashMap<>();
variables.put("order",new Order("Deniro"));
//发起实例
ProcessInstance processInstance = executionService.startProcessInstanceByKey
("ScriptText",variables);
String pId=processInstance.getId();
//获取流程变量 text
String text=(String) executionService.getVariable(pId,"text");
assertTextPresent("发送包裹给 Deniro",text);
3 hql (Hibernate 查询语言)活动
有时候需要直接从持久化层读取流程数据,因为 jBPM4 的持久化层是基于 Hibernate 框架实现的,所以可以直接使用 hql 活动,执行 HQL 查询语句,并将返回结果保存到流程变量中。
hql 活动的属性:
属性 | 类型 | 默认值 | 是否必需 | 描述 |
---|---|---|---|---|
var | 字符串 | 无 | 可选 | 存储 HQL 执行结果的流程变量名。 |
unique | true/false | false | 可选 | 为 true 时,在查询结果上调用 uniqueResult() 方法获得唯一结果集;为 false 时,即查询结果调用 list() 方法得到结果集列表 。 |
hql 活动的元素:
元素 | 数目 | 描述 |
---|---|---|
query | 1 | HQL 查询的语句 |
parameter | 0..* | HQL 查询语句的外部参数。 |
jPDL:
<?xml version="1.0" encoding="UTF-8"?>
<process name="Hql" xmlns="http://jbpm.org/4.4/jpdl">
<start g="197,185,48,48" name="start1">
<transition to="获取任务名称"/>
</start>
<!-- HQL 活动,查询名称中含有字母 “i" 的任务名称 -->
<hql g="308,180,116,52" name="获取任务名称" var="tasknames with i">
<query>select task.name from org.jbpm.pvm.internal.task.TaskImpl as task
where task.name like :taskName
</query>
<!-- 定义 HQL 语句的输入参数值,这将替换 HQL 语句中的 taskName 部分-->
<parameters>
<string name="taskName" value="%i%"/>
</parameters>
<transition to="统计任务数"/>
</hql>
<!-- HQL 活动,unique 设置为 true -->
<hql g="461,180,92,52" name="统计任务数" unique="true" var="tasks">
<query>select count(*)
from org.jbpm.pvm.internal.task.TaskImpl
</query>
<transition to="等待"/>
</hql>
<state g="622,184,92,52" name="等待"/>
</process>
测试代码:
//发起实例
ProcessInstance processInstance = executionService.startProcessInstanceByKey
("Hql");
String pId = processInstance.getId();
//获取第一个 hql 活动的执行结果
Collection<String> taskNames = (Collection<String>) executionService.getVariable
(pId, "tasknames with i");
taskNames = new HashSet<>(taskNames);
Set<String> expectedTaskNames = new HashSet<>();
// expectedTaskNames.add("活动名称");
assertEquals(expectedTaskNames, taskNames);//验证第一个活动
Object activities = executionService.getVariable(pId, "tasks");
assertEquals("0", activities.toString());
4 sql (SQL 查询语言)活动
sql 活动与 hql 活动的属性与元素基本相同,只不过在 sql 活动中的 query 元素的内容填入的是 SQL 语句。
强调一下,在 sql 活动与 hql 活动中,它们都有以下的特性:
* 查询获取的数据库结果集(RecorderSet)是 java.lang.Object[] 的对象数组形式。
* 查询获取的数据库结果集列表是 java.util.Collection
<?xml version="1.0" encoding="UTF-8"?>
<process name="Sql" xmlns="http://jbpm.org/4.4/jpdl">
<start g="397,138,48,48" name="start1">
<transition to="获取任务对象列表"/>
</start>
<!-- sql 活动;从任务表中获取记录集的 5 个属性 -->
<sql g="487,134,137,52" name="获取任务对象列表" var="taskObjects">
<query>
select DBID_,NAME_,STATE_,PRIORITY_,DUEDATE_ from JBPM4_TASK
</query>
<transition to="等待"/>
</sql>
<state g="654,136,92,52" name="等待"/>
</process>
测试代码:
//发起实例
ProcessInstance processInstance = executionService.startProcessInstanceByKey
("Sql");
String pId=processInstance.getId();
//获取查询结果
Collection<Object[]> taskObjects=( Collection<Object[]>)executionService
.getVariable(pId,"taskObjects");
assertEquals(0,taskObjects.size());
mail 活动(邮件)
通过 mail 活动,我们可以发送邮件给一个或多个邮件地址。可以在定义模板中指定邮件的内容。模板可以在 mail 活动的元素中指定,也可以在 jBPM 的配置文件(jbpm.mail.templates.xml)中的 process-engine-context → mail-template 元素中定义。
mail 活动的独有属性:
属性 | 类型 | 默认值 | 是否必需 | 描述 |
---|---|---|---|---|
template | 字符串 | 无 | 否 | 配置文件中 mail-template 元素的名称,即邮件模板。如果找不到,就使用 mail 活动的 text 或 html 元素内容作为邮件的内容。 |
mail 活动的元素:
元素 | 数目 | 描述 |
---|---|---|
from | 0..1 | 发件人列表 |
to | 1 | 收件人列表 |
cc | 0..1 | 抄送收件人列表 |
bcc | 0..1 | 密送收件人列表 |
subject | 1 | 邮件标题 |
text | 0..1 | 邮件内容,纯文本格式 |
html | 0..1 | 邮件内容,HTML 格式 |
attachments | 0..1 | 邮件附件,可以引用 URL 、classpath 以及本地文件资源 |
注意:jBPM4 的 eclipse 插件没有提供 mail 活动的组件,我们可以直接定义在 jPDL 中:
jPDL:
<?xml version="1.0" encoding="UTF-8"?>
<process name="sendMail" xmlns="http://jbpm.org/4.4/jpdl">
<start g="201,197,48,48" name="start1">
<transition to="发送邮件"/>
</start>
<mail g="307,198,80,40" name="发送邮件">
<!-- 收件人-->
<to addresses="lisq037@163.com"></to>
<!-- 抄送收件人(user 指定用户;groups 指定群组)-->
<cc users="lily@163.com" groups="group"></cc>
<!-- 密送收件人-->
<bcc groups="group"></bcc>
<!-- 邮件标题-->
<subject>${account} 确认函</subject>
<!-- 纯文本-->
<!--<text>${account} ${date} 约定于 xx 日,进行会议商讨,感谢您的支持。</text>-->
<!-- html 格式-->
<html>
<table>
<tr>
<td>${account}</td>
</tr>
</table>
</html>
<!-- 指定附件-->
<attachments>
<!-- web URL-->
<attachment url="https://www.baidu.com/img/bd_logo1.png"/>
<!-- classpath-->
<attachment resource="xxx/xxx.zip"/>
<!-- 本地文件系统-->
<attachment file="c:/xxx.zip"/>
</attachments>
<transition to="结束"/>
</mail>
<state name="结束" g="432,193,92,52"/>
</process>
邮件服务器地址配置在 jbpm.mail.properties 文件中,它有这些属性:
属性 | 描述 |
---|---|
mail.smtp.host | smtp 邮件服务器地址 |
mail.smtp.port | smtp 邮件服务器端口 |
mail.from | 默认的邮件发送人地址 |