疯狂Activiti6.0连载(18) Activiti与Drools整合


声明:本文转载自https://my.oschina.net/JavaLaw/blog/1578146,转载目的在于传递更多信息,仅供学习交流之用。如有侵权行为,请联系我,我会及时删除。

本文节选自《疯狂Workflow讲义(第2版)》

疯狂Activiti电子书:https://my.oschina.net/JavaLaw/blog/1570397

工作流Activiti教学视频:https://my.oschina.net/JavaLaw/blog/1577577

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 = new Person("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 Sale implements Serializable {      // 销售单号     private String saleCode;     // 销售日期     private Date date;     // 销售明细     private List<SaleItem> items;         //折扣     private BigDecimal discount = 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 SaleItem implements 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     salience 1     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     salience 3     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打了七折。

本文节选自《疯狂Workflow讲义(第2版)》

疯狂Activiti电子书:https://my.oschina.net/JavaLaw/blog/1570397

工作流Activiti教学视频:https://my.oschina.net/JavaLaw/blog/1577577

本书代码目录:https://gitee.com/yangenxiong/CrazyActiviti

本文发表于2017年11月23日 22:34
(c)注:本文转载自https://my.oschina.net/JavaLaw/blog/1578146,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除.

阅读 4462 讨论 1 喜欢 0

抢先体验

扫码体验
趣味小程序
文字表情生成器

闪念胶囊

你要过得好哇,这样我才能恨你啊,你要是过得不好,我都不知道该恨你还是拥抱你啊。

直抵黄龙府,与诸君痛饮尔。

那时陪伴我的人啊,你们如今在何方。

不出意外的话,我们再也不会见了,祝你前程似锦。

这世界真好,吃野东西也要留出这条命来看看

快捷链接
网站地图
提交友链
Copyright © 2016 - 2021 Cion.
All Rights Reserved.
京ICP备2021004668号-1