如何零配置方式实现SpringBoot集成Drools

原创
2021/03/21 09:21
阅读数 596

1 概述

上一节讲了Drools的具体使用语法:Drools语法详解,那么本节将介绍在SpringBoot中如何集成Drools,以及动态修改配置使其规则生效的案例。

如我们在电商平台买东西,一般会有购买金额满多少送赠品的活动,我们下面就将结合该案例来介绍Drools的使用,并且采用零配置的方式实现。

源代码地址:Github 源码

包依赖

<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 总结

我们将复杂多变的业务规则抽离通过规则引擎可以做到灵活配置,解决了我们频繁改代码发布服务的的痛点,小伙伴们如果在业务中有这样的场景,赶快用起来吧,哈哈,香不香,用了才知道。

展开阅读全文
加载中

作者的其它热门文章

打赏
0
0 收藏
分享
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部