1、为什么规则引擎Drools要做到动态生成规则
因为规则引擎的作用
一些多变的活动逻辑可以再不改变代码,不重新部署系统,如需求改需求,
一些通用但微变的逻辑,如人工智能的机器学习,达到ai修改数据库来微调自己的行为。
以上统称为 决策从逻辑剥离。
2、drools动态加载效果演示
先上演示效果,让直观的感受一下什么是动态加载。
2.1 启动项目
这里我设定两条优惠策略,按顺序执行:
- 1.总价大于1000减少300
- 2.满3件打8折
配置SpringBoot项目,写一个简单的控制器,
/order/{price}&{count}
price代表价格,count代表数量,启动SpringBoot项目。
访问:http://127.0.0.1:8999/order/5000&3
可以看到页面输出:优惠后的价格3200元
2.2 修改规则并重新加载
因为是动态加载,所以规则不是写在drl文件中而是存储在数据库中,这里我将数据库中的规则改为:
- 1.总价大于1000减少1000元
- 2.满3件打8折
刷新规则,访问:http://127.0.0.1:8999/reload/1
重新执行5000元价格,3件商品的请求:http://127.0.0.1:8999/order/5000&3
2.3 小结Drools的动态加载
可以看到我们在没有重启服务的情况下,通过修改数据库,和执行规则刷新,使新的优惠规则生效了,这就是Drool的动态加载策略,适用于规则频繁多变的场景。
3 Drools动态加载规则的实现
3.1业务代码
drl规则文件:
package order;
import com.lt.drools.demo.domain.Order
//两条优惠策略,按顺序执行
//1.总价大于1000减少300
//2.满3件打8折
rule "总价大于1000减少500"
salience 100
when
$order:Order(orginalPrice>1000);
then
$order.setDiscountPrice($order.getOrginalPrice()-1000);
end
rule "满3件打8折"
salience 99
when
$order:Order(count>=3);
then
$order.setDiscountPrice($order.getDiscountPrice()*0.8);
end
定义一个订单类Order
package com.lt.drools.demo.domain;
/**
* @author devtao
* @date 2021/11/10 10:54
* @description
*/
public class Order {
private Integer count; //商品数量
private double orginalPrice;//原始价格
private double discountPrice; //优惠价格
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
public double getOrginalPrice() {
return orginalPrice;
}
public void setOrginalPrice(double orginalPrice) {
this.orginalPrice = orginalPrice;
}
public double getDiscountPrice() {
return discountPrice;
}
public void setDiscountPrice(double discountPrice) {
this.discountPrice = discountPrice;
}
}
3.2 Drools的动态加载代码
Drools动态加载的代码,基本是固定写法,主要类是:kieBuilder 。
当Spring启动时候执行reloadAll()方法,当访问/reload/{rule_id}时执行reload(Integer ruleId) 方法,安装数据库中配置的规则ID来进行刷新。
package com.lt.drools.demo.service;
import com.lt.drools.demo.domain.Rules;
import com.lt.drools.demo.mapper.RulesMapper;
import com.lt.drools.demo.util.KieUtils;
import org.kie.api.KieServices;
import org.kie.api.builder.KieBuilder;
import org.kie.api.builder.KieFileSystem;
import org.kie.api.builder.Message;
import org.kie.api.builder.Results;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @author devtao
* @date 2021/11/10 9:47
* @description
*/
@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();
}
}
3.3 SpringBoot的配置
RulesCommandLineRunner.java
package com.lt.drools.demo.config;
import com.lt.drools.demo.service.ReloadDroolsRules;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
/**
* @author devtao
* @date 2021/11/10 13:55
* @description
*/
@Component
public class RulesCommandLineRunner implements CommandLineRunner {
@Autowired
private ReloadDroolsRules reloadDroolsRules;
@Override
public void run(String... args) throws Exception {
reloadDroolsRules.reloadAll();
}
}
DroolsAutoConfiguration.java
package com.lt.drools.demo.config;
import com.lt.drools.demo.util.KieUtils;
import org.kie.api.KieBase;
import org.kie.api.KieServices;
import org.kie.api.builder.*;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
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;
/**
* @author devtao
* @date 2021/11/10 9:24
* @description
*/
@Configuration
public class DroolsAutoConfiguration {
private static final String RULES_PATH = "rules/";
@Bean
@ConditionalOnMissingBean(KieFileSystem.class)
public KieFileSystem kieFileSystem() throws IOException {
KieFileSystem kieFileSystem = getKieServices().newKieFileSystem();
// 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();
}
}
DroolsDemoApplication.java
package com.lt.drools.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author devtao
*/
@SpringBootApplication
public class DroolsDemoApplication {
public static void main(String[] args) {
SpringApplication.run(DroolsDemoApplication.class, args);
}
}