drools规则引擎

drools规则引擎

简单地说将那些非常复杂多变但结构类似的业务逻辑编码,抽成规则来做,这些规则可以放在数据库中来动态加载。
官网用户指南“>http://docs.jboss.org/drools/release/_
http://docs.jboss.org/drools/release/7.0.0.Final/kie-api-javadoc/overview-summary.html

必须知道的相关组件概念
1)KieServices:kie整体的入口,可以用来创建Container,resource,fileSystem等。
(2)KieContainer: KieContainer就是一个KieBase的容器,可以根据kmodule.xml 里描述的KieBase信息来获取具体的KieSession。
(3)KieBase: KieBase就是一个知识仓库,包含了若干的规则、流程、方法等,在Drools中主要就是规则和方法,
KieBase本身并不包含运行时的数据之类的,如果需要执行规则KieBase中的规则的话,就需要根据KieBase创建KieSession。
(4)KieSession:就是一个跟Drools引擎打交道的会话,其基于KieBase创建,它会包含运行时数据,包含“事实 Fact”,并对运行时数据事实进行规则运算。
(5)KieModule:是一个包含了多个kiebase定义的容器。一般用kmodule.xml来表示。
(6)KieModuleModel:是kmodule.xml 文件的java表示,可以不用添加xml文件而是通过程序代码的方式来构建,
(7)KieFileSystem:一个内存文件系统,用于以编程方式定义组成KieModule的资源
(8)KieBuilder:当把所有的规则文件添加到KieFileSystem中后,通过把KieFileSystem传递给一个KieBuilder,
    可以构建出这个虚拟文件系统。其中有个buildAll()方法,会在构建好虚拟文件系统后,自动去构建KieModule
(9)KieRepository:是一个KieModule的仓库,包含了所有的KieModule描述,用一个ReleaseId做区分
(10)KieResources:是一个定义了如何获取资源的工厂,包括url,classpath,filesystem等
有状态session与无状态session
通过KieContainer可以获取KieSession,在kmodule.xml配置文件中如果不指定ksession的type默认也是有状态的session。
有状态session的特性是,我们可以通过建立一次session完成多次与规则引擎之间的交互,在没有调用dispose方法时,会维持会话状态。
使用KieSession的一般步骤为,获取session,insert Fact对象,然后调用fireAllRules进行规则匹配,随后调用dispose方法关闭session。

StatelessKieSession提供了一个更加便利的API,是对KisSession的封装,不再调用dispose方法进行session的关闭。
它隔离了每次与规则引擎的交互,不会再去维护会话的状态。同时也不再提供fireAllRules方法。

使用场景: 
(1)数据校验 
(2)运算 
(3)数据过滤 
(4)消息路由 
(5)任何能被描述成函数或公式的规则
quickstart

1、新建Fact类

public class Boy {
    /**
     * 年龄
     */
    private Integer age;
    /**
     * 能否进入网吧
     */
    private Boolean enterInternetCafe;
    //...
}

2、在/resource/META-INFO下新建kmodule.xml

<?xml version="1.0" encoding="UTF-8"?>
<kmodule xmlns="http://www.drools.org/xsd/kmodule">
    <!--
    Kmodule中可以包含一个到多个kbase,分别对应drl的规则文件。
    Kbase需要一个唯一的name,可以取任意字符串。
    packages为drl文件所在resource目录下的路径。注意区分drl文件中的package与此处的package不一定相同。多个包用逗号分隔。默认情况下会扫描resources目录下所有(包含子目录)规则文件。
    kbase的default属性,标示当前KieBase是不是默认的,如果是默认的则不用名称就可以查找到该KieBase,但每个module最多只能有一个默认KieBase。
    kbase下面可以有一个或多个ksession,ksession的name属性必须设置,且必须唯一
    -->
    <kbase name="rules" packages="com.huanmeiqi.drl">
        <ksession name="ksession-rule"/>
    </kbase>
</kmodule>

3、在/resource/rules/下新建规则文件,rules.drl

package rules;
import com.huanmeiqi.drools.demo.quickstart.Boy

rule yesEnter
    when
        boyObject : Boy(age >= 18)
    then
        boyObject.setEnterInternetCafe(true);
    end
rule noEnter
    when
        boyObject: Boy(age < 18)
    then
        boyObject.setEnterInternetCafe(false);
    end

4、测试

    @Test
    public void testRules() {
        /**
         * KieServices接口提供了很多方法,可以通过这些方法访问KIE关于构建和运行的相关对象,比如说可以获取KieContainer,
         * 利用KieContainer来访问KBase和KSession等信息;可以获取KieRepository对象,利用KieRepository来管理KieModule等。
         * KieServices就是一个中心,通过它来获取的各种对象来完成规则构建、管理和执行等操作.
         */
        KieServices ks = KieServices.Factory.get();// 通过单例创建KieServices

        /**
         * KieRepository是一个单例对象,它是存放KieModule的仓库,KieModule由kmodule.xml文件定义(当然不仅仅只是用它来定义)。
         */
        KieRepository kieRepository = ks.getRepository();

        /**
         * 可以理解KieContainer就是一个KieBase的容器。提供了获取KieBase的方法和创建KieSession的方法。
         * 其中获取KieSession的方法内部依旧通过KieBase来创建KieSession
         */
        KieContainer kieContainer = ks.getKieClasspathContainer();

        /**
         * KieBase就是一个知识仓库,包含了若干的规则、流程、方法等,在Drools中主要就是规则和方法,KieBase本身并不包含运行时的数据之类的,
         * 如果需要执行规则KieBase中的规则的话,就需要根据KieBase创建KieSession
         * 获取KieBase正常流程
         * KieBase kieBase = kieContainer.getKieBase();
         * KieSession kieSession = kieBase.newKieSession();
         * StatelessKieSession statelessKieSession = kieBase.newStatelessKieSession();
         * KieSession就是一个跟Drools引擎打交道的会话,其基于KieBase创建,它会包含运行时数据,包含“事实Fact”,并对运行时数据实时进行规则运算。
         * 创建KieBase是一个成本非常高的事情,KieBase会建立知识(规则、流程)仓库,而创建KieSession则是一个成本非常低的事情,所以KieBase会建立缓存,而KieSession则不必。
         */
        KieSession kieSession = kieContainer.newKieSession("ksession-rule");// 获取kmodule.xml中配置中名称为ksession-rule的session,默认为有状态的

        Boy boy = new Boy();
        boy.setAge(17);
        kieSession.insert(boy);
        int count = kieSession.fireAllRules();
        System.out.println("命中了" + count + "条规则!");
        System.out.println("男孩是否允许进入网吧" + boy.getEnterInternetCafe());

    }
集成SpringBoot且从数据库动态加载规则

1、将上面test中创建kieSession的步骤使用@Bean基于Java配置

@Configuration
public class DroolsAutoConfiguration {

    private static final String RULES_PATH = "rules/";

    @Bean
    @ConditionalOnMissingBean(KieFileSystem.class)
    public KieFileSystem kieFileSystem() throws IOException {
        KieFileSystem kieFileSystem = getKieServices().newKieFileSystem();
//        注释掉的地方本来是从项目的本地文件写入KieFileSystem(内存文件系统),我们这里不写入规则,而是从数据库动态加载
//        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();
    }
}

2、创建静态类便于动态更新kieFileSystem

public class KieUtils {

    private static KieFileSystem kieFileSystem;

    private static KieContainer kieContainer;

    private static KieSession kieSession;

    // get、set...
}

3、创建一个刷新规则的service类

@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();
    }

}

RulesMapper对应数据库rules表,注name即对应kfs.write(path,str)中的路径,content就是drl文件的内容

CREATE TABLE `rules` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `content` text NOT NULL,
  `description` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;

4、让程序一启动就去调用reloadAll方法

@Component
public class RulesCommandLineRunner implements CommandLineRunner {

    @Autowired
    private ReloadDroolsRules reloadDroolsRules;

    @Override
    public void run(String... args) throws Exception {
        reloadDroolsRules.reloadAll();
    }
}

5、提供对外更新规则的接口。这时候你可以去修改数据库表中的content为Boy(age >= 0)。然后调用/reload/{ruleId}更新规则,这时未成年人也可以进入网吧啦。
这个过程我们不需要重新启动程序,只需要修改数据库的规则就行。

@RestController
public class TestController {

//    @Autowired
//    private KieSession kieSession;

    @Resource
    private ReloadDroolsRules rules;

    @RequestMapping("/testBoy/{age}")
    public void testB(@PathVariable("age") Integer age) {
        KieSession kieSession = KieUtils.getKieSession();

        Boy boy = new Boy();
        boy.setAge(age);
        kieSession.insert(boy);
        int count = kieSession.fireAllRules();
        System.out.println("命中了" + count + "条规则!");
        System.out.println("男孩是否允许进入网吧" + boy.getEnterInternetCafe());
    }

    @RequestMapping("/reload/{ruleId}")
    public String reload(@PathVariable("ruleId") Integer ruleId) {
        rules.reload(ruleId);
        return "ok";
    }
}

代码下载地址git@gitee.com:gas_exchange/drools-demo.git

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值