Drools是用Java语言编写的开放源码的规则引擎。
那什么是规则引擎呢?参考自 百度百科 里面的定义:
规则引擎由推理引擎发展而来,是一种嵌入在应用程序中的组件,实现了将业务决策从应用程序代码中分离出来,并使用预定义的语义模块编写业务决策。接受数据输入,解释业务规则,并根据业务规则做出业务决策。
Drools使用RETE算法对规则进行求值,在Drools6.0(当前最新版本)中还引进了PHREAK算法,Drools 允许使用声明方式表达业务逻辑。可以使用非 XML 的本地语言编写规则,从而便于学习和理解。并且,还可以将 Java 代码直接嵌入到规则文件中,这令 Drools 的学习更加吸引人。Drools 还具有其他优点:
*非常活跃的社区支持
*易用
*快速的执行速度
*在 Java 开发人员中流行
*与 Java Rule Engine API(JSR 94)兼容
Drools 规则是在 Java 应用程序上运行的,其要执行的步骤顺序由代码确定。为了实现这一点,Drools 规则引擎将业务规则转换成执行树,如下图所示:
如上图所示,每个规则条件分为小块,在树结构中连接和重用。每次将数据添加到规则引擎中时,它将在与此类似的树中进行求值,并到达一个动作节点,在该节点处,它们将被标记为准备执行特定规则的数据。
1.Pattern Matching:对新的数据和被修改的数据进行规则的匹配称为模式匹配.
2.Production Memory:被访问的规则.
3.Agenda:负责具体执行推理算法中被激发规则的结论部分,同时 Agenda 通过冲突决策策略管理这些冲突规则的执行顺序.
Drools 中规则冲突决策策略有:
(1) 优先级策略
(2) 复杂度优先策略
(4) 广度策略
(5) 深度策略
(6) 装载序号策略
(7) 随机策略
4.Working Memory:被推理机进行匹配的数据.
5.Inference Engine:进行匹配的引擎称为推理机.
推理机所采用的模式匹配算法有下列几种:
(1) Linear
(2) RETE
(3) Treat
(4) Leaps
进阶内容:Drools规则冲突决策策略和Drools匹配算法
数据被 assert 进 WorkingMemory 后,和 RuleBase 中的 rule 进行匹配(确切的说应该是 rule 的 LHS ),如果匹配成功这条 rule 连同和它匹配的数据(此时就叫做 Activation )一起被放入 Agenda ,等待 Agenda 来负责安排激发 Activation (其实就是执行 rule 的 RHS ),上图中的菱形部分就是在 Agenda 中来执行的, Agenda 就会根据冲突解决策略来安排 Activation 的执行顺序。
优点:
l声明式编程
l逻辑和数据分离
l速度和可扩展性
知识集中化
缺点:
l复杂性提高
l需要学习新的规则脚本语法
l引入新组件的风险
//Gradle构建
compile('org.kie:kie-spring:7.15.0.Final')
compile('com.thoughtworks.xstream:xstream:1.4.11.1')
其他可参考官方说明
template.drl
package com.hisun.lemon.mkt.rules;
import com.hisun.lemon.mkt.bo.MessageBO;
rule "Hello World"
when
m : MessageBO( status == MessageBO.HELLO, message : message )
then
System.out.println( message );
m.setMessage( "Goodbye cruel world" );
m.setStatus( MessageBO.GOODBYE );
update( m );
end
rule "GoodBye"
no-loop true
when
m : MessageBO( status == MessageBO.GOODBYE, message : message )
then
System.out.println( message );
m.setStatus(MessageBO.GAME_OVER);
m.setMessage("game over now!");
update( m );
end
rule "game over"
when
m : MessageBO( status == MessageBO.GAME_OVER)
then
System.out.println( m.getMessage() );
end
impl
private static KieContainer container = null;
private KieSession statefulKieSession = null;
@Override
public void helloWorld(MessageBO messageBO) {
KieServices kieServices = KieServices.Factory.get();
container = kieServices.getKieClasspathContainer();
statefulKieSession = container.newKieSession("myAgeSession");
statefulKieSession.insert(messageBO);
Integer count=statefulKieSession.fireAllRules();
statefulKieSession.dispose();
}
package com.hisun.lemon.mkt.bo;
public class MessageBO {
public static final int HELLO = 0;
public static final int GOODBYE = 1;
public static final int GAME_OVER = 2;
private String message;
private int status;
public String getMessage() {
return this.message;
}
public void setMessage(String message) {
this.message = message;
}
public int getStatus() {
return this.status;
}
public void setStatus( int status ) {
this.status = status;
}
}
输入:
{
"body": {
"message": "Hi Zain",
"status": 0
}
}
输出:
Hi Zain
Goodbye cruel world
game over now!
也可参考官方说明
Drools语法官方文档
Drools简书
动态加载规则:不直接写drl规则文件,从数据库中读取数据,然后拼接成字符串,再加载到work memory中。下面是执行代码:
KieConfiguration
package com.hisun.lemon.mkt.config;
import com.hisun.lemon.mkt.utils.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.internal.io.ResourceFactory;
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
* @create 2019-01-03 6:57 PM
* 此类主要用来初始化Drools的配置,其中需要注意的是对KieContainer和KieSession的初始化之后都将其设置到KieUtils类中。
**/
@Configuration
public class KieConfiguration {
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"));
}
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() {
public ReleaseId getReleaseId() {
return kieRepository.getDefaultReleaseId();
}
});
KieBuilder kieBuilder = getKieServices().newKieBuilder(kieFileSystem());
kieBuilder.buildAll();
KieContainer kieContainer = getKieServices().newKieContainer(kieRepository.getDefaultReleaseId());
KieUtils.setKieContainer(kieContainer);
return getKieServices().newKieContainer(kieRepository.getDefaultReleaseId());
}
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();
}
}
@RedisCacheable(cacheNames = "${lemon.cache.cacheName.prefix}.RuleFile", key = "#ruleVer")
String getRule(Integer ruleVer) {
List couponRules = couponRuleDao.findAll(RuleTypEnum.SendCoupon.getValue());
String ruleHeader = "package com.hisun.lemon.mkt.rule;\ndialect \"java\";\n\nimport com.hisun.lemon.mkt.bo.RuleParamReqBO;\n"
+ "import com.hisun.lemon.mkt.dao.IMktUserCouponDao;\n"
+ "import com.hisun.lemon.mkt.dao.IMktCouponTypeDao;\n"
+ "import com.hisun.lemon.mkt.utils.SummaryUtils;\n\n"
+ "global IMktUserCouponDao userCouponDao;\n"
+ "global IMktCouponTypeDao couponTypeDao\n"
+ "global SummaryUtils summaryUtils\n";
if (JudgeUtils.isEmpty(couponRules)) {
//没有生效的营销活动
LemonException.throwBusinessException("MKT10006");
}
StringBuffer ruleBody = getCouponRule(couponRules);
StringBuffer rule = new StringBuffer(ruleHeader).append(ruleBody);
//打印规则明细
logger.info("ruleFile:\n" + rule);
return rule.toString();
}
StringBuffer getCouponRule(List couponRules) {
String couponId = "";
String categoryCode = "";
StringBuffer rule = new StringBuffer();
String ruleEnd = "msg.addCouponIdList(\"";
while (couponRules.size() != 0) {
MktCouponRuleDO couponRule = couponRules.get(0);
String tempCouponId = couponRule.getCouponId();
String ruleOperater = couponRule.getRuleOperater();
if (!couponId.equals("")) {
if (!couponId.equals(tempCouponId)) {
rule = rule.append(")")
.append(getActRule(couponId))
.append(");\nthen\n")
.append(ruleEnd)
.append(couponId)
.append("\");\n")
.append("end\n");
}
}
//处理规则命名
if (!tempCouponId.equals(couponId)) {
categoryCode = "";
rule = rule.append("\nrule \"coupon_")
.append(couponRule.getRuleTyp())
.append("_")
.append(tempCouponId)
.append("\"\nwhen\nmsg:RuleParamReqBO(");
}
//处理规则条件部分
String tempCategoryCode = couponRule.getCategoryCode();
if (!categoryCode.equals("")) {
if (tempCategoryCode.equals(categoryCode)) {
rule = rule.append("||");
} else {
rule = rule.append(")&&");
}
} else {
rule = rule.append("(msg.getIsUseCoupon() == ")
.append(convertRuleType(couponRule.getRuleTyp()))
.append(")&&");
}
if (!rule.substring(rule.length() - 2).equals("||")) {
rule = rule.append("(");
}
rule = rule.append("msg.getReqParam(\"")
.append(couponRule.getRuleParam())
.append("\")");
if (ruleOperater.equals("in")) {
rule = rule.append(" memberOf ")
.append(gson.toJson(couponRule.getRuleValue().split(",")));
} else if (ruleOperater.equals("bt")) {
rule = rule.append(">=")
.append(couponRule.getRuleValue().split(",")[0])
.append("&&msg.getReqParam(\"")
.append(couponRule.getRuleParam())
.append("\")<=")
.append(couponRule.getRuleValue().split(",")[1]);
} else {
rule = rule.append(couponRule.getRuleOperater())
.append(couponRule.getRuleValue());
}
//处理规则结果部分
if (couponRules.size() == 1) {
rule = rule.append(")")
.append(getActRule(tempCouponId))
.append(");\nthen\n")
.append(ruleEnd)
.append(tempCouponId)
.append("\");\n")
.append("end\n");
}
categoryCode = tempCategoryCode;
couponId = tempCouponId;
couponRules.remove(couponRule);
}
return rule;
}
缓存规则不影响速度
风控系统(规则很多,而且容易变动,做互联网金融的同志深有体会)
活动营销系统(活动很多种,集积分送礼品,抽奖送礼品,竞争成功送礼品等等不同形式,这里可以变成不同的规则)
商品折扣系统(同一个商品,不同的用户,每个用户有不同的折扣优惠)
积分系统