Drools基础应用
1. 为什么要使用规则擎
if ("周一".equals(day)) {
System.out.println("I will play basketball");
} else if ("周二".equals(day)) {
System.out.println("I will do shopping");
} else if ("周三".equals(day)) {
System.out.println("I will do shopping");
} else if ("周四".equals(day)) {
System.out.println("I will do shopping");
} else if (...) {
...
} else {
...
}
- 产品需求1:周二不应该do shopping,应该要play switch
- 产品需求2:周五、周六、周日呢?
实际生产情况可能涉及的规则更为复杂多变,如何修改上述代码呢。可能有人会使用策略模式、模板方法、装饰器、工厂方法之类,但那仅仅是针对于代码层面的优化。但针对于复杂的业务逻辑,还需要做到以下几点:
- 简化if else结构,让业务逻辑和数据分离!
- 分理处的业务逻辑必须要易于编写,至少单独编写这些业务逻辑,要比写代码快!
- 分离出的业务逻辑必须要比原来的代码更容易读懂!
- 分离出的业务逻辑必须比原来的易于维护,至少改动这些逻辑,应用程序不用重启!
基于此种需求,规则引擎诞生了。规则引擎是一种嵌套在应用程序中的组件,它实现了将业务规则从应用程序代码中分离出来。规则引擎使用特定的语法编写业务规则,规则引擎可以接受数据输入、解释业务规则、并根据业务规则做出相应的决策。
市面上的规则引擎框架有很多,Aviator、Drools、RasyRules、RuleBook、Ikexpression、MVEL、JRules、JLisa、QuickRules等等。但是更多的公司还是选择Drools作为生产应用的规则引擎。
2.Drools简介
Drools是由Java语言开发的开源业务规则引擎,其具有一个易于访问企业策略、易于调整以及易于管理的开源业务规则引擎,符合业内标准、速度快、效率高。业务分析师或者审核人员可以利用它轻松查看业务规则,从而检验是否已编码的规则执行了所需的业务规则。
Drools具有以下优点:
- 活跃的社区支持
- 简洁易用
- 快速的执行速度:Rete算法
- 在Java开发人员中流行
2.1 HelloWorld
2.1.1引入Maven依赖
<!-- drools -->
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-internal</artifactId>
<version>7.10.0.Final</version>
</dependency>
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-api</artifactId>
<version>7.10.0.Final</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-core</artifactId>
<version>7.10.0.Final</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
<version>7.10.0.Final</version>
</dependency>
2.1.2创建一个JavaBean
public class DailyProgram {
int day;
String plan = "无";
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
public String getPlan() {
return plan;
}
public void setPlan(String plan) {
this.plan = plan;
}
}
2.1.3创建kmodule.xml,放在resources目录的META-INF文件夹下
<?xml version="1.0" encoding="UTF-8"?>
<kmodule xmlns="http://jboss.org/kie/6.0.0/kmodule">
<kbase name="daily_program" packages="rules">
<ksession name="daily_program_session" type="stateful"/>
</kbase>
</kmodule>
2.1.4创建规则文件dailyprogram.drl
package wang.myrule;
import wang.smalleyes.drools.bean.DailyProgram;
function String hello(String name){
return "Hello " + name + "!";
}
rule "day1"
when
$dailyProgram: DailyProgram(day == 1);
then
$dailyProgram.setPlan("I will play basketball");
System.out.println( "exec rule day1 ... " );
System.out.println( hello("day1") );
end
2.1.5测试规则运行
public void testRules1(){
//创建KieServices
KieServices kieServices = KieServices.get();
//创建KieServices,classPathContainer默认获取META-INF下的kmodule.xml
KieContainer kieContainer = kieServices.getKieClasspathContainer();
//按名称指定获取KieSession
KieSession kieSession = kieContainer.newKieSession("daily_program_session");
DailyProgram dailyProgram = new DailyProgram();
dailyProgram.setDay(1);
kieSession.insert(dailyProgram);
int fireCount = kieSession.fireAllRules();
kieSession.dispose();
System.err.println(dailyProgram.getPlan());
System.out.println("规则执行击中" + fireCount + "条规则");
}
运行结果如下:
2.2重要的API
- KieServices:KIE整体的入口,可以用来创建下面这些API等。
- KieContainer:KieBase的容器,根据kmodule.xml里描述的信息获取具体的KieSession、KieBase;
- KieBase:就是一个知识仓库,包含了若干的规则、流程、方法等,在Drools中主要就是规则和方法,KieBase本身并不包含运行时的数据之类的,如果需要执行规则KieBase中的规则的话,就需要根据KieBase创建KieSession
- KieSession:KieSession就是一个跟Drools引擎打交道的会话,其基于KieBase创建,它会包含运行时数据,包含“事实 Fact”,并对运行时数据事实进行规则运算
- KieModule:是一个包含了多个kiebase定义的容器。一般用kmodule.xml来表示
2.3KieSession
KieSession在创建时可指定创建为有状态Session或无状态Session。
有状态Session:它可以在交互的同时,保持住状态。这就是有状态的会话。当我们不想再使用KieSession的时候,那么我们必须显示的调用dispose()方法来声明下。
有状态Session使用fireAllRules()方法执行规则。
无状态Session:无状态的KieSession会将这种会话描述为一个函数,具有无状态以及原子性。理想情况下,函数不应该具有任何间接影响。
无状态Session使用execute()方法执行规则。
在Kmodule.xml文件中定义Kbase下的Kession,由属性type可以控制无状态(stateless)或者有状态(stateful)。或者通过以下API创建
kieContainer.newStatelessKieSession(”daily_program_session“);
有状态和无状态的区别在于多次会话时,有状态会保留前次计算的信息,而无状态多次运算之间是不相互影响的。
2.4规则文件语法
2.4.1 package
在一个规则文件当中 package 是必须的,而且必须放置在文件的第一行。
package 的名字是随意的,不必必须对应物理路径,这里跟 java 的package 的概念不同,只是逻辑上的区分,但建议与文件路径一致。
同一的 package 下定义的 function 和 query 等可以直接使用。如:
package wang.myrule;
2.4.2 import
导入规则文件需要的外部变量,使用方法跟 java 相同。 和 java 的 import 一样, 还可以导入类中的某一个可访问的静态方法。
import wang.smalleyes.drools.bean.DailyProgram;
2.4.3 global
global用于定义全局变量。它可以让应用程序的对象在规则文件中能够被访问。通常,可以用来为规则文件提供数据或服务。在规则执行前由kieSession注入。
@Autowired
private EmailService emailService
...
kieSession.setGlobal("emailService", emailService);
drl文件中的用法:
import wang.smalleyes.drools.EmailService;
global EmailService $emailService;
rule "rule1"
when
...
then
$emailService.sendEmail()
end
2.4.4 方法定义——function
将语义代码放置在规则文件中的一种方式,就相当于java类中的方法
package wang.myrule;
import wang.smalleyes.drools.bean.DailyProgram;
function String hello(String name){
return "Hello " + name + "!"
}
rule "rule1"
when
...
then
hello("tom");
end
2.4.5规则定义
rule "rule1" //规则名称,全局唯一
attributes //规则属性
when
LHS //规则执行条件
then
RHS //规则执行结果
end
常用的attributes规则属性:
- no-loop:定义当前的规则是否不允许多次循环执行,默认是 false,也就是当前的规则只要满足条件,可以无限次执行。当执行Update、insert方法更新Fact对象时,会触发规则执行。
- salience:规则执行顺序,值越大,越优先执行
3.Springboot整合问题
在本地运行时,规则中的globals引用的外部服务,始终无法被Drools找到,显示Null。经过Debug后,发现是引用了SpringBoot的dev-tools依赖,开启了热加载。
原因:
dev-tools开启后,被Spring管理的bean的类加载器使用了自定义的RestartClassLoader。而Drools在运行时,获取的类的类加载器是AppClassLoader。因为类加载器不一致,并不是由双亲委派模型的下的上级类加载器加载进来,对于一个类,由不同的类加载器实例加载的话,会在方法区产生两个不同的类,彼此不可见,并且在堆中生成不同Class实例(所有通过正常双亲委派模式的类加载器加载的classpath下的和ext下的所有类在方法区都是同一个类,堆中的Class实例也是同一个)。因为两者不一致,所以drools在workingmemory中使用的是AppAppClassLoader的实例自然无法找到由RestartClassLoader加载的实例。
解决方式:
- 将Drools升级至7.23.0FINAL或更高版本。
- 取出SpringBoot的dev-tools依赖。
4.Drools热部署——实时更新KieBase
@Test
public void testRules(){
KieServices kieServices = KieServices.get()
KieContainer kieContainer = kieServices.getKieclasspathContainer()
KieSession kieSession = kiecontainer.newKieSession("daily_program_ session")
KnowledgeBaseImpl kieBase(KnowledgeBaseImpl)kieContainer.getKieBase("daily_program");
//实时更新规则
insertRules(kieBase)
DailyProgram dailyProgram = new DailyProgram();
dailyProgram.setDay(1);
kieSession.insert(dailyProgram)
DailyProgram dailyProgram2 = new DailyProgram();
dailyProgram2.setDay(2);
kieSession.insert(dailyProgram2)
int firecount = kiesession,fireAllRules()
kieSession.dispose()
System.err.println(dailyProgram.getPlan());
System.err.println(dailyProgram2.getPlan());
System.out.println("规则执行击中”+firecount +"条规则")
}
private void insertRules(KnowledgeBaseImpl kieBase){
String rule = "package com.myrule;\n"
+ "\n" + "import wang.smalleyes.drools.bean.DailyProgram;\n"
+ "\n" + "rule \"day2\"\n"
+ "\n" + "when\n"
+ "$dailyProgram: DailyProgram(day == 2);\n"
+ "then\n"
+ "$dailyProgram.setPlan(\"I will do shopping\");\n"
+ "System.err.println(\"exec rule day2 ...\");\n" + "end\n";
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(kieBase);
kbuilder .add(ResourceFactory.newByteArrayResource(rule.getBytes())
.setSourcePath("com/myrule/new.drl")
,ResourceType.DRL).
if(kbuilder.hasErrors()){
System.err.println("规则加载异常");
}
Collection<KiePackage> kiePackages = kbuilder.getKnowledgePackages();
kieBase.addPackages(kiePackages);
}
- KnowledgeBuilder:在业务代码中收集已编写的规则,并对规则文件进行编译,生成编译好的KnowledgePackage集合,提供给其他API使用。
- KiePackage:编译好的规则包
执行结果: