Drools规则引擎

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;

/**
 * @author maple Yu
 * @version 1.0
 * @date 2023/9/1 下午3:42
 */
@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;

/**
 * @author maple Yu
 * @version 1.0
 * @date 2023/9/4 下午2:21
 */
@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;

/**
 * @author maple Yu
 * @version 1.0
 * @date 2023/8/24 上午11:12
 */
@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;

/**
 * @author maple Yu
 * @version 1.0
 * @date 2023/8/24 上午11:12
 */
@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"));
        }
    		// 存储数据库或者redis都可以
        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;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值