Drools规则引擎
1.什么是规则引擎
规则引擎的主要思想是将应用程序中的业务决策部分抽离出来,并使用预定义的语义模块编写业务决策,由用户或开发者在需要时进行配置管理。规则引擎产品有Drools、VisualRules、iLog等。
例如,在信用卡申请业务中,需要根据学历、收入、是否有房、是否有车判断是否符合信用卡申请要求以及可以申请的额度是多少。传统的做法是使用if...else的做法,代码量大,且不易修改。
规则引擎实现将业务决策从应用程序代码中分离出来,接收数据输入,解释业务规则,并根据业务规则做出业务决策。规则引擎就是一个输入到输出的平台。
信用卡申请业务通过规则引擎的处理就变成了这样:
系统引入规则引擎之后,业务规则不再以程序代码的方式驻留在系统中,取而代之的是处理规则的规则引擎,业务规则存储在规则库中,完全独立于程序。业务人员可以像管理数据一样对业务规则进行管理,比如查询、添加、更改、统计、提交业务规则等。业务规则被加载到规则引擎后,供系统的应用程序调用。
2.规则引擎的优势
- 业务代码与系统代码分离,实现业务规则的集中管理;
- 在不重启服务的情况下,可以随时对业务规则进行扩展和维护;
- 可以动态修改业务规则,从而快速响应需求变更;
- 规则引擎是相对独立的,只关心业务规则,使得业务分析人员也可以参与编辑、维护系统的业务规则;
- 减少了硬编码业务规则的成本和风险;
- 还可以使用规则引擎提供的规则编辑工具,使复杂的业务规则实现变得简单。
3.规则引擎的应用场景
业务规则复杂,且经常变动。
(1)风险控制系统--风险贷款、风险评估
(2)反欺诈项目--银行贷款、征信验证
(3)决策平台系统--财务计算
(4)促销平台系统--满减、打折、加价购
4.Drools介绍
Drools是一款由JBoss组织提供的基于Java语言开发的开源规则引擎,可以将复杂多变的业务规则从硬编码中解放出来,以规则脚本的形式存放在文件或者指定的存储介质中(例如存放在数据库中),使得业务规则的变更不需要修改项目代码、重启服务器就可以在线上环境立即生效。IDEA中集成了Drools的插件,eclipse开发需要单独安装Drools 的插件。
Drools API开发步骤:
- 获取KieServices
- 获取KieContainer
- 获取KieSession
- 插入Fact对象
- 触发规则
- 关闭KieSession
//1.获取KieServices
KieServices kieServices = KieServices.Factory.get();
//2.获取Kie容器对象
KieContainer kieContainer = kieServices.getKieClasspathContainer();
//3.从kie容器对象中获取会话对象
KieSession session = kieContainer.newKieSession();
//4.创建Fact对象(事实对象)
Product product = new Product();
product.setType("diamond");
//5.将Product对象插入到工作内存中去
session.insert(product);
//6.激活规则,由drools框架自动进行规则匹配,如果匹配成功,则执行规则
session.fireAllRules();
//7.关闭session
session.dispose();
5.规则引擎的构成
working Memory(工作内存)
Rule Base(规则库)
Inference Engine(推理引擎):Pattern Matcher(匹配器)、Agenda(议程)、Execution Engine(执行引擎)
6.规则引擎执行过程
- 将初始数据(fact)输入至工作内存(working Memory)
- 使用匹配器(Pattern Matcher)将规则库中的规则(rule)和数据(fact)匹配,匹配成功的放入到议程(Agenda)中
- 如果执行规则存在冲突,即同时激活了多个规则,将冲突的规则放入冲突集合(冲突是同时匹配了多条规则,不需要自己处理,drools会自动处理)
- 解决冲突,将激活的规则按顺序放入议程(Agenda)
- 执行议程(Agenda)中的规则,重复2-4,直到执行完毕议程中的所有规则
7.规则文件的构成
- package:包名,package对应的不一定是真正的目录,可以任意写com.abc,同一个包下的drl文件可以相互访问
- import:用于导入类或者静态方法
- global:全局变量
- function:自定义函数
- query:查询
- rule end:规则体
8.规则体语法结构
rule "ruleName" //rule关键字,表示规则开始,参数为规则的唯一名称
attributes //规则属性,是rule与when之间的参数,为可选项
when //关键字,后面是规则的条件部分
LHS //Left Hand Side,是规则的条件部分
then //后面跟规则的结果部分
RHS //是规则的结果或行为
end //表示一个规则的结束
9.Drools基础语法
-
< >= <= == !=
- contains:检查一个Fact对象的某个属性值是否包含一个指定的对象值
- not contains:检查一个Fact对象的某个属性值是否不包含一个指定的对象值
- memberOf:判断一个Fact对象的某个属性是否在一个或多个集合中
- not memberOf:判断一个Fact对象的某个属性是否不在一个或多个集合中
- matches:判断一个Fact对象的属性是否与提供的标准的Java正则表达式进行匹配
- not matcher:判断一个Fact对象的属性是否不与提供的标准的Java正则表达式进行匹配
10.Drools内置方法
规则文件的RHS部分的主要作用是通过插入、删除或修改工作内存中的Fact数据,来达到控制规则引擎执行的目的。Drools提供了一些方法可以用来操作工作内存中的数据,操作完成后规则引擎会重新进行相关规则的匹配,原来没有匹配成功的规则在我们修改数据完成后有可能就会匹配成功了。
(1)update方法
update方法的作用是更新工作内存中的数据,并让相关的规则重新匹配。更新数据时,要注意防止陷入死循环。
rule "年龄小于十岁"
when
$student:Student(age<10)
then
$student:setAge(15)
update($student) //update方法用于更新Fact对象,会导致相关规则重新匹配
end
(2)insert方法
insert方法的作用是向工作内存中插入数据,并让相关的规则重新匹配
rule "年龄等于十岁"
when
$student:Student(age==10)
then
Student s = new Student();
s.setAge(5);
insert(s); //insert方法的作用是向工作内存中插入Fact对象,会导致相关规则重新匹配
end
(3)retract方法
retract方法的作用是删除工作内存中的数据,并让相关的规则重新匹配
rule "年龄等于十岁"
when
$student:Student(age==10)
then
retract($student) //retract方法的作用是删除工作内存中的Fact对象,会导致相关规则重新匹配
end
11.规则属性
- salience:指定规则执行的优先级,用数字表示,数字越大优先级越高。如果不设置,默认从上到下执行
- dialect:指定规则使用的语言类型,java或mvel
- enabled:指定规则是否启用,取值为ture或false,默认为true
- date-effective:指定规则生效的时间
- date-expires:指定规则失效的时间
- timer:定时器,指定规则触发的时间
- activation-group:激活分组,同一个组内只能有一个规则触发
- agenda-group:议程分组,只有获取焦点的组中的规则才有可能触发。在java代码中获得焦点。
session.getAgenda().getAgendaGroup("agenda_group_name").setFocus;
- auto-focus:自动获取焦点,一般结合agenda-group使用,取值为ture或false
- no-loop:防止死循环,当规则使用update之类的函数修改了Fact对象时,使当前规则再次被激活从而导致死循环。
12.Drools高级语法
1.global全局变量
global关键字用于在规则文件中定义全局变量,它可以让应用程序的对象在规则文件中能够被访问。可以用来为规则文件提供数据或服务。语法结构:global 对象类型 对象名称。
注意事项:如果对象类型为包装类时,在一个规则中改变了global的值,那么只针对当前规则有效,对其他规则中的global不会有影响。如果对象类型为集合类型或者javaBean时,在一个规则中改变了global的值,对java代码和所有规则都有效。
2.query查询
query查询提供了一种查询working memory中符合约束条件的Fact对象的简单方法。它仅包含规则文件中的LHS部分,不用指定"when"和"then"部分并且以end结束。具体语法结构如下:
query 查询的名称(可选参数)
LHS
end
3.function函数
function关键字用于在规则文件中定义函数,规则体中可以调用定义的function函数。使用函数的好处是可以将业务逻辑集中放置在一个地方,根据需要可以对函数进行修改。
function 返回值类型 函数名(可选参数){
//逻辑代码
}
4.LHS语法
(1)复合值限制in/not in
复合值限制是指超过一种匹配值的限制条件,类似于SQL语句中的in关键字。
$s:Student(name in ("张三","李四","王五"))
$s:Student(name not in ("张三","李四","王五"))
(2)条件元素eval
eval用于规则体的LHS部分,并返回一个Boolean类型的值。语法结构:eval(表达式)
eval(1==1)
(3)条件元素not
not用于判断working memory中是否存在某个Fact对象,如果不存在则返回true,存在返回true。
not Student(age<10)
(4)条件元素exists
exists与not相反,用于判断working memory中是否存在某个Fact对象,如果存在则返回true,不存在返回true。
exists Student(age<10)
(5)规则继承
规则之间使用extends关键字进行规则部分的继承,类似于java类之间的继承。
rule "rule_1"
when
Student(age>10)
then
System.out.println("rule_1触发");
end
rule "rule_2" extends "rule_1" //继承上面的规则
when
Student(age<20) //此处虽然只有一个条件,但继承上面的条件,所以此处的条件是10<age<20
then
System.out.println("rule_2触发");
end
5.RHS语法
RHS部分需要进行业务处理。在RHS部分Drools提供了一个内置对象,名称是drools,介绍几个drools对象的方法。
(1)halt
halt方法的作用是立即终止后面所有规则的执行。
rule "rule_1"
when
Student(age>10)
then
System.out.println("rule_1触发");
drools.halt(); //后面的规则不再执行
end
rule "rule_2"
when
Student(age<20)
then
System.out.println("rule_2触发");
end
(2)getWorkingMemory
getRule方法的作用是返回工作内存中的对象。
rule "rule_getWorkingMemory"
when
Student(age>10)
then
System.out.println(drools.getWorkingMemory());
end
(3)getRule
getRule方法的作用是返回规则对象。
rule "rule_getRule"
when
Student(age>10)
then
System.out.println(drools.getRule());
end
13.规则文件编码规范
- 所有的规则文件(.drl)应统一放在一个规定的文件夹汇总,如:/rules文件夹
- 书写的每一个规则应该尽量加上注释
- 同一类型的对象尽量放在一个规则文件中,如所有的Student类型的对象尽量放在一个规则文件中
- 规则结果部分(RHS)尽量不要有条件语句,尽量不要有复杂的逻辑和深层嵌套
- 每个规则最好都加上salience属性,明确执行顺序
- Drools默认dialect为"java",尽量避免使用dialect "mvel"
14.Spring Boot整合Drools----以信用卡申请为例
- 创建maven工程creditCardApply,并配置pom.xml
- 创建resources/application.yml文件
- 创建entity/CreditCardApply.java实体类
- 创建resources/rules/CreditCardApply.drl文件
- 编写配置类config/DroolsConfig.java
- 创建RuleService.java
- 创建RuleController.java
具体代码如下:
(1)创建maven工程creditCardApply,并配置pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<!-- drools lib -->
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-core</artifactId>
<version>7.5.0.Final</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
<version>7.5.0.Final</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-templates</artifactId>
<version>7.5.0.Final</version>
</dependency>
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-api</artifactId>
<version>7.5.0.Final</version>
</dependency>
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-spring</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</exclusion>
</exclusions>
<version>7.5.0.Final</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
(2)创建resources/application.yml文件
server:
port: 8088
spring:
application:
name: creditCardApply
(3)创建entity/CreditCardApply.java实体类
package com.example.demo.entity;
public class CreditCardApplyInfo {
public static final String EDUCATION_1 = "专科以下";
public static final String EDUCATION_2 = "专科";
public static final String EDUCATION_3 = "本科";
public static final String EDUCATION_4 = "本科以上";
private String name;
private String sex;
private int age;
private String education;
private String telephone;
private double monthlyIncome = 0;
private String address;
private boolean hasHouse = false; //是否有房
private boolean hasCar = false; //是否有车
private int hasCreditCardCount = 0; //现有信用卡数量
private boolean checkResult = true; //审核是否通过
private double quota = 0; //额度
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getEducation() {
return education;
}
public void setEducation(String education) {
this.education = education;
}
public String getTelephone() {
return telephone;
}
public void setTelephone(String telephone) {
this.telephone = telephone;
}
public double getMonthlyIncome() {
return monthlyIncome;
}
public void setMonthlyIncome(double monthlyIncome) {
this.monthlyIncome = monthlyIncome;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public boolean isHasHouse() {
return hasHouse;
}
public void setHasHouse(boolean hasHouse) {
this.hasHouse = hasHouse;
}
public boolean isHasCar() {
return hasCar;
}
public void setHasCar(boolean hasCar) {
this.hasCar = hasCar;
}
public int getHasCreditCardCount() {
return hasCreditCardCount;
}
public void setHasCreditCardCount(int hasCreditCardCount) {
this.hasCreditCardCount = hasCreditCardCount;
}
public boolean isCheckResult() {
return checkResult;
}
public void setCheckResult(boolean checkResult) {
this.checkResult = checkResult;
}
public double getQuota() {
return quota;
}
public void setQuota(double quota) {
this.quota = quota;
}
@Override
public String toString() {
if(checkResult){
return "审核通过,信用卡额度为" + quota;
}else{
return "审核不通过";
}
}
}
(4)创建resources/rules/CreditCardApply.drl文件
//package对应的不一定是真正的目录,可以任意写com.abc,同一个包下的drl文件可以相互访问
package CreditCardApply;
import com.example.demo.entity.CreditCardApplyInfo;
/*
*用户合法性检查规则,四个规则
*/
/*规则名字,任意写,只要在当前文件内唯一即可,可以写中文*/
rule "如果申请人既没房也没车,同时学历大专以下,月薪少于5000,申请不通过"
salience 10 //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
no-loop true //防止陷入死循环
when
/*规则条件,到工作内存中查找CreditCardApplyInfo对象
里面出来的结果只能是ture或者false
$c是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/
$c:CreditCardApplyInfo(hasHouse == false && hasCar == false &&
education == CreditCardApplyInfo.EDUCATION_1 &&
monthlyIncome<5000)
then
$c.setCheckResult(false); //审核不通过
drools.halt(); //终止规则引擎执行,后续规则不再执行
System.out.println("如果申请人既没房也没车,同时学历大专以下,月薪少于5000,申请不通过");
end
rule "如果申请人既没房也没车,同时学历大专或本科,月薪少于3000,申请不通过"
salience 10
no-loop true
when
$c:CreditCardApplyInfo(hasHouse == false && hasCar == false &&
(education == CreditCardApplyInfo.EDUCATION_2 ||
education == CreditCardApplyInfo.EDUCATION_3) &&
monthlyIncome<3000)
then
$c.setCheckResult(false);
drools.halt();
end
rule "如果申请人既没房也没车,同时学历本科以上,月薪少于2000,同时之前没有过信用卡,申请不通过"
salience 10
no-loop true
when
$c:CreditCardApplyInfo(hasHouse == false && hasCar == false &&
education == CreditCardApplyInfo.EDUCATION_4 &&
monthlyIncome<2000 && hasCreditCardCount == 0)
then
$c.setCheckResult(false);
drools.halt();
end
rule "申请人现有信用卡数量大于10,申请不通过"
salience 10
no-loop true
when
$c:CreditCardApplyInfo(hasCreditCardCount > 10)
then
$c.setCheckResult(false);
drools.halt();
end
/*
*确定信用卡额度,五个规则
*/
rule "如果申请人有房有车,或者月收入在20000以上,发放信用卡的额度是15000"
salience 1
no-loop true
activation-group "quota_group" //同一个组里面的规则是互斥的
when
$c:CreditCardApplyInfo(checkResult==true &&
(hasHouse == true && hasCar == true) ||
(monthlyIncome>20000))
then
$c.setQuota(15000);
end
rule "如果申请人没房没车,但月收入在10000—20000之间,发放信用卡的额度是6000"
salience 1
no-loop true
activation-group "quota_group"
when
$c:CreditCardApplyInfo(checkResult==true &&
hasHouse == false && hasCar == false &&
monthlyIncome>10000 && monthlyIncome<20000)
then
$c.setQuota(6000);
end
rule "如果申请人没房没车,但月收入在10000以下,发放信用卡的额度是3000"
salience 1
no-loop true
activation-group "quota_group"
when
$c:CreditCardApplyInfo(checkResult==true &&
hasHouse == false && hasCar == false &&
monthlyIncome<10000)
then
$c.setQuota(3000);
end
rule "如果申请人有房没车或者没房有车,但月收入在10000以下,发放信用卡的额度是5000"
salience 1
no-loop true
activation-group "quota_group"
when
$c:CreditCardApplyInfo(checkResult==true &&
((hasHouse == true && hasCar == false) ||
(hasHouse == false && hasCar == true)) &&
monthlyIncome<10000)
then
$c.setQuota(5000);
end
rule "如果申请人有房没车或者没房有车,但月收入在10000—20000之间,发放信用卡的额度是8000"
salience 1
no-loop true
activation-group "quota_group"
when
$c:CreditCardApplyInfo(checkResult==true &&
((hasHouse == true && hasCar == false) ||
(hasHouse == false && hasCar == true)) &&
monthlyIncome>10000 && monthlyIncome<20000)
then
$c.setQuota(8000);
end
(5)编写配置类config/DroolsConfig.java
package com.example.demo.config;
import org.kie.api.KieBase;
import org.kie.api.KieServices;
import org.kie.api.builder.KieBuilder;
import org.kie.api.builder.KieFileSystem;
import org.kie.api.builder.KieRepository;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.kie.internal.io.ResourceFactory;
import org.kie.spring.KModuleBeanFactoryPostProcessor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import java.io.IOException;
@Configuration
public class DroolsConfig {
private static final String RULES_PATH = "rules/"; //指定规则存放的目录
private final KieServices kieServices = KieServices.Factory.get();
@Bean
@ConditionalOnMissingBean
public KieFileSystem kieFileSystem() throws IOException {
KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
Resource[] files = resourcePatternResolver.getResources("classpath*:" + RULES_PATH + "*.*");
String path = null;
for (Resource file : files) {
path = RULES_PATH + file.getFilename();
kieFileSystem.write(ResourceFactory.newClassPathResource(path, "UTF-8"));
}
return kieFileSystem;
}
@Bean
@ConditionalOnMissingBean
public KieContainer kieContainer() throws IOException {
KieRepository kieRepository = kieServices.getRepository();
kieRepository.addKieModule(kieRepository::getDefaultReleaseId);
KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem());
kieBuilder.buildAll();
return kieServices.newKieContainer(kieRepository.getDefaultReleaseId());
}
@Bean
@ConditionalOnMissingBean
public KieBase kieBase() throws IOException {
return kieContainer().getKieBase();
}
@Bean
@ConditionalOnMissingBean
public KieSession kieSession() throws IOException {
return kieContainer().newKieSession();
}
@Bean
@ConditionalOnMissingBean
public KModuleBeanFactoryPostProcessor kiePostProcessor() {
return new KModuleBeanFactoryPostProcessor();
}
}
(6)创建RuleService.java
package com.example.demo.service;
import com.example.demo.entity.CreditCardApplyInfo;
import org.kie.api.KieBase;
import org.kie.api.runtime.KieSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class RuleService {
@Autowired
private KieBase kieBase;
//调用Drools规则引擎实现信用卡申请
public CreditCardApplyInfo creditCardApply(CreditCardApplyInfo creditCardApplyInfo){
KieSession session = kieBase.newKieSession(); //获取KieSession
session.insert(creditCardApplyInfo); //插入
session.fireAllRules(); //执行规则
session.dispose(); //释放资源
return creditCardApplyInfo;
}
}
(7)创建RuleController.java
package com.example.demo.controller;
import com.example.demo.entity.CreditCardApplyInfo;
import com.example.demo.service.RuleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class RuleController {
@Autowired
private RuleService ruleService;
// @RequestMapping("/creditCradApply")
// public CreditCardApplyInfo creditCardApply(@RequestBody CreditCardApplyInfo creditCardApplyInfo){
// creditCardApplyInfo = ruleService.creditCardApply(creditCardApplyInfo);
// return creditCardApplyInfo;
// }
@RequestMapping("/creditCradApply")
public CreditCardApplyInfo creditCardApply(){
CreditCardApplyInfo creditCardApplyInfo = new CreditCardApplyInfo();
creditCardApplyInfo.setEducation("专科以下"); //学历
creditCardApplyInfo.setHasHouse(false); //是否有房
creditCardApplyInfo.setHasCar(false); //是否有车
creditCardApplyInfo.setMonthlyIncome(3500); //月收入
CreditCardApplyInfo creditCardApplyResult = ruleService.creditCardApply(creditCardApplyInfo);
return creditCardApplyResult;
}
}