规则引擎之Drools
Drools概述
drools是一款由JBoss组织提供的基于Java语言开发的开源规则引擎,可以将复杂且多变的业务规则从硬编码中解放出来,以规则脚本的形式存放在文件或特定的存储介质中,使得业务规则的变更不需要修改项目代码、重启服务器就可以在线上环境立即生效。
drools官网:https://drools.org
drools源码地址:https://github.com/kiegroup/drools
规则引擎构成
三部分构成:
1.Working Memory(工作内存)
2.Rule Base(规则库)
3.Inference Engine(推理引擎)
a.Pattern Matcher(匹配器)
b.Agenda(议程)
c.Execution Engine(执行引擎)
术语概述:
Working Memory:
drools规则引擎会从Working Memory中获取数据并和规则文件中定义的规则进行模式匹配,在开发应用程序时调用kieSession.insert(Object)就将Object对象插入到了工作内存中。
Fact:
将一个普通的JavaBean插入到Working Memory后的对象就是Fact对象。Fact对象是应用和规则引擎进行数据交互的桥梁或通道。
Rule Base:
规则库,在规则文件中定义的规则都会被加载到规则库中
Pattern Matcher:
匹配器,将Rule Base中的所有规则与Working Memory中的Fact对象进行模式匹配,匹配成功的规则将被激活并放入Agenda中。
Agenda:
议程,用于存放通过匹配器进行模式匹配后被激活的规则。
Execution Engine:
执行引擎,执行Agenda中被激活的规则。
规则文件构成
规则文件的后缀为.drl。drl是Drools Rule Language的缩写。在规则文件中编写具体的规则内容。
关键字 | 描述 |
---|---|
package | 包名,只限于逻辑上的管理,同一个包名下的查询或者函数可以直接调用 |
import | 用于导入类或者静态方法 |
global | 全局变量 |
function | 自定义函数 |
query | 查询 |
rule end | 规则体 |
规则体语法结构
规则体是规则文件内容中的重要组成部分,是进行业务规则判断、处理业务结果的部分。
rule "ruleName"
attributes
when
LHS
then
RHS
end
关键字 | 描述 |
---|---|
rule | 关键字,表示规则开始,参数为规则的唯一名称 |
attributes | 规则属性,是rule与when之间的参数,为可选项 |
when | 关键字,后面跟规则的条件部分 |
LHS(Left Hand Side) | 是规则的条件部分的通用名称。它由零个或多个条件元素组成。如果LHS为空,则它将被视为始终为true的条件元素。 |
then | 关键字,后面跟规则的结果部分 |
RHS(Right Hand Side) | 是规则的后果或行动部分的通用名称 |
end | 关键字,表示一个规则结束 |
规则属性
属性名 | 说明 |
---|---|
salience | 指定规则执行优先级 |
dialect | 指定规则使用的语言类型,取值为java和mvel |
enabled | 指定规则是否启用 |
activation-group | 激活分组,具有相同分组名称的规则只能有一个规则触发 |
no-loop | 防止死循环 |
salience属性
用于指定规则的执行优先级,取值类型为Integer。数值越大越优先执行。每个规则都有一个默认的执行顺序,如果不设置salience属性,规则体的执行顺序为由上到下。
rule "rules1"
when
eval(true)
then
System.out.println("1");
end
rule "rules2"
salience 100
when
eval(true)
then
System.out.println("2");
end
enabled属性
取值为true和false,默认值为true。用于指定当前规则是否启用,如果设置的值为false则当前规则无论是否匹配成功都不会触发
rule "rules1"
enabled false
when
$course:Course(score ==60)
then
$course.setRating("C级");
end
activation-group属性
指激活分组,取值为String类型。具有相同分组名称的规则只能有一个规则被触发。
rule "rules1"
activation-group "myGroup"
when
$course:Course(score==60)
then
System.out.println("activation-group");
end
rule "rules2"
activation-group "myGroup"
salience 100
when
$course:Course(score==60)
then
System.out.println("activation-group");
end
no-loop属性
用于防止死循环,当规则通过update之类的函数修改了Fact对象时,可能使当前规则再次被激活从而导致死循环。取值类型为Boolean,默认值为false。
rule "rules1"
no-loop true
when
$course:Course(score==60)
then
update($course);
System.out.println("no-loop");
end
比较操作符
常规比较操作符
符号 | 说明 |
---|
| 大于
< | 小于
= | 大于等于
<= | 小于等于
== | 等于
!= | 不等于
特殊比较操作符
// 创建普通的JavaBean(Fact)对象
@Data
public class Course {
private String name;
private List<String> list;
}
符号 | 说明 | 示例 |
---|---|---|
contains | 检查一个Fact对象的某个属性值是否包含一个指定的对象值 | Course(name contains “小白”) |
not contains | 检查一个Fact对象的某个属性值是否不包含一个指定的对象值 | Course(name no contains “小白”) |
memberOf | 判断一个Fact对象的某个属性是否在一个或多个集合中 | name memberOf list |
not memberOf | 判断一个Fact对象的某个属性是否不在一个或多个集合中 | name not memberOf list |
matches | 判断一个Fact对象的属性是否与提供的标准的Java正则表达式进行匹配 | name matches “小.*” |
not matches | 判断一个Fact对象的属性是否不与提供的标准的Java正则表达式进行匹配 | name not matches “小.*” |
Pattern模式匹配
在规则体的LHS部分定义规则并进行模式匹配。LHS部分由一个或者多个条件组成,条件又称为pattern。
pattern的语法结构为:绑定变量名:Object(Field约束)
绑定变量名可以省略,通常绑定变量名的命名一般以$开始。如果定义了绑定变量名,就可以在规则体的RHS部分使用此绑定变量名来操作相应的Fact对象。
LHS部分还可以定义多个pattern,多个pattern之间可以使用and或者or进行连接,默认连接为and。
//规则1:100分~90分并且为95分 为A级
rule "rules1"
when
$course:Course($score:score >=90 && score<=100) and
$course2:Course(score==95)
then
$course.setRating("A级");
System.out.println("$score = " + $score);
System.out.println("course = " + $course.getScore());
System.out.println("匹配规则1:A级");
end
$score = 95.0
course = 95.0
匹配规则1:A级
course = Course(score=95.0, rating=A级)
执行指定规则
满足条件的规则都会被执行,如果只想执行其中的某个规则,可以通过规则过滤器来实现执行指定规则。
//通过规则过滤器实现只执行指定规则
kieSession.fireAllRules(new RuleNameEqualsAgendaFilter("rules1"));
Drools内置方法
规则文件的RHS部分的主要作用是通过插入,删除或修改工作内存中的Fact数据,来达到控制规则引擎执行的目的。
Drools提供了一些方法可以用来操作工作内存中的数据,操作完成后规则引擎会重新进行相关规则的匹配,原来没有匹配成功的规则在我们修改数据完成后有可能就会匹配成功
update方法
update方法的作用是更新工作内存中的数据,并让相关的规则重新匹配
rule "rules1"
when
$course:Course(score ==60)
then
$course.setRating("C级");
end
rule "rules2"
when
$course:Course(score <60 && score>=50)
then
$course.setScore(60D);
update($course);
end
insert方法
insert方法的作用是向工作内存中插入数据,并让相关的规则重新匹配
rule "rules1"
when
$course:Course(score ==60)
then
$course.setRating("C级");
end
rule "rules2"
when
$course:Course(score <60 && score>=50)
then
Course course = new Course();
course.setScore(60D);
insert(course);
end
retract方法
retract方法的作用是删除工作内存中的数据,并让相关的规则重新匹配
rule "rules1"
when
$course:Course(score ==60)
then
$course.setRating("C级");
end
rule "rules2"
when
$course:Course(score <60 && score>=50)
then
Course course = new Course();
course.setScore(60D);
insert(course);
end
rule "rules3"
// salience 2
when
$course:Course(score <60 && score>=50)
then
$course.setScore(60D);
retract($course);
end
Drools的基本使用
添加依赖
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
<version>7.62.0.Final</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-mvel</artifactId>
<version>7.62.0.Final</version>
</dependency>
创建kmodule.xml
创建resources/META-INF/kmodule.xml配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<kmodule xmlns="http://www.drools.org/xsd/kmodule">
<kbase name="myKbase">
<ksession name="myksession" default="true"/>
</kbase>
</kmodule>
创建JavaBean(Fact)对象
@Data
public class Course {
/**
* 分数
*/
private Double score;
/**
* 评级
*/
private String rating;
}
创建规则文件
创建resources/myrules(任意名称)目录,并创建规则文件resources/myrules/course.drl
IDEA默认集成了drools插件,在IDEA中可以识别drools文件并给与提示
# 逻辑包,建议包名和文件夹名一一对应。
package myrules
# 导入数据载体对象
import cn.ybzy.demo.model.Course
//规则1:100分~90分为A级
rule "rules1"
when
$course:Course(score >=90 && score<=100)
then
$course.setRating("A级");
System.out.println("匹配规则1:A级");
end
//规则2:89分~75分为B级
rule "rules2"
when
$course:Course(score >=75 && score<=89)
then
$course.setRating("B级");
System.out.println("匹配规则2:B级");
end
//规则3:74分~60分为C级
rule "rules3"
when
$course:Course(score >=60 && score<=74)
then
$course.setRating("C级");
System.out.println("匹配规则3:C级");
end
//规则4:59分及其以下为D级
rule "rules4"
when
$course:Course(score<=59)
then
$course.setRating("D级");
System.out.println("匹配规则4:D级");
end
执行测试
public static void main(String[] args) {
KieServices kieServices = KieServices.Factory.get();
// kieClasspathContainer容器对象; 默认自动加载META-INF/kmodule.xml
KieContainer kieClasspathContainer = kieServices.getKieClasspathContainer();
//从容器中获取会话对象,用于和规则引擎交互; default="true"指定则使用默认的,否则使用kmodule.xml中定义的ksession name
KieSession kieSession = kieClasspathContainer.newKieSession();
//创建数据载体对象,设置分数,由规则引擎根据分数规则计算不同的评级
Course course = new Course();
course.setScore(85D);
//将数据提供给规则引擎,规则引擎会根据提供的数据进行规则匹配
kieSession.insert(course);
//激活规则引擎,如果规则匹配成功则执行规则
kieSession.fireAllRules();
//关闭会话
kieSession.dispose();
System.out.println("course = " + course);
}
匹配规则2:B级
course = Course(score=85.0, rating=B级)
Spring Boot整合Drools
添加依赖
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<drools.version>7.62.0.Final</drools.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-mvel</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-spring</artifactId>
<version>${drools.version}</version>
<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>
</dependency>
创建JavaBean(Fact)对象
@Data
public class Course {
/**
* 分数
*/
private Double score;
/**
* 评级
*/
private String rating;
}
创建规则文件
创建/resources/myrules/springbootrules.drl规则文件
package myrules
import cn.ybzy.demo.model.Course
rule "rules1"
when
eval(true)
then
System.out.println("springbootrules....");
end
rule "rules2"
when
$course:Course(score >=75 && score<=89)
then
$course.setRating("B级");
System.out.println("匹配规则2:B级");
end
规则引擎配置类
创建配置类DroolsConfig
/**
* 规则引擎配置类
*/
@Configuration
public class DroolsConfig {
//指定规则文件存放的目录
private static final String RULES_PATH = "myrules/";
private final KieServices kieServices = KieServices.Factory.get();
@Bean
@ConditionalOnMissingBean
public KieFileSystem kieFileSystem() throws IOException {
KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
Resource[] ruleFiles = resourcePatternResolver.getResources("classpath*:" + RULES_PATH + "*.*");
String path = null;
for (Resource file : ruleFiles) {
path = RULES_PATH + file.getFilename();
kieFileSystem.write(ResourceFactory.newClassPathResource(path, "UTF-8"));
}
return kieFileSystem;
}
@Bean
@ConditionalOnMissingBean(KieContainer.class)
public KieContainer kieContainer() throws IOException {
KieRepository kieRepository = kieServices.getRepository();
// kieRepository.addKieModule(kieRepository::getDefaultReleaseId);
kieRepository.addKieModule(new KieModule() {
@Override
public ReleaseId getReleaseId() {
return kieRepository.getDefaultReleaseId();
}
});
KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem());
kieBuilder.buildAll();
return kieServices.newKieContainer(kieRepository.getDefaultReleaseId());
}
@Bean
@ConditionalOnMissingBean(KieSession.class)
public KieSession kieSession() throws IOException {
return kieContainer().newKieSession();
}
@Bean
@ConditionalOnMissingBean(KieBase.class)
public KieBase kieBase() throws IOException {
return kieContainer().getKieBase();
}
@Bean
@ConditionalOnMissingBean(KModuleBeanFactoryPostProcessor.class)
public KModuleBeanFactoryPostProcessor kiePostProcessor() {
return new KModuleBeanFactoryPostProcessor();
}
}
创建RuleService类
@Service
public class RuleService {
@Autowired
private KieBase kieBase;
public void rule(){
KieSession kieSession = kieBase.newKieSession();
kieSession.fireAllRules();
kieSession.dispose();
}
public void rule(Course course){
KieSession kieSession = kieBase.newKieSession();
kieSession.insert(course);
kieSession.fireAllRules();
kieSession.dispose();
}
}
创建TestController
@RestController
public class TestController {
@Autowired
private RuleService ruleService;
@RequestMapping("/rule1")
public String rule1() {
ruleService.rule();
return "success";
}
@RequestMapping("/rule2")
public String rule2() {
Course course = new Course();
course.setScore(85D);
ruleService.rule(course);
return "success";
}
}
执行测试
访问http://localhost:8080/rule
springbootrules....
匹配规则2:B级
动态规则
提供HTTP访问接口,将规则文件的内容存储在数据库中,当规则发生变化时调用此接口重新加载数据库中的规则。
创建数据库表存储规则
@Data
@TableName("rules")
public class Rules implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Long id;
private String ruleName;
private String content;
}
数据库表添加规则
package dynamicRules
rule "rules1"
when
eval(true)
then
System.out.println("动态规则....");
end
创建Mapper接口
@Mapper
public interface RulesMapper extends BaseMapper<Rules> {
}
加载数据库规则
@Service
public class ReloadDroolsRulesService {
public static KieContainer kieContainer;
@Autowired
private RulesMapper rulesMapper;
public KieContainer loadKieContainer() {
List<Rules> rules = rulesMapper.selectList(null);
KieServices kieServices = KieServices.Factory.get();
KieRepository kieRepository = kieServices.getRepository();
KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
for (Rules rule : rules) {
String drl = rule.getContent();
kieFileSystem.write("src/main/resources/dynamicRules/" + rule.getRuleName() + ".drl", drl);
}
KieBuilder kb = kieServices.newKieBuilder(kieFileSystem);
kb.buildAll();
kieContainer = kieServices.newKieContainer(kieRepository.getDefaultReleaseId());
return kieContainer;
}
}
项目启动时自动加载规则
@Component
@Slf4j
public class CommandLineRunnerImpl implements CommandLineRunner {
@Resource
private ReloadDroolsRulesService reloadDroolsRulesService;
@Override
public void run(String... args) {
log.info(" load rules ...");
reloadDroolsRulesService.loadKieContainer();
}
}
提供HTTP访问接口
@RestController
public class RulesReloadController {
@Autowired
private ReloadDroolsRulesService reloadDroolsRulesService;
@RequestMapping("/reload")
public String reload() {
reloadDroolsRulesService.loadKieContainer();
return "success";
}
@RequestMapping("/test")
public String test() {
KieSession kieSession = ReloadDroolsRulesService.kieContainer.newKieSession();
kieSession.fireAllRules();
kieSession.dispose();
return "success";
}
}
动态规则测试
1.访问/test接口
后修改数据库规则内容
2.访问/reload接口
后再访问/test接口
动态规则....
INFO 7152 --- [io-8080-exec-10] o.d.c.kie.builder.impl.KieContainerImpl : Start creation of KieBase: defaultKieBase
INFO 7152 --- [io-8080-exec-10] o.d.c.kie.builder.impl.KieContainerImpl : End creation of KieBase: defaultKieBase
动态修改规则....
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/137010.html