Drools的动态加载规则

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元

image

2.2 修改规则并重新加载

因为是动态加载,所以规则不是写在drl文件中而是存储在数据库中,这里我将数据库中的规则改为:

  • 1.总价大于1000减少1000元
  • 2.满3件打8折

image

刷新规则,访问:http://127.0.0.1:8999/reload/1

image

重新执行5000元价格,3件商品的请求:http://127.0.0.1:8999/order/5000&3

image

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);
    }
}
大T笔记所有文章均为本人原创,转载请您注明来源,并留下原文链接地址,是对我的尊重,也是对知识的尊重,谢谢!
大T笔记 » Drools的动态加载规则

大T笔记-我的个人互联网创业和自由职业之路

关于我 我的百宝箱
大T笔记-专注个人互联网创业和自由职业