本文節選自《瘋狂工作流講義(第2版)》
京東購買地址:https://item.jd.com/12246565.html
工作流Activiti6電子書:http://blog.csdn.net/boxiong86/article/details/78488562
工作流Activiti6教學視頻:http://blog.csdn.net/boxiong86/article/details/78608585
Activiti與Drools整合
使用Activiti中的業務規則任務(Business Rule Task)可以執行一個或者多個業務規則,當前Activiti只支持Drools。根據流程任務章節可知,每個流程活動都會有自己的行爲,那麼Activiti在實例業務規則任務行爲的時候,只需要使用Drools的API,就可以實現規則文件的加載、事實實例的插入和規則觸發等操作,任務的定義者只需要提供參數、規則和計算結果等信息,就可以在Activiti中調用規則。
業務規則任務詳解
在調用規則前,需要告訴規則引擎加載哪些規則文件,而對於Activiti來說,這些文件都會被看作資源(數據被保存在ACT_GE_BYTEARRAY表中),因此在部署流程資源文件時,就需要提供這些規則文件。當執行流到達業務規則任務時,就會執行業務規則任務的行爲,Activiti中對應的行爲實現類是BusinessRuleTaskActivityBehavior,那麼根據本章前面幾節中Drools的API可以知道,這個類的實現應該是創建(獲取緩存中的)KnowledgeBase實例,然後創建一個StatefulKnowledgeSession實例,插入事實實例,最後調用fireAllRules方法觸發規則。BusinessRuleTaskActivityBehavior的實現大致如代碼清單14-26所示。
代碼清單14-26:
//創建一個KnowledgeBuilder
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory
.newKnowledgeBuilder();
//添加規則資源到KnowledgeBuilder
kbuilder.add(ResourceFactory.newClassPathResource("rule/MyDrools.drl",
FirstTest.class), ResourceType.DRL);
if (kbuilder.hasErrors()) {
System.out.println(kbuilder.getErrors().toString());
System.exit(0);
}
//獲取知識包集合
Collection<KnowledgePackage> pkgs = kbuilder
.getKnowledgePackages();
//創建KnowledgeBase實例
KnowledgeBase kbase =kbuilder.newKnowledgeBase();①
//將知識包部署到KnowledgeBase中
kbase.addKnowledgePackages(pkgs);
//使用KnowledgeBase創建StatefulKnowledgeSession
StatefulKnowledgeSession ksession = kbase
.newStatefulKnowledgeSession();
//創建事實
Person p1 = newPerson("person 1", 11);
//插入到Working Memory
ksession.insert(p1);
//匹配規則
ksession.fireAllRules();
//關閉當前session的資源
ksession.dispose();
從代碼清單14-26的①開始,將會是BusinessRuleTaskActivityBehavior所做的工作,Activiti的實現與代碼清單14-26存在差異,KnowledgeBase實例的創建將由Activiti的其他類完成,包括KnowledgeBuilder的創建、編譯信息輸出等工作,BusinessRuleTaskActivityBehavior的實現中,得到KnowledgeBase後,會創建一個StatefulKnowledgeSession,然後根據任務節點的配置,解析爲事實實例,調用StatefulKnowledgeSession的insert方法插入到Working Memory中,最後會觸發全部的規則並關閉資源。需要注意的是,觸發規則時,會讀取任務所配置的規則來添加一個規則攔截器,調用StatefulKnowledgeSession的fireAllRules(AgendaFilter filte)方法來觸發規則,如果在任務中沒有配置使用(或者不使用)的規則,那麼將調用無參數的fireAllRules方法。在接下來的兩個小節,將以一個銷售流程爲基礎,在Activiti中調用規則。
制定銷售單優惠規則
假設當前有一個銷售流程,銷售人員在錄入銷售商品後,系統需要對錄入的商品進行規則處理,例如在單筆消費100元以上打九折、200元以上打八折等優惠策略,都可以在規則文件中定義,然後通過業務規則任務的調用,最後通過一個ServiceTask來輸出計算後的結果。在設定銷售流程前,可以先設計相應的銷售對象。代碼清單14-27爲一個銷售單對象和一個銷售單明細對象。
代碼清單14-27:
codes\14\14.7\drools-sale\src\org\crazyit\activiti\Sale.java,
codes\14\14.7\drools-sale\src\org\crazyit\activiti\SaleItem.java
// 銷售單對象
public class Saleimplements Serializable {
//銷售單號
private String saleCode;
//銷售日期
private Date date;
//銷售明細
private List<SaleItem> items;
//折扣
private BigDecimaldiscount = new BigDecimal(1);
public Sale(String saleCode, Date date) {
super();
this.saleCode = saleCode;
this.date = date;
this.items = new ArrayList<SaleItem>();
}
//返回日期爲星期幾
public int getDayOfWeek() {
Calendar c = Calendar.getInstance();
c.setTime(this.date);
int dow = c.get(Calendar.DAY_OF_WEEK);
return dow;
}
//返回該銷售單的總金額(優惠前)
public BigDecimal getTotal() {
BigDecimal total = new BigDecimal(0);
for (SaleItem item : this.items) {
BigDecimal itemTotal = item.getPrice().multiply(item.getAmount());
total = total.add(itemTotal);
}
total = total.setScale(2, BigDecimal.ROUND_HALF_UP);
return total;
}
//返回優惠後的總金額
public BigDecimal getDiscountTotal() {
BigDecimal total = getTotal();
total = total.multiply(this.discount).setScale(2, BigDecimal.ROUND_HALF_UP);
return total;
}
public void setDiscount(BigDecimal dicsount) {
this.discount = dicsount.setScale(2, BigDecimal.ROUND_HALF_UP);
}
public BigDecimal getDiscount() {
return this.discount;
}
...省略setter和getter方法
}
// 銷售明細
public class SaleItemimplements Serializable {
//商品名稱
private String goodsName;
//商品單價
private BigDecimal price;
//數量
private BigDecimal amount;
public SaleItem(String goodsName, BigDecimal price, BigDecimal amount) {
super();
this.goodsName = goodsName;
this.price = price;
this.amount = amount;
}
...省略setter和getter方法
}
代碼清單14-27中的Sale對象,表示在銷售過程中產生的一筆交易,一張銷售單中有多個銷售明細,每個明細表示所銷售的商品信息,包括商品名稱、單價和數量。在代碼清單14-27中,Sale對象提供了getDayOfWeek和getTotal方法,用於返回銷售單日期是星期幾和銷售單總金額,這兩個方法將會被規則的條件所調用,判斷是否符合規則觸發的條件,Sale對象中的getDiscountTotal方法,用於返回優惠後銷售單的總金額,這個方法將會用於顯示結果值。銷售單中有一個discount的屬性,用來標識銷售單的打折情況。
編寫規則文件
設計完事實對象後,就可以制定各種銷售規則,只需要按照具體的業務和Drools的語法來制定規則。假設需要滿足以下的銷售規則:每週六和週日,全部商品打九折;消費滿100打八折,滿200打七折。根據該業務,設定的Drools規則如代碼清單14-28所示。
代碼清單14-28:codes\14\14.7\drools-sale\resource\rule\Sale.drl
package org.crazyit.activiti;
import java.util.*;
import java.math.*;
// 週六週日打九折
rule "Sat. and Sun. 90%"
no-loop true
lock-on-active true
salience1
when
$s : Sale(getDayOfWeek() == 1 || getDayOfWeek() == 7)
then
$s.setDiscount(new BigDecimal(0.9));
update($s);
end
// 100元打八折
rule "100 80%"
no-loop true
lock-on-active true
salience 2
when
$s : Sale(getTotal() >= 100)
then
$s.setDiscount(new BigDecimal(0.8));
update($s);
end
// 200元打七折
rule "200 70%"
no-loop true
lock-on-active true
salience3
when
$s : Sale(getTotal() >= 200)
then
$s.setDiscount(new BigDecimal(0.7));
update($s);
end
代碼清單14-28中定義了三個規則,這三個規則都設置了no-loop和lock-on-active屬性爲true,表示一個規則被觸發後,其他規則(包括自身)將不會被再次觸發,三個規則中均設置了規則的優先級,200元打七折的優先級最高,週六週日打九折的規則優先級最低,如果一筆銷售發生在週六日,同時也滿200元的話,這時只會觸發“200元打七折”的業務規則。代碼清單中的三個規則,符合條件後,均會調用Sale的setDiscount方法設置銷售單的折扣屬性。
實現銷售流程
制定了銷售規則後,就可以在Activiti中設計銷售流程,本例的銷售流程較爲簡單,在銷售員錄入銷售數據後(使用User Task),將數據交給業務規則任務(Business Rule Task)進行處理,最後使用一個簡單的Service Task進行輸出,流程結束,當然,在實際應用的過程中,會有更復雜的後續流程,但並不是本例的重點。本例設計的銷售流程如圖14-3所示,對應的流程文件內容爲代碼清單14-28。
圖14-3銷售流程
代碼清單14-30:codes\14\14.7\drools-sale\resource\bpmn\SaleRule.bpmn
<process id="process1" name="process1">
<startEvent id="startevent1" name="Start"></startEvent>
<businessRuleTask id="businessruletask1" name="進行優惠策略應用"
activiti:ruleVariablesInput="${sale1}, ${sale2}, ${sale3}, ${sale4}"
activiti:resultVariable="saleResults"></businessRuleTask>
…省略其他元素
</process>
代碼清單14-30的粗體字代碼,使用了businessRuleTask,該任務中會以四個流程參數(sale1到sale4)作爲規則事實,交給規則引擎進行處理,最終返回結果的名稱爲“saleResults”,結果類型是一個集合。本例中的四個Sale流程參數,爲代碼清單14-25中的Sale對象,需要匹配的規則爲代碼清單14-26的規則(週六日打九折、100元以上打八折、200元以上打七折)。爲了讓流程引擎能加載規則文件(drl),需要在資源部署時將規則文件一併部署到流程引擎中,流程的部署以及運行,如代碼清單14-31所示。
代碼清單14-31:codes\14\14.7\drools-sale\src\org\crazyit\activiti\SaleProcess.java
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("rule/Sale.drl")
.addClasspathResource("bpmn/SaleRule.bpmn").deploy();
ProcessInstance pi = runtimeService
.startProcessInstanceByKey("process1");
//創建事實實例,符合週六日打九折條件
Sale s1 = new Sale("001", createDate("2017-07-01"));①
SaleItem s1Item1 = new SaleItem("礦泉水", new BigDecimal(5),
new BigDecimal(4));
s1.addItem(s1Item1);
//滿100打八折
Sale s2 = new Sale("002", createDate("2017-07-03"));②
SaleItem s2Item1 = new SaleItem("爆米花", new BigDecimal(20),
new BigDecimal(5));
s2.addItem(s2Item1);
//滿200打七折
Sale s3 = new Sale("003", createDate("2017-07-03"));③
SaleItem s3Item1 = new SaleItem("可樂一箱", new BigDecimal(70), new BigDecimal(3));
s3.addItem(s3Item1);
//星期天滿200
Sale s4 = new Sale("004", createDate("2017-07-02"));④
SaleItem s4Item1 = new SaleItem("爆米花一箱", new BigDecimal(80), new BigDecimal(3));
s4.addItem(s4Item1);
Map<String, Object> vars = new HashMap<String, Object>();
vars.put("sale1", s1);
vars.put("sale2", s2);
vars.put("sale3", s3);
vars.put("sale4", s4);
//查找任務
Task task = taskService.createTaskQuery().processInstanceId(pi.getId())
.singleResult();
taskService.complete(task.getId(), vars);
}
static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
//根據字符串創建日期對象
static Date createDate(String date) {
try {
return sdf.parse(date);
} catch (Exception e) {
throw new RuntimeException("parse date error: " + e.getMessage());
}
}
代碼清單14-31中的粗體字代碼,除了正常部署流程文件(.bpmn)外,還將一份Sale.drl部署到流程引擎中,該份文件內容與代碼清單14-26內容一致。本例中創建了4個Sale對象,代碼清單14-31中的①創建了第一個銷售單實例,該實例將會滿足週六日打九折的條件。②創建的Sale對象,總金額等於100元,符合滿100元打八折的條件。③創建的Sale對象,總金額爲210元,符合滿200打七折的條件。④創建的Sale對象,總金額爲240元,並且發生在週日,即同時滿足兩個規則的條件,但是根據代碼清單14-26中的規則,200元打七折的規則比周六日打九折的規則優先級高,因此可以知道,第四個Sale對象只會觸發“200元打七折”的規則。如果需要成功運行代碼清單14-31,還需要配置activiti.cfg.xml,爲其加入規則文件的部署實現類,本例中activiti.cfg.xml的配置如下:
<bean id="processEngineConfiguration"
class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
…省略其他元素
<property name="customPostDeployers">
<list>
<bean class="org.activiti.engine.impl.rules.RulesDeployer" />
</list>
</property>
</bean>
以上配置的粗體部分爲新加入的規則部署者。在整個銷售流程中,當業務規則任務完成後,執行流會到達一個Service Task,在本例中,這個Service Task僅僅用於將規則處理後的銷售單結果輸出,Service Task的實現如代碼清單14-32所示。
代碼清單14-32:codes\14\14.7\drools-sale\src\org\crazyit\activiti\SaleJavaDelegate.java
public class SaleJavaDelegate implements JavaDelegate {
public void execute(DelegateExecution execution) {
Collection sales = (Collection) execution.getVariable("saleResults");
System.out.println("輸出處理結果:");
for (Object obj : sales) {
Sale sale = (Sale) obj;
System.out.println("銷售單:" + sale.getSaleCode() + " 原價:"
+ sale.getTotal() + " 優惠後:" + sale.getDiscountTotal()
+ " 折扣:" + sale.getDiscount());
}
}
}
在流程最後的Service Task中,得到業務規則任務處理後的結果(一個集合),然後對集合進行遍歷,強制類型轉換爲Sale對象,然後將Sale的各個信息輸出。運行代碼清單14-31,最終輸出如下:
輸出處理結果:
銷售單:002原價:100.00優惠後:80.00折扣:0.80
銷售單:001原價:20.00優惠後:18.00折扣:0.90
銷售單:004原價:240.00優惠後:168.00折扣:0.70
銷售單:003原價:210.00優惠後:147.00折扣:0.70
根據結果可知,相應的Sale對象均按預期匹配到不同的規則,銷售單001打了九折,銷售單002打了八折,銷售單003打了七折,銷售單004打了七折。
京東購買地址:https://item.jd.com/12246565.html
工作流Activiti6電子書:http://blog.csdn.net/boxiong86/article/details/78488562
工作流Activiti6教學視頻:http://blog.csdn.net/boxiong86/article/details/78608585