1.什么是drools?
Drools 具有一个易于访问企业策略、易于调整以及易于管理的开源业务
规则引擎,符合业内标准,速度快、效率高。业务分析师或审核人员可以利用它轻松查看业务规则,从而检验已编码的规则是否执行了所需的业务规则。其前身是 Codehaus 的一个开源项目叫 Drools,后被纳入 JBoss 门下,更名为 JBoss Rules,成为了 JBoss 应用服务器的规则引擎。
Drools 被分为两个主要的部分:编译和运行时。编译是将规则描述文件按 ANTLR 3 语法进行解析,对语法进行正确性的检查,然后产生一种中间结构“descr”,descr 用 AST 来描述规则。目前,Drools 支持四种规则描述文件,分别是:drl 文件、 xls 文件、brl 文件和 dsl 文件,其中,常用的描述文件是 drl 文件和 xls 文件,而 xls 文件更易于维护,更直观,更为被业务人员所理解。运行时是将 AST传到 PackageBuilder,由 PackagBuilder来产生 RuleBase,它包含了一个或多个 Package 对象。
原理
在 AI 领域,产生式系统是一个很重要的理论,产生式推理分为正向推理和逆向推理产生式,其规则的一般形式是:IF 条件 THEN 操作。rete 算法是实现产生式系统中正向推理的高效模式匹配算法,通过形成一个 rete 网络进行模式匹配,利用基于规则的系统的时间冗余性和结构相似性特征 ,提高系统模式匹配效率
正向推理(Forward-Chaining)
和
反向推理(Backward-Chaining)
- 正向推理也叫演绎法,由事实驱动,从一个初始的事实出发,不断地从应用规则得出结论。首先在候选队列中选择一条规则作为启用规则进行推理,记录其结论作为下一步推理的证据。如此重复这个过程,直到再无可用规则可被选用或者求得了所要求的解为止。
- 反向推理也叫归纳法,由目标驱动,首先提出某个假设,然后寻找支持该假设的证据,若所需的证据都能找到,说明原假设是正确的,若无论如何都找不到所需要的证据,则说明原假设不成立,此时需要另作新的假设。
rete算法
Rete 算法最初是由卡内基梅隆大学的 Charles L.Forgy 博士在 1974 年发表的论文中所阐述的算法 , 该算法提供了专家系统的一个高效实现。自 Rete 算法提出以后 , 它就被用到一些大型的规则系统中 , 像 ILog、Jess、JBoss Rules 等都是基于 RETE 算法的规则引擎 。
Rete 在拉丁语中译为”net”,即网络。Rete 匹配算法是一种进行大量模式集合和大量对象集合间比较的高效方法,通过网络筛选的方法找出所有匹配各个模式的对象和规则。
其核心思想是将分离的匹配项根据内容动态构造匹配树,以达到显著降低计算量的效果。Rete 算法可以被分为两个部分:规则编译和规则执行 。当 Rete 算法进行事实的断言时,包含三个阶段:匹配、选择和执行,称做 match-select-act cycle。
Drools相关概念:
- 事实(Fact):对象之间及对象属性之间的关系
- 规则(rule):是由条件和结论构成的推理语句,一般表示为if…Then。一个规则的if部分称为LHS,then部分称为RHS。
- 模式(module):就是指IF语句的条件。这里IF条件可能是有几个更小的条件组成的大条件。模式就是指的不能在继续分割下去的最小的原子条件。
Drools通过事实、规则和模式相互组合来完成工作,drools在开源规则引擎中使用率最广,但是在国内企业使用偏少,保险、支付行业使用稍多。
2.代码工程
实验目的
实验根据订单金额和品类进行折扣计算
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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springboot-demo</artifactId>
<groupId>com.et</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>drools</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<drools.version>7.53.0.Final</drools.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-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-decisiontables</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-core</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
<version>${drools.version}</version>
</dependency>
</dependencies>
</project>
规则文件
package KieRule
import com.et.drools.Order;
import com.et.drools.OutputDisplay;
global OutputDisplay showResults;
global OutputDisplay sh;
rule "MASTERCARD"
when
orderObject : Order(cardType == "MASTERCARD" && price > 10000);
then
orderObject.setDiscount(10);
showResults.showText("showResults MASTERCARD has been added a discount");
sh.showText("sh MASTERCARD has been added a discount");
end;
rule "VISA"
when
orderObject : Order(cardType == "VISA" && price > 5000);
then
orderObject.setDiscount(14);
showResults.showText("showResults VISA has been added a discount");
sh.showText("sh VISA has been added a discount");
end;
rule "ICICI"
when
orderObject : Order(cardType == "ICICI" && price > 7000);
then
sh.showText("sh ICICI has been added a discount");
orderObject.setDiscount(20);
showResults.showText("showResults ICICI has been added a discount");
end;
- package 与Java语言类似,drl的头部需要有package和import的声明,package不必和物理路径一致。
- import 导出java Bean的完整路径,也可以将Java静态方法导入调用。
- rule 规则名称,需要保持唯一,可以无限次执行。
- no-loop 定义当前的规则是否不允许多次循环执行,默认是 false,也就是当前的规则只要满足条件,可以无限次执行。
- lock-on-active 将lock-on-active属性的值设置为true,可避免因某些Fact对象被修改而使已经执行过的规则再次被激活执行。
- salience 用来设置规则执行的优先级,salience 属性的值是一个数字,数字越大执行优先级越高, 同时它的值可以是一个负数。默认情况下,规则的 salience 默认值为 0。如果不设置规则的 salience 属性,那么执行顺序是随机的。
- when 条件语句,就是当到达什么条件的时候
- then 根据条件的结果,来执行什么动作
- end 规则结束
这个规则文件就是描述了,当符合什么条件的时候,应该去做什么事情,每当规则有变动的时候,我们只需要修改规则文件,然后重新加载即可生效。
DroolsConfig
代码解释:首先通过请求获取 KieServices,通过KieServices获取KieContainer,KieContainer加载规则文件并获取KieSession,KieSession来执行规则引擎,KieSession是一个轻量级组建,每次执行完销毁。
package com.et.drools;
import org.kie.api.KieServices;
import org.kie.api.builder.*;
import org.kie.api.event.rule.ObjectDeletedEvent;
import org.kie.api.event.rule.ObjectInsertedEvent;
import org.kie.api.event.rule.ObjectUpdatedEvent;
import org.kie.api.event.rule.RuleRuntimeEventListener;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.kie.internal.io.ResourceFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
@Configuration
public class DroolsConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(DroolsConfig.class);
private KieServices kieServices = KieServices.Factory.get();
@Bean
public KieSession getKieSession() throws IOException {
LOGGER.info("Session created...");
KieSession kieSession = getKieContainer().newKieSession();
kieSession.setGlobal("showResults", new OutputDisplay());
kieSession.setGlobal("sh", new OutputDisplay());
kieSession.addEventListener(new RuleRuntimeEventListener() {
@Override
public void objectInserted(ObjectInsertedEvent event) {
System.out.println("Object inserted \n "
+ event.getObject().toString());
}
@Override
public void objectUpdated(ObjectUpdatedEvent event) {
System.out.println("Object was updated \n"
+ "New Content \n"
+ event.getObject().toString());
}
@Override
public void objectDeleted(ObjectDeletedEvent event) {
System.out.println("Object retracted \n"
+ event.getOldObject().toString());
}
});
return kieSession;
}
@Bean
public KieContainer getKieContainer() throws IOException {
LOGGER.info("Container created...");
getKieRepository();
KieBuilder kb = kieServices.newKieBuilder(getKieFileSystem());
kb.buildAll();
KieModule kieModule = kb.getKieModule();
return kieServices.newKieContainer(kieModule.getReleaseId());
}
private void getKieRepository() {
final KieRepository kieRepository = kieServices.getRepository();
kieRepository.addKieModule(new KieModule() {
@Override
public ReleaseId getReleaseId() {
return kieRepository.getDefaultReleaseId();
}
});
}
private KieFileSystem getKieFileSystem() throws IOException {
KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
kieFileSystem.write(ResourceFactory.newClassPathResource("order.drl"));
return kieFileSystem;
}
}
OrderController
package com.et.drools;
import org.kie.api.runtime.KieSession;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderController {
private final KieSession kieSession;
public OrderController(KieSession kieSession) {
this.kieSession = kieSession;
}
@PostMapping("/order")
public Order processRules(@RequestBody Order order) {
kieSession.insert(order);
kieSession.fireAllRules();
return order;
}
}
3.测试
启动Spring Boot应用
运行controller测试类
package com.et.drools;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Integration-level testing for {@link OrderController} object.
* Show the result of the Drools engine.
*/
@SpringBootTest
@AutoConfigureMockMvc
public class OrderControllerIT {
ObjectMapper objectMapper = new ObjectMapper();
@Autowired
private MockMvc mockMvc;
public static final String VISA = "VISA";
public static final String MASTER_CARD = "MASTERCARD";
public static final String ICICI = "ICICI";
@Test
public void shouldApplyVISARule() throws Exception {
//given
Order order = new Order();
order.setCardType(VISA);
order.setPrice(11000);
//when
MockHttpServletRequestBuilder request = createPostOrder(order);
String contentAsString = performAction(request);
Order resultOrder = objectMapper.readValue(contentAsString, Order.class);
//then
assertEquals(order.getCardType(), resultOrder.getCardType());
assertEquals(order.getPrice(), resultOrder.getPrice());
assertEquals(14, resultOrder.getDiscount());
}
@Test
public void shouldApplyMASTERCARDRule() throws Exception {
//given
Order order = new Order();
order.setCardType(MASTER_CARD);
order.setPrice(11000);
//when
MockHttpServletRequestBuilder request = createPostOrder(order);
String contentAsString = performAction(request);
Order resultOrder = objectMapper.readValue(contentAsString, Order.class);
//then
assertEquals(order.getCardType(), resultOrder.getCardType());
assertEquals(order.getPrice(), resultOrder.getPrice());
assertEquals(10, resultOrder.getDiscount());
}
@Test
public void shouldApplyICICIRule() throws Exception {
//given
Order order = new Order();
order.setCardType(ICICI);
order.setPrice(11000);
//when
MockHttpServletRequestBuilder request = createPostOrder(order);
String contentAsString = performAction(request);
Order resultOrder = objectMapper.readValue(contentAsString, Order.class);
//then
assertEquals(order.getCardType(), resultOrder.getCardType());
assertEquals(order.getPrice(), resultOrder.getPrice());
assertEquals(20, resultOrder.getDiscount());
}
private MockHttpServletRequestBuilder createPostOrder(Order order) throws JsonProcessingException {
return post("/order")
.content(objectMapper.writeValueAsBytes(order))
.contentType(MediaType.APPLICATION_JSON_VALUE)
.accept(MediaType.APPLICATION_JSON_VALUE);
}
private String performAction(MockHttpServletRequestBuilder request) throws Exception {
return mockMvc.perform(request)
.andExpect(status().isOk())
.andReturn()
.getResponse()
.getContentAsString();
}
}
返回结果
Object inserted
Order(name=null, cardType=ICICI, discount=0, price=11000)
==================================================
Text: sh ICICI has been added a discount, date: 2024-07-11
==================================================
==================================================
Text: showResults ICICI has been added a discount, date: 2024-07-11
==================================================
4.引用