Drools规则引擎
1. Drools介绍
java语言开发的开源业务规则引擎 DROOLS (JBOSS RULES )具有一个易于访问企业策略、易于调整以及易于管理的开源业务规则引擎 ,符合业内标准,速度快、效率高。业务分析师或审核人员可以利用它轻松查看业务规则,从而检验是否已编码的规则执行了所需的业务规则。
2. 关键词
关键词 描述 package 包名,只限于逻辑上的管理,同⼀个包名下的查询或者函数可以直接调⽤ import ⽤于导⼊类或者静态⽅法 global 全局变量 function ⾃定义函数 query 查询 rule end 规则体
3. 设置属性
属性 描述 salience 定义规则优先级的整数。在激活队列中排序时,具有较高显着性值的规则将具有较高的优先级。例: salience 10 enabled 布尔值。选择该选项后,将启用规则。如果未选择该选项,则该规则将被禁用。例: enabled true date-effective 包含日期和时间定义的字符串。仅当当前日期和时间在date-effective属性之后时,才能激活该规则。例: date-effective “4-Sep-2018” date-expires 包含日期和时间定义的字符串。如果当前日期和时间在date-expires属性之后,则无法激活该规则。例: date-expires “4-Oct-2018” no-loop 布尔值。选择该选项后,如果规则的结果重新触发了先前满足的条件,则无法重新激活(循环)规则。如果未选择条件,则在这些情况下可以循环规则。例: no-loop true agenda-group 一个字符串,用于标识您要向其分配规则的议程组。通过议程组,您可以对议程进行分区,以提供对规则组的更多执行控制。只有已获得焦点的议程组中的规则才能被激活。例: agenda-group “GroupName” activation-group 一个字符串,用于标识您要向其分配规则的激活(或XOR)组。在激活组中,只能激活一个规则。触发的第一个规则将取消激活组中所有规则的所有未决激活。例: activation-group “GroupName” duration 一个长整数值,定义了如果仍满足规则条件,则可以激活规则的持续时间(以毫秒为单位)。例: duration 10000 timer 一个字符串,用于标识int(间隔)或cron计时器定义以调度规则。示例:(timer ( cron:* 0/15 * * * ? ) 每15分钟一次) calendar 一个石英调度规则日历定义。示例:(calendars “* * 0-7,18-23 ? * *” 不包括非营业时间) auto-focus 一个布尔值,仅适用于议程组中的规则。选择该选项后,下次激活该规则时,将自动为分配了该规则的议程组指定焦点。例: auto-focus true lock-on-active 一个布尔值,仅适用于规则流组或议程组中的规则。选择该选项后,下一次该规则的规则流组变为活动状态或该规则的议程组获得焦点时,将无法再次激活该规则,直到规则流组不再处于活动状态或议程组失去焦点为止。这是该no-loop属性的更强版本,因为匹配匹配规则的激活将被忽略,而与更新的来源无关(不仅是规则本身)。此属性是计算规则的理想选择,在计算规则中,您有许多修改事实的规则,并且您不希望任何规则重新匹配并再次触发。例: lock-on-active true ruleflow-group 标识规则流组的字符串。在规则流组中,只有在相关规则流激活了该组时,规则才能触发。例: ruleflow-group “GroupName” dialect 字符串,用于标识规则中的代码表达式JAVA或MVEL将其用作语言。默认情况下,该规则使用在程序包级别指定的方言。此处指定的任何方言都会覆盖规则的打包方言设置。例: dialect “JAVA”
4. 比较操作符
在 Drools当中共提供了⼗⼆种类型的⽐较操作符, 分别是: >、 >=、 <、 <=、 = =、 !=、 contains、 not contains、memberof、not memberof、matches、not matches;在这⼗⼆种类型的⽐较操作符当中,前六个是⽐较常⻅也是⽤的⽐较多的⽐较操作符
符号 说明 > ⼤于 < ⼩于 >= ⼤于等于 <= ⼩于等于 == 等于 != 不等于 contains 检查⼀个Fact对象的某个属性值是否包含⼀个指定的对象值 not contains 检查⼀个Fact对象的某个属性值是否不包含⼀个指定的对象值 memberOf 判断⼀个Fact对象的某个属性是否在⼀个或多个集合中,如果是字符串判断的标准就变为:该字符串是否包含Fact对象的字段内容了。当然这个过程并不会神奇的转换成数组什么的,仅仅类似于Java中String提供的contains方法的比较。 not memberOf 判断⼀个Fact对象的某个属性是否不在⼀个或多个集合中 matches 判断⼀个Fact对象的属性是否与提供的标准的Java正则表达式进⾏匹配 not matches 判断⼀个Fact对象的属性是否不与提供的标准的Java正则表达式进⾏匹配
5. 使用场景
针对复杂的业务规则代码处理,往往存在一下问题:
1 、硬编码实现业务规则难以维护;
2 、硬编码实现业务规则难以应对变化;
3 、业务规则发生变化需要修改代码,重启服务后才能生效;
于是规则引擎Drools 便诞生了
规则引擎的优势
1 、业务规则与系统代码分离,实现业务规则的集中管理
2 、在不重启服务的情况下可随时对业务规则进行扩展和维护
3 、可以动态修改业务规则,从而快速响应需求变更
4 、规则引擎是相对独立的,只关心业务规则,使得业务分析人员也可以参与编辑、维护系统的业务规则
5 、减少了硬编码业务规则的成本和风险
6 、使用规则引擎提供的规则编辑工具,使复杂的业务规则实现变得的简单
6. Drools 组成结构
Working Memory: 工作内存,drools规则引擎会从Working Memory中获取数据并和规则文件中定义的规则进行模式匹配,所以我们开发的应用程序只需要将我们的数据插入到Working Memory中即可; Rule Base:规则库,我们在规则文件中定义的规则都会被加载到规则库中 Inference Engine: 推理引擎
7. 涉及ja包
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-spring</artifactId>
<version>7.69.0.Final</version>
</dependency>
8. 实际应用
8.1 drl文件(静态)
import com. connext. soa. platform. price. dto. PersonDTO
import com. connext. soa. platform. price. enu. UserLevelDiscountEnum
import org. slf4j. Logger
global Logger logger;
rule "rule00"
salience 1
when
$p : PersonDTO ( userLevel == 0 )
then
$p. setHitRuleCode ( drools. getRule ( ) . getName ( ) ) ;
$p. setDiscountRate ( $p. getDiscountRate ( ) . subtract ( UserLevelDiscountEnum . getEnumByCode ( $p. getUserLevel ( ) ) . getDiscount ( ) ) ) ;
logger. info ( "命中规则 {} {}" , drools. getRule ( ) . getName ( ) , $p. getDiscountRate ( ) ) ;
end
rule "rule01"
salience 1
when
$p : PersonDTO ( userLevel == 1 )
then
$p. setHitRuleCode ( drools. getRule ( ) . getName ( ) ) ;
$p. setDiscountRate ( $p. getDiscountRate ( ) . subtract ( UserLevelDiscountEnum . getEnumByCode ( $p. getUserLevel ( ) ) . getDiscount ( ) ) ) ;
logger. info ( "命中规则 {} {}" , drools. getRule ( ) . getName ( ) , $p. getDiscountRate ( ) ) ;
end
rule "rule02"
salience 2
when
$p : PersonDTO ( userLevel == 2 )
then
$p. setHitRuleCode ( drools. getRule ( ) . getName ( ) ) ;
$p. setDiscountRate ( $p. getDiscountRate ( ) . subtract ( UserLevelDiscountEnum . getEnumByCode ( $p. getUserLevel ( ) ) . getDiscount ( ) ) ) ;
logger. info ( "命中规则 {} {}" , drools. getRule ( ) . getName ( ) , $p. getDiscountRate ( ) ) ;
end
rule "rule03"
salience 3
when
$p : PersonDTO ( userLevel == 3 )
then
$p. setHitRuleCode ( drools. getRule ( ) . getName ( ) ) ;
$p. setDiscountRate ( $p. getDiscountRate ( ) . subtract ( UserLevelDiscountEnum . getEnumByCode ( $p. getUserLevel ( ) ) . getDiscount ( ) ) ) ;
logger. info ( "命中规则 {} {}" , drools. getRule ( ) . getName ( ) , $p. getDiscountRate ( ) ) ;
end
rule "rule04"
salience 4
when
$p : PersonDTO ( userLevel == 4 )
then
$p. setHitRuleCode ( drools. getRule ( ) . getName ( ) ) ;
$p. setDiscountRate ( $p. getDiscountRate ( ) . subtract ( UserLevelDiscountEnum . getEnumByCode ( $p. getUserLevel ( ) ) . getDiscount ( ) ) ) ;
logger. info ( "命中规则 {} {}" , drools. getRule ( ) . getName ( ) , $p. getDiscountRate ( ) ) ;
end
rule "rule05"
salience 5
when
$p : PersonDTO ( userLevel == 5 )
then
$p. setHitRuleCode ( drools. getRule ( ) . getName ( ) ) ;
$p. setDiscountRate ( $p. getDiscountRate ( ) . subtract ( UserLevelDiscountEnum . getEnumByCode ( $p. getUserLevel ( ) ) . getDiscount ( ) ) ) ;
logger. info ( "命中规则 {} {}" , drools. getRule ( ) . getName ( ) , $p. getDiscountRate ( ) ) ;
end
rule "rule06"
salience 6
when
$p : PersonDTO ( userLevel == 6 )
then
$p. setHitRuleCode ( drools. getRule ( ) . getName ( ) ) ;
$p. setDiscountRate ( $p. getDiscountRate ( ) . subtract ( UserLevelDiscountEnum . getEnumByCode ( $p. getUserLevel ( ) ) . getDiscount ( ) ) ) ;
logger. info ( "命中规则 {} {}" , drools. getRule ( ) . getName ( ) , $p. getDiscountRate ( ) ) ;
end
8.1.1 UserLevelDiscountEnum.java
package com. connext. soa. platform. price. enu ;
import lombok. Getter ;
import lombok. NoArgsConstructor ;
import java. math. BigDecimal ;
@Getter
@NoArgsConstructor
@SuppressWarnings ( { "PMD" , "all" } )
public enum UserLevelDiscountEnum {
ALL ( 0 , BigDecimal . ZERO ) ,
EIP ( 1 , BigDecimal . valueOf ( 0.50 ) ) ,
SPORTY ( 2 , BigDecimal . valueOf ( 0.40 ) ) ,
TALENT ( 3 , BigDecimal . valueOf ( 0.30 ) ) ,
POPULARITY ( 4 , BigDecimal . valueOf ( 0.20 ) ) ,
STAR ( 5 , BigDecimal . valueOf ( 0.10 ) ) ,
TRAINEE ( 6 , BigDecimal . ZERO ) ,
;
private Integer userLevel;
private BigDecimal discount;
UserLevelDiscountEnum ( Integer userLevel, BigDecimal discount) {
this . userLevel = userLevel;
this . discount = discount;
}
public Integer getUserLevel ( ) {
return userLevel;
}
public BigDecimal getDiscount ( ) {
return discount;
}
public static UserLevelDiscountEnum getEnumByCode ( int userLevel) {
for ( UserLevelDiscountEnum ele : values ( ) ) {
if ( ele. getUserLevel ( ) == userLevel) {
return ele;
}
}
return null ;
}
}
8.1.2 Drl文件加载配置:
package com. connext. soa. platform. price. config ;
import org. kie. api. KieServices ;
import org. kie. api. builder. KieBuilder ;
import org. kie. api. builder. KieFileSystem ;
import org. kie. api. builder. KieModule ;
import org. kie. api. runtime. KieContainer ;
import org. kie. internal. io. ResourceFactory ;
import org. springframework. context. annotation. Bean ;
import org. springframework. context. annotation. Configuration ;
@Configuration
public class DroolsConfig {
private static final String RULES_RATE_RULE_DRL = "rules/ruleDiscount.drl" ;
private static final KieServices kieServices = KieServices . get ( ) ;
@Bean
public KieContainer kieContainer ( ) {
KieFileSystem kieFileSystem = kieServices. newKieFileSystem ( ) ;
kieFileSystem. write ( ResourceFactory . newClassPathResource ( RULES_RATE_RULE_DRL ) ) ;
KieBuilder kieBuilder = kieServices. newKieBuilder ( kieFileSystem) ;
kieBuilder. buildAll ( ) ;
KieModule kieModule = kieBuilder. getKieModule ( ) ;
return kieServices. newKieContainer ( kieModule. getReleaseId ( ) ) ;
}
}
8.1.3 规则命中
package com. connext. soa. platform. price. service ;
import com. alibaba. fastjson. JSONObject ;
import com. connext. soa. platform. price. dto. PersonDTO ;
import lombok. extern. slf4j. Slf4j ;
import org. kie. api. runtime. KieContainer ;
import org. kie. api. runtime. KieSession ;
import org. slf4j. Logger ;
import org. slf4j. LoggerFactory ;
import org. springframework. stereotype. Service ;
import javax. annotation. Resource ;
import java. math. BigDecimal ;
@Service
@Slf4j
public class HitRuleEngineService {
private static final Logger logger = LoggerFactory . getLogger ( "ProductRuleLogger" ) ;
private static final Integer LEVEL_NUM = 7 ;
@Resource
private KieContainer kieContainer;
public String dynamicLoadingDrlFile ( Integer crowdType) {
List < PersonDTO > list = new ArrayList < > ( ) ;
for ( int i = 0 ; i < LEVEL_NUM ; i++ ) {
PersonDTO personDTO = new PersonDTO ( ) ;
personDTO. setUserName ( "test0" + i) ;
personDTO. setUserLevel ( i) ;
personDTO. setDiscountRate ( BigDecimal . ONE ) ;
KieSession kieSession = kieContainer. newKieSession ( ) ;
kieSession. setGlobal ( "logger" , logger) ;
kieSession. insert ( personDTO) ;
kieSession. fireAllRules ( ) ;
kieSession. dispose ( ) ;
list. add ( personDTO) ;
}
return JSONObject . toJSONString ( list) ;
}
}
8.1.4 执行结果
8.2 动态加载生成规则库(将规则库储存在Cache中)
< ! -- Java 8 高性能缓存库-- >
< dependency>
< groupId> com. github. ben- manes. caffeine< / groupId>
< artifactId> caffeine< / artifactId>
< / dependency>
8.2.1 首先初始化缓存大小以及淘汰策略
package com. connext. soa. platform. price. config ;
import com. github. benmanes. caffeine. cache. Cache ;
import com. github. benmanes. caffeine. cache. Caffeine ;
import org. springframework. context. annotation. Bean ;
import org. springframework. context. annotation. Configuration ;
import java. util. concurrent. TimeUnit ;
@Configuration
public class CaffeineCacheConfig {
@Bean
public Cache < String , Object > caffeineCache ( ) {
return Caffeine . newBuilder ( )
. initialCapacity ( 100 )
. maximumSize ( 5000 )
. expireAfterAccess ( 24 , TimeUnit . HOURS )
. build ( ) ;
}
}
8.2.2 始化规则库初缓存并获取
package com. connext. soa. platform. price. listener ;
import com. alibaba. nacos. common. utils. MD5Utils ;
import com. github. benmanes. caffeine. cache. Cache ;
import lombok. extern. slf4j. Slf4j ;
import org. drools. core. impl. KnowledgeBaseImpl ;
import org. kie. api. io. ResourceType ;
import org. kie. api. runtime. KieSession ;
import org. kie. internal. utils. KieHelper ;
import org. springframework. stereotype. Component ;
import javax. annotation. Resource ;
@Component
@Slf4j
public class DroolUtils {
@Resource
Cache < String , Object > caffeineCache;
public KieSession getSession ( String agenda, String drl) {
KnowledgeBaseImpl kieBase = buildKieBase ( agenda, drl) ;
return kieBase. newKieSession ( ) ;
}
public KnowledgeBaseImpl buildKieBase ( String agenda, String drl) {
String md5Key = agenda + "_" + MD5Utils . md5Hex ( drl, "UTF-8" ) ;
KnowledgeBaseImpl kieBase = ( KnowledgeBaseImpl ) caffeineCache. getIfPresent ( md5Key) ;
if ( kieBase == null ) {
KieHelper helper = new KieHelper ( ) ;
helper. addContent ( drl, ResourceType . DRL ) ;
kieBase = ( KnowledgeBaseImpl ) helper. build ( ) ;
caffeineCache. put ( md5Key, kieBase) ;
}
return kieBase;
}
}
8.2.3 组装规则库
public String refreshDiscountRuleEngine ( ) {
StringBuilder ruleStr = new StringBuilder ( "" ) ;
ruleStr. append ( "import com.connext.soa.platform.price.dto.PersonDTO" ) ;
ruleStr. append ( System . getProperty ( "line.separator" ) ) ;
ruleStr. append ( "import com.connext.soa.platform.price.enu.UserLevelDiscountEnum" ) ;
ruleStr. append ( System . getProperty ( "line.separator" ) ) ;
ruleStr. append ( "global org.slf4j.Logger logger" ) ;
for ( int i = 0 ; i < 7 ; i++ ) {
ruleStr. append ( System . getProperty ( "line.separator" ) ) ;
ruleStr. append ( "rule \"" ) . append ( "rule0" + i) . append ( "\"" ) ;
ruleStr. append ( System . getProperty ( "line.separator" ) ) ;
ruleStr. append ( " when " ) ;
ruleStr. append ( System . getProperty ( "line.separator" ) ) ;
ruleStr. append ( " $p: PersonDTO(" ) ;
ruleStr. append ( "userLevel" ) . append ( " == " ) . append ( i) ;
ruleStr. append ( ")" ) ;
ruleStr. append ( System . getProperty ( "line.separator" ) ) ;
ruleStr. append ( " then " ) ;
ruleStr. append ( System . getProperty ( "line.separator" ) ) ;
ruleStr. append ( " $p.setHitRuleCode(drools.getRule().getName());" ) ;
ruleStr. append ( System . getProperty ( "line.separator" ) ) ;
ruleStr. append ( " $p.setDiscountRate(UserLevelDiscountEnum.getEnumByCode($p.getUserLevel()).getDiscount());" ) ;
ruleStr. append ( System . getProperty ( "line.separator" ) ) ;
ruleStr. append ( " logger.info(\"命中" ) . append ( " rule0" ) . append ( i) . append ( " discountRate " ) . append ( "$p.getDiscountRate" ) . append ( "\");" ) ;
ruleStr. append ( System . getProperty ( "line.separator" ) ) ;
ruleStr. append ( "end" ) ;
ruleStr. append ( System . getProperty ( "line.separator" ) ) ;
ruleStr. append ( System . getProperty ( "line.separator" ) ) ;
}
redisTemplate. opsForValue ( ) . set ( RULE_SUFFIX + 1 , ruleStr) ;
return ruleStr. toString ( ) ;
}
8.2.4 命中规则引擎
public String hitDiscountRuleEngine ( Integer crowdType) {
Object objRule = redisTemplate. opsForValue ( ) . get ( RULE_SUFFIX + 1 ) ;
if ( null != objRule) {
String ruleStr = objRule. toString ( ) ;
PersonDTO personDTO = new PersonDTO ( ) ;
personDTO. setUserLevel ( crowdType) ;
personDTO. setUserName ( "test01" ) ;
KieSession kieSession = droolUtils. getSession ( "1" , ruleStr) ;
kieSession. setGlobal ( "logger" , logger) ;
FactHandle fact = kieSession. insert ( personDTO) ;
kieSession. fireAllRules ( ) ;
return JSONObject . toJSONString ( personDTO) ;
}
return null ;
}