1 概述
上一节讲了Drools的具体使用语法:Drools语法详解,那么本节将介绍在SpringBoot中如何集成Drools,以及动态修改配置使其规则生效的案例。
如我们在电商平台买东西,一般会有购买金额满多少送赠品的活动,我们下面就将结合该案例来介绍Drools的使用,并且采用零配置的方式实现。
源代码地址:Github 源码
2 包依赖
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-core</artifactId>
<version>7.24.0.Final</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
<version>7.24.0.Final</version>
</dependency>
3 引擎代码
3.1 抽象规则引擎
首先我们定义一个抽象的规则引擎,这样有利于后面的灵活扩展,可以扩展出不同的规则引擎,如Drools,Groovy,js等等来实现不同的规则引擎
public abstract class AbstractRuleEngine {
protected void init() {
//模拟规则内容改变后,可以动态刷新,这部分可以通过数据库配置或者如Apollo配置改变后的监听器来完成动态刷新
ScheduledExecutorService executor = new ScheduledThreadPoolExecutor(1);
executor.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
refresh();
}
}, 0, 10, TimeUnit.SECONDS);
}
/**
* 规则引擎执行
* @param param 执行引擎所需参数
* @param cls 返回结果类型
* @param <T>
* @return
*/
public abstract <T> T execute(Object param, Class<T> cls);
/**
* 规则文件刷新
*/
public abstract void refresh();
注:这里的init方法用来初始化规则引擎数据,具体的规则内容可以配置到数据库,或通过配置中心如Apollo来完成配置的动态修改
3.2 扩展一个Drools规则引擎
public class DroolsEngine extends AbstractRuleEngine {
private static KieContainer kieContainer;
private static final String RULE_FILE_PATH = System.getProperty("user.dir") + File.separator + "drools" + File.separator;
private static final String RULE_FILE_NAME = "rules.drl";
private DroolsEngine() {
}
/**
* 引擎延迟加载
*/
private static class DroolsEngineHolder {
private static DroolsEngine engine = new DroolsEngine();
static {
engine.init();
initKieContainer(getRuleFile());
}
}
public static DroolsEngine getInstance() {
return DroolsEngineHolder.engine;
}
/**
* 初始化kieContainer容器
* @param ruleFile
*/
private static void initKieContainer(File ruleFile) {
KieServices kieServices = KieServices.Factory.get();
KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
kieFileSystem.write(ResourceFactory.newFileResource(ruleFile));
KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem);
Results results = kieBuilder.getResults();
if (results.hasMessages(Message.Level.ERROR)) {
log.error("drools engine init failed cause is:{}", JSON.toJSONString(results.getMessages(Message.Level.ERROR)));
}
KieModule kieModule = kieBuilder.getKieModule();
kieContainer = kieServices.newKieContainer(kieModule.getReleaseId());
}
private static File getRuleFile() {
try {
File fileDir = new File(RULE_FILE_PATH);
if (!fileDir.exists()) {
fileDir.mkdirs();
}
File[] files = fileDir.listFiles();
for (File file : files) {
if (file.exists()) {
FileUtils.deleteQuietly(file);
}
}
File ruleFile = new File(getRuleFileName());
ruleFile.createNewFile();
String content = System.getProperty(CommonConstants.RULE_CONTENT);
FileUtils.writeStringToFile(ruleFile, content, "utf-8");
return ruleFile;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private static String getRuleFileName() {
return RULE_FILE_PATH + System.currentTimeMillis() + RULE_FILE_NAME;
}
@Override
public <T> T execute(Object param, Class<T> cls) {
KieSession kieSession = kieContainer.newKieSession();
try {
T t = cls.newInstance();
kieSession.insert(t);
kieSession.insert(param);
kieSession.fireAllRules();
return t;
} catch (Exception e) {
e.printStackTrace();
} finally {
kieSession.dispose();
}
return null;
}
@Override
public void refresh() {
initKieContainer(getRuleFile());
}
}
注:如果规则变化了,通过监听器调用refresh刷新方法来操作具体的规则引擎更新规则内容
3.3 创建一个赠品执行结果对象
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class GiftStrategyResult {
private String orderId;
/**
* 赠送赠品编码
*/
private String giftCode;
}
3.3 订单类
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Order {
private String orderId;
private double price;
}
3.5 测试类
public class DroolsTest {
String updateRuleContent = "package rules;\n" +
"import org.example.engine.Order\n" +
"import org.example.engine.GiftStrategyResult\n" +
"\n" +
"dialect \"java\"\n" +
"\n" +
"rule \"price > 100\"\n" +
"salience 100\n" +
" when\n" +
" $strategyResult : GiftStrategyResult();\n" +
" $order : Order(price > 100)\n" +
" then\n" +
" $strategyResult.setOrderId($order.getOrderId());\n" +
" $strategyResult.setGiftCode(\"ZP01\");\n" +
"\n" +
" end\n" +
"\n" +
"rule \"90 < price < 100\"\n" +
"salience 99\n" +
" when\n" +
" $strategyResult : GiftStrategyResult();\n" +
" $order : Order(90 < price, price < 100);\n" +
" then\n" +
" $strategyResult.setOrderId($order.getOrderId());\n" +
" $strategyResult.setGiftCode(\"ZP03\");\n" +
"\n" +
" end";
@Before
public void before() {
//这段规则内容可以存到配置中心,或数据库中
System.setProperty(CommonConstants.RULE_CONTENT, "package rules;\n" +
"import org.example.engine.Order\n" +
"import org.example.engine.GiftStrategyResult\n" +
"\n" +
"dialect \"java\"\n" +
"\n" +
"rule \"price > 100\"\n" +
"salience 100\n" +
" when\n" +
" $strategyResult : GiftStrategyResult();\n" +
" $order : Order(price > 100)\n" +
" then\n" +
" $strategyResult.setOrderId($order.getOrderId());\n" +
" $strategyResult.setGiftCode(\"ZP01\");\n" +
"\n" +
" end\n" +
"\n" +
"rule \"90 < price < 100\"\n" +
"salience 99\n" +
" when\n" +
" $strategyResult : GiftStrategyResult();\n" +
" $order : Order(90 < price, price < 100);\n" +
" then\n" +
" $strategyResult.setOrderId($order.getOrderId());\n" +
" $strategyResult.setGiftCode(\"ZP02\");\n" +
"\n" +
" end");
}
@Test
public void execute() throws InterruptedException {
AbstractRuleEngine engine = DroolsEngine.getInstance();
Order order = new Order("SO_01", 95d);
GiftStrategyResult strategyResult = engine.execute(order, GiftStrategyResult.class);
System.out.println(strategyResult);
Order order2 = new Order("SO_02", 128d);
GiftStrategyResult strategyResult2 = engine.execute(order2, GiftStrategyResult.class);
System.out.println(strategyResult2);
System.setProperty(CommonConstants.RULE_CONTENT, updateRuleContent);
//睡眠一会,模拟规则内容更新后,动态刷新,看规则执行结果是否会改变
Thread.sleep(11000);
Order order3 = new Order("SO_01", 95d);
GiftStrategyResult strategyResult3 = engine.execute(order3, GiftStrategyResult.class);
System.out.println(strategyResult3);
}
}
注:测试用的规则脚本如下
package rules;
import org.example.engine.Order
import org.example.engine.GiftStrategyResult
dialect "java"
rule "price > 100"
salience 100
when
$strategyResult : GiftStrategyResult();
$order : Order(price > 100)
then
$strategyResult.setOrderId($order.getOrderId());
$strategyResult.setGiftCode("ZP01");
end
rule "90 < price < 100"
salience 99
when
$strategyResult : GiftStrategyResult();
$order : Order(90 < price, price < 100);
then
$strategyResult.setOrderId($order.getOrderId());
$strategyResult.setGiftCode("ZP03");
end
3.6 输出结果
我们发现规则已经正常执行了,而且当规则动态刷新后,赠品策略的执行结果也会随之变化
GiftStrategyResult(orderId=SO_01, giftCode=ZP02) GiftStrategyResult(orderId=SO_02, giftCode=ZP01) GiftStrategyResult(orderId=SO_01, giftCode=ZP03)
4 总结
我们将复杂多变的业务规则抽离通过规则引擎可以做到灵活配置,解决了我们频繁改代码发布服务的的痛点,小伙伴们如果在业务中有这样的场景,赶快用起来吧,哈哈,香不香,用了才知道。