drools规则引擎
简单地说将那些非常复杂多变但结构类似的业务逻辑编码,抽成规则来做,这些规则可以放在数据库中来动态加载。
官网用户指南“>http://docs.jboss.org/drools/release/_
http://docs.jboss.org/drools/release/7.0.0.Final/kie-api-javadoc/overview-summary.html
必须知道的相关组件概念
(1)KieServices:kie整体的入口,可以用来创建Container,resource,fileSystem等。
(2)KieContainer: KieContainer就是一个KieBase的容器,可以根据kmodule.xml 里描述的KieBase信息来获取具体的KieSession。
(3)KieBase: KieBase就是一个知识仓库,包含了若干的规则、流程、方法等,在Drools中主要就是规则和方法,
KieBase本身并不包含运行时的数据之类的,如果需要执行规则KieBase中的规则的话,就需要根据KieBase创建KieSession。
(4)KieSession:就是一个跟Drools引擎打交道的会话,其基于KieBase创建,它会包含运行时数据,包含“事实 Fact”,并对运行时数据事实进行规则运算。
(5)KieModule:是一个包含了多个kiebase定义的容器。一般用kmodule.xml来表示。
(6)KieModuleModel:是kmodule.xml 文件的java表示,可以不用添加xml文件而是通过程序代码的方式来构建,
(7)KieFileSystem:一个内存文件系统,用于以编程方式定义组成KieModule的资源
(8)KieBuilder:当把所有的规则文件添加到KieFileSystem中后,通过把KieFileSystem传递给一个KieBuilder,
可以构建出这个虚拟文件系统。其中有个buildAll()方法,会在构建好虚拟文件系统后,自动去构建KieModule
(9)KieRepository:是一个KieModule的仓库,包含了所有的KieModule描述,用一个ReleaseId做区分
(10)KieResources:是一个定义了如何获取资源的工厂,包括url,classpath,filesystem等
有状态session与无状态session
通过KieContainer可以获取KieSession,在kmodule.xml配置文件中如果不指定ksession的type默认也是有状态的session。
有状态session的特性是,我们可以通过建立一次session完成多次与规则引擎之间的交互,在没有调用dispose方法时,会维持会话状态。
使用KieSession的一般步骤为,获取session,insert Fact对象,然后调用fireAllRules进行规则匹配,随后调用dispose方法关闭session。
StatelessKieSession提供了一个更加便利的API,是对KisSession的封装,不再调用dispose方法进行session的关闭。
它隔离了每次与规则引擎的交互,不会再去维护会话的状态。同时也不再提供fireAllRules方法。
使用场景:
(1)数据校验
(2)运算
(3)数据过滤
(4)消息路由
(5)任何能被描述成函数或公式的规则
quickstart
1、新建Fact类
public class Boy {
/**
* 年龄
*/
private Integer age;
/**
* 能否进入网吧
*/
private Boolean enterInternetCafe;
//...
}
2、在/resource/META-INFO下新建kmodule.xml
<?xml version="1.0" encoding="UTF-8"?>
<kmodule xmlns="http://www.drools.org/xsd/kmodule">
<!--
Kmodule中可以包含一个到多个kbase,分别对应drl的规则文件。
Kbase需要一个唯一的name,可以取任意字符串。
packages为drl文件所在resource目录下的路径。注意区分drl文件中的package与此处的package不一定相同。多个包用逗号分隔。默认情况下会扫描resources目录下所有(包含子目录)规则文件。
kbase的default属性,标示当前KieBase是不是默认的,如果是默认的则不用名称就可以查找到该KieBase,但每个module最多只能有一个默认KieBase。
kbase下面可以有一个或多个ksession,ksession的name属性必须设置,且必须唯一
-->
<kbase name="rules" packages="com.huanmeiqi.drl">
<ksession name="ksession-rule"/>
</kbase>
</kmodule>
3、在/resource/rules/下新建规则文件,rules.drl
package rules;
import com.huanmeiqi.drools.demo.quickstart.Boy
rule yesEnter
when
boyObject : Boy(age >= 18)
then
boyObject.setEnterInternetCafe(true);
end
rule noEnter
when
boyObject: Boy(age < 18)
then
boyObject.setEnterInternetCafe(false);
end
4、测试
@Test
public void testRules() {
/**
* KieServices接口提供了很多方法,可以通过这些方法访问KIE关于构建和运行的相关对象,比如说可以获取KieContainer,
* 利用KieContainer来访问KBase和KSession等信息;可以获取KieRepository对象,利用KieRepository来管理KieModule等。
* KieServices就是一个中心,通过它来获取的各种对象来完成规则构建、管理和执行等操作.
*/
KieServices ks = KieServices.Factory.get();// 通过单例创建KieServices
/**
* KieRepository是一个单例对象,它是存放KieModule的仓库,KieModule由kmodule.xml文件定义(当然不仅仅只是用它来定义)。
*/
KieRepository kieRepository = ks.getRepository();
/**
* 可以理解KieContainer就是一个KieBase的容器。提供了获取KieBase的方法和创建KieSession的方法。
* 其中获取KieSession的方法内部依旧通过KieBase来创建KieSession
*/
KieContainer kieContainer = ks.getKieClasspathContainer();
/**
* KieBase就是一个知识仓库,包含了若干的规则、流程、方法等,在Drools中主要就是规则和方法,KieBase本身并不包含运行时的数据之类的,
* 如果需要执行规则KieBase中的规则的话,就需要根据KieBase创建KieSession
* 获取KieBase正常流程
* KieBase kieBase = kieContainer.getKieBase();
* KieSession kieSession = kieBase.newKieSession();
* StatelessKieSession statelessKieSession = kieBase.newStatelessKieSession();
* KieSession就是一个跟Drools引擎打交道的会话,其基于KieBase创建,它会包含运行时数据,包含“事实Fact”,并对运行时数据实时进行规则运算。
* 创建KieBase是一个成本非常高的事情,KieBase会建立知识(规则、流程)仓库,而创建KieSession则是一个成本非常低的事情,所以KieBase会建立缓存,而KieSession则不必。
*/
KieSession kieSession = kieContainer.newKieSession("ksession-rule");// 获取kmodule.xml中配置中名称为ksession-rule的session,默认为有状态的
Boy boy = new Boy();
boy.setAge(17);
kieSession.insert(boy);
int count = kieSession.fireAllRules();
System.out.println("命中了" + count + "条规则!");
System.out.println("男孩是否允许进入网吧" + boy.getEnterInternetCafe());
}
集成SpringBoot且从数据库动态加载规则
1、将上面test中创建kieSession的步骤使用@Bean基于Java配置
@Configuration
public class DroolsAutoConfiguration {
private static final String RULES_PATH = "rules/";
@Bean
@ConditionalOnMissingBean(KieFileSystem.class)
public KieFileSystem kieFileSystem() throws IOException {
KieFileSystem kieFileSystem = getKieServices().newKieFileSystem();
// 注释掉的地方本来是从项目的本地文件写入KieFileSystem(内存文件系统),我们这里不写入规则,而是从数据库动态加载
// for (Resource file : getRuleFiles()) {
// kieFileSystem.write(ResourceFactory.newClassPathResource(RULES_PATH + file.getFilename(), "UTF-8"));
// }
// 放到静态对象中便于获取,供其他使用的地方获取和更新
KieUtils.setKieFileSystem(kieFileSystem);
return kieFileSystem;
}
private Resource[] getRuleFiles() throws IOException {
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
return resourcePatternResolver.getResources("classpath*:" + RULES_PATH + "**/*.*");
}
@Bean
@ConditionalOnMissingBean(KieContainer.class)
public KieContainer kieContainer() throws IOException {
final KieRepository kieRepository = getKieServices().getRepository();
kieRepository.addKieModule(new KieModule() {
@Override
public ReleaseId getReleaseId() {
return kieRepository.getDefaultReleaseId();
}
});
KieBuilder kieBuilder = getKieServices().newKieBuilder(kieFileSystem());
kieBuilder.buildAll();
// 放到静态对象中便于获取,供其他使用的地方获取和更新
KieContainer kieContainer = getKieServices().newKieContainer(kieRepository.getDefaultReleaseId());
KieUtils.setKieContainer(kieContainer);
return kieContainer;
}
private KieServices getKieServices() {
return KieServices.Factory.get();
}
@Bean
@ConditionalOnMissingBean(KieBase.class)
public KieBase kieBase() throws IOException {
return kieContainer().getKieBase();
}
@Bean
@ConditionalOnMissingBean(KieSession.class)
public KieSession kieSession() throws IOException {
KieSession kieSession = kieContainer().newKieSession();
// 放到静态对象中便于获取
KieUtils.setKieSession(kieSession);
return kieSession;
}
@Bean
@ConditionalOnMissingBean(KModuleBeanFactoryPostProcessor.class)
public KModuleBeanFactoryPostProcessor kiePostProcessor() {
return new KModuleBeanFactoryPostProcessor();
}
}
2、创建静态类便于动态更新kieFileSystem
public class KieUtils {
private static KieFileSystem kieFileSystem;
private static KieContainer kieContainer;
private static KieSession kieSession;
// get、set...
}
3、创建一个刷新规则的service类
@Service
public class ReloadDroolsRules {
@Autowired
private RulesMapper rulesMapper;
private KieServices kieServices = KieServices.Factory.get();
/**
* 刷新某条规则
*
* @param ruleId
*/
public void reload(Integer ruleId) {
// 从数据库加载的规则
Rules rules = rulesMapper.selectByPrimaryKey(ruleId);
if (rules != null) {
KieFileSystem kfs = KieUtils.getKieFileSystem();
System.out.println(">>>>>" + kfs);
kfs.delete("src/main/resources/rules/" + rules.getName() + ".drl");
kfs.write("src/main/resources/rules/" + rules.getName() + ".drl", rules.getContent());
KieBuilder kieBuilder = kieServices.newKieBuilder(kfs).buildAll();
Results results = kieBuilder.getResults();
if (results.hasMessages(Message.Level.ERROR)) {
System.out.println(results.getMessages());
throw new IllegalStateException("### errors ###");
}
KieUtils.setKieContainer(kieServices.newKieContainer(getKieServices().getRepository().getDefaultReleaseId()));
System.out.println("新规则重载成功" + rules.getContent());
}
}
/**
* 加载所有规则
*/
public void reloadAll() {
List<Rules> rules = rulesMapper.selectAll();
KieFileSystem kfs = KieUtils.getKieFileSystem();
for (Rules rule : rules) {
System.out.println(">>>>>" + kfs);
kfs.delete("src/main/resources/rules/" + rule.getName() + ".drl");
kfs.write("src/main/resources/rules/" + rule.getName() + ".drl", rule.getContent());
}
KieBuilder kieBuilder = kieServices.newKieBuilder(kfs).buildAll();
Results results = kieBuilder.getResults();
if (results.hasMessages(Message.Level.ERROR)) {
System.out.println(results.getMessages());
throw new IllegalStateException("### errors ###");
}
KieUtils.setKieContainer(kieServices.newKieContainer(getKieServices().getRepository().getDefaultReleaseId()));
System.out.println("初始化规则成功");
}
private KieServices getKieServices() {
return KieServices.Factory.get();
}
}
RulesMapper对应数据库rules表,注name即对应kfs.write(path,str)中的路径,content就是drl文件的内容
CREATE TABLE `rules` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`content` text NOT NULL,
`description` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
4、让程序一启动就去调用reloadAll方法
@Component
public class RulesCommandLineRunner implements CommandLineRunner {
@Autowired
private ReloadDroolsRules reloadDroolsRules;
@Override
public void run(String... args) throws Exception {
reloadDroolsRules.reloadAll();
}
}
5、提供对外更新规则的接口。这时候你可以去修改数据库表中的content为Boy(age >= 0)。然后调用/reload/{ruleId}更新规则,这时未成年人也可以进入网吧啦。
这个过程我们不需要重新启动程序,只需要修改数据库的规则就行。
@RestController
public class TestController {
// @Autowired
// private KieSession kieSession;
@Resource
private ReloadDroolsRules rules;
@RequestMapping("/testBoy/{age}")
public void testB(@PathVariable("age") Integer age) {
KieSession kieSession = KieUtils.getKieSession();
Boy boy = new Boy();
boy.setAge(age);
kieSession.insert(boy);
int count = kieSession.fireAllRules();
System.out.println("命中了" + count + "条规则!");
System.out.println("男孩是否允许进入网吧" + boy.getEnterInternetCafe());
}
@RequestMapping("/reload/{ruleId}")
public String reload(@PathVariable("ruleId") Integer ruleId) {
rules.reload(ruleId);
return "ok";
}
}
代码下载地址git@gitee.com:gas_exchange/drools-demo.git