JBoss Rules 的前身是Codehaus的一个开源项目叫Drools。最近被纳入JBoss门下,更名为JBoss Rules,成为了JBoss应用服务器的规则引擎。
Drools是为Java量身定制的基于Charles Forgy的RETE算法的规则引擎的实现。具有了OO接口的RETE,使得商业规则有了更自然的表达。
Drools的用XML的
、
节点表达If--Then
句式,而里面可以嵌入上述语言的代码作为判断语句和执行语句。其中Java代码会使用Antlr进行解释,而Groovy和Python本身就是脚本语言,可以直接调用。Drools的聪明之处在于,用XML节点来规范If--Then
句式和事实的定义,使引擎干起活来很舒服。而使用Java,Groovy等原生语言来做判断和执行语句,让程序员很容易过渡、移植,学习曲线很低。
为了更好的了解Drools,我们以一个现成的例子为基础来进行详细的展示。
我们假设我们向供应商采购某类商品,比如说我们向供应商采购的商品001为机油,现在由于业务扩张,我们需要加大采购量,因而我们下次的采购规则是这样制定的:
假设在上次采购数量为1-100之间,我们推荐采购数量为100,当上次采购数量为100-1000之间,我们推荐采购数量为1000。
分析:
在这里,由于一个供应商提供多种商品,即除了提供编号为001的机油之外,还提供其它的商品,比如说编号为002的车载导航等,但是由于现在机油畅销,我们只需要临时制定编号为001的机油的采购规则。
同时在这里,一个商品对应多个供应商,因而我们需要针对不同供应商的上次采购记录制定下次不同的采购数量规划。
在这里,由于商品和供应商之间是一种多对多的关系,同时编号为001的机油又是由于季节等原因临时畅销的,如果我们采用硬编码的方式,那么由于不同的商品有不同的畅销期,而价格以及采购策略又经常会出现变动,因而我们会遭遇频繁改动代码,这样会造成很大的开发困扰,因而在这里我们就使用规则引擎——Drools来制定相应的采购策略。
项目结构图如下:
源码如下:
Goods
package com.lyc.drools.entity;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@Builder
@ToString
public class Goods {
private String goodsNo;
private String goodsName;
private String supplierNo;
private String supplierName;
private int total;
}
Rules.drl
package com.rules
import com.lyc.drools.entity.Goods
rule rule1
when
$goods : Goods((goodsNo == "001") && (total >= 1) && (total < 100) )
then
System.out.println("建议向【" + $goods.getSupplierName() + "】的" + $goods.getGoodsName() + "采购数量为100");
end
rule rule2
when
$goods : Goods((goodsNo == "001") && (total > 100) && (total <= 1000) )
then
System.out.println("建议向【" + $goods.getSupplierName() + "】的" + $goods.getGoodsName() + "采购数量为1000");
end
kmodule.xml
<kmodule xmlns="http://www.drools.org/xsd/kmodule">
<kbase name="rules" packages="com.rules">
<ksession name="ksession-rule"/>
kbase>
kmodule>
GoodsTest
package com.lyc.drools.entity;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.junit.Before;
import org.junit.Test;
import org.kie.api.KieServices;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import java.util.List;
@Slf4j
public class GoodsTest {
private KieContainer kieContainer = null;
private List goodsList = Lists.newArrayList();
@Before
public void init(){
KieServices kieServices = KieServices.Factory.get();
kieContainer = kieServices.getKieClasspathContainer();
Goods goods1 = Goods.builder()
.goodsNo("001")
.goodsName("机油001")
.supplierNo("01")
.supplierName("供应商01")
.total(10)
.build();
Goods goods2 = Goods.builder()
.goodsNo("002")
.goodsName("车载导航002")
.supplierNo("02")
.supplierName("供应商02")
.total(200)
.build();
Goods goods3 = Goods.builder()
.goodsNo("001")
.goodsName("机油001")
.supplierNo("03")
.supplierName("供应商03")
.total(200)
.build();
goodsList.add(goods1);
goodsList.add(goods2);
goodsList.add(goods3);
}
@Test
public void test(){
KieSession kSession = kieContainer.newKieSession("ksession-rule");
goodsList.forEach(goods -> {
log.info(goods.toString());
kSession.insert(goods);
});
kSession.fireAllRules();
kSession.dispose();
}
}
项目需要引入的依赖:
<dependency>
<groupId>org.droolsgroupId>
<artifactId>drools-compilerartifactId>
<version>7.0.0.Finalversion>
dependency>
其它配置文件:
log4j2.xml
<configuration status="OFF">
<appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} %5p %t %-5l - %m%n"/>
Console>
<RollingFile name="RollingFile" fileName="F:/logs/log4j2-xml.log" filePattern="F:/logs/log4j2-xml-$${date:yyyy-MM}/log4j2-xml-%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} %5p %t %-5l - %m%n"/>
<SizeBasedTriggeringPolicy size="5 MB" />
RollingFile>
appenders>
<loggers>
<root level="info">
<appender-ref ref="Console"/>
<appender-ref ref="RollingFile"/>
root>
loggers>
configuration>
其它依赖:
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.16.20version>
<scope>providedscope>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-slf4j-implartifactId>
<version>2.10.0version>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-coreartifactId>
<version>2.10.0version>
dependency>
<dependency>
<groupId>org.codehaus.jacksongroupId>
<artifactId>jackson-core-aslartifactId>
<version>1.9.13version>
dependency>
<dependency>
<groupId>org.codehaus.jacksongroupId>
<artifactId>jackson-mapper-aslartifactId>
<version>1.9.13version>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatypegroupId>
<artifactId>jackson-datatype-guavaartifactId>
<version>2.9.4version>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-lang3artifactId>
<version>3.5version>
dependency>
<dependency>
<groupId>joda-timegroupId>
<artifactId>joda-timeartifactId>
<version>2.9.9version>
dependency>
运行结果:
建议向【供应商01】的机油001采购数量为100
建议向【供应商03】的机油001采购数量为1000
Drools的语法结构如下:
package 包名
import 引入的java类
rule 规则名
when
(条件)左手定则(LHS(Left Hand Side))
then
(动作/结果)右手定则(RHS(Right Hand Side))
end
其中每一个规则都是以rule
开头,以end
结尾,中间有且只有一个唯一的when和then。而如果业务中要有多个判断条件时,就采用&&
等运算符与多条规则混合使用,就如我上面所举的例子中一样。
左手定则(LHS(Left Hand Side))是按照 DRL 语言编写的,条件eval(true)表示永远为真,即该条规则总会获得执行。也就是说如果左手定则为空时,其永远都是执行的。
右手定则(RHS(Right Hand Side))使用 Java 语言实现,在这里直接调用java中的方法就可以了。
Kmodule
Kbase
一个Kbase对应多个ksession。其中在同一个Kmodule中Kbase的名称是唯一的,并且有且只有唯一的一个Kbase拥有default属性。
KieBase就是一个知识仓库,包含了若干的规则、流程、方法等,在Drools中主要就是规则和方法,KieBase本身并不包含运行时的数据之类的,如果需要执行规则KieBase中的规则的话,就需要根据KieBase创建KieSession。
ksession
一个Kbase对应多个ksession,其中在同一个Kbase中的ksession名称是唯一的。
KieSession就是一个跟Drools引擎打交道的会话,其基于KieBase创建,它会包含运行时数据,包含“事实Fact”,并对运行时数据实时进行规则运算。通过KieContainer创建KieSession是一种较为方便的做法,其本质上是从KieBase中创建出来的。KieSession就是应用程序跟规则引擎进行交互的会话通道。
创建KieBase是一个成本非常高的事情,KieBase会建立知识(规则、流程)仓库,而创建KieSession则是一个成本非常低的事情,所以KieBase会建立缓存,而KieSession则不必。
packages
KieServices
KieContainer
KieRepository
Fact
通常是一个普通的Java 的POJO,一般它们会有若干个属性,每一个属性都会对应getter 和setter 方法,用来对外提供数据的设置与访问。
一般来说,在Drools 规则引擎当中,fact 所承担的作用就是将规则当中要用到的业务数据从应用当中传入进来,对于规则当中产生的数据及状态的变化通常不用fact 传出。