知识图谱程序员首页投稿(暂停使用,暂停投稿)

Drools入门

2018-01-03  本文已影响1423人  xiaolyuh

规则引擎

相关介绍

规则引擎起源于基于规则的专家系统,而基于规则的专家系统又是专家系统的其中一个分支。专家系统属于人工智能的范畴,它模仿人类的推理方式,使用试探性的方法进行推理,并使用人类能理解的术语解释和证明它的推理结论。

利用它就可以在应用系统中分离商业决策者的商业决策逻辑和应用开发者的技术决策,并把这些商业决策放在中心数据库或其他统一的地方,让它们能在运行时可以动态地管理和修改,从而为企业保持灵活性和竞争力提供有效的技术支持。

在需求里面我们往往把约束,完整性,校验,分支流等都可以算到业务规则里面。在规则引擎里面谈的业务规则重点是谈当满足什么样的条件的时候,需要执行什么样的操作。因此一个完整的业务规则包括了条件和触发操作两部分内容。而引擎是事物内部的重要的运行机制,规则引擎即重点是解决规则如何描述,如何执行,如何监控等一系列问题。

规则引擎由推理引擎发展而来,是一种嵌入在应用程序中的组件,实现了将业务决策从应用程序代码中分离出来,并使用预定义的语义模块编写业务决策。接受数据输入,解释业务规则,并根据业务规则做出业务决策。

java开源的规则引擎有:Drools、Easy Rules、Mandarax、IBM ILOG。使用最为广泛并且开源的是Drools。

规则引擎的优点

Drools 介绍

Drools 是一个基于Charles Forgy’s的RETE算法的,易于访问企业策略、易于调整以及易于管理的开源业务规则引擎,符合业内标准,速度快、效率高。 业务分析师人员或审核人员可以利用它轻松查看业务规则,从而检验是否已编码的规则执行了所需的业务规则。

Drools 是用Java语言编写的开放源码规则引擎,使用Rete算法对所编写的规则求值。Drools允许使用声明方式表达业务逻辑。可以使用非XML的本地语言编写规则,从而便于学习和理解。并且,还可以将Java代码直接嵌入到规则文件中,这令Drools的学习更加吸引人。

Drools优点:

Drools相关概念:

Drools通过 事实、规则和模式相互组合来完成工作,drools在开源规则引擎中使用率最广,但是在国内企业使用偏少,保险、支付行业使用稍多。

Drools的基本语法

一个规则可以包含三个部分:

规则示例:

rule "name"
       no-loop true
       lock-on-active true
       when
               $message:Message(status == 0)
       then
               System.out.println("fit");
               $message.setStatus(1);
               update($message);
end

规则的条件部分,即LHS部分

when
         eval(true)
         $customer:Customer()
         $message:Message(status==0)

上述罗列了三个条件,当前规则只有在这三个条件都匹配的时候才会执行RHS部分。

Drools中条件操作符

Drools提供了十二中类型比较操作符:< 、<=、>、>=、==、!=、contains、not contains、memberOf、not memberOf、matches、not matches,并且这些条件都可以组合使用。

  $order:Order(name=="qu")
  $message:Message((status==0 ||  (status > 1 && status <=100)) && orders contains $order && $order.name=="qu")

注意:如果条件全部是 &&关系,可以使用“,”来替代,但是两者不能混用

规则的结果部分

当规则条件满足,则进入规则结果部分执行,结果部分可以是纯java代码,比如:

then
       System.out.println("OK"); //会在控制台打印出ok
end
function void console {
   System.out.println();
   StringUtils.getId();// 调用外部静态方法,StringUtils必须使用import导入,getId()必须是静态方法
}
declare Address
 @author(quzishen) // 元数据,仅用于描述信息
 @createTime(2011-1-24)
 city : String @maxLengh(100)
 postno : int
end

'@'是元数据定义,用于描述数据的数据~,没什么执行含义。
你可以在RHS部分中使用Address address = new Address()的方法来定义一个对象。

更多的规则语法,可以参考其他互联网资料,推荐:
http://wenku.baidu.com/view/a6516373f242336c1eb95e7c.html

pom中加入依赖

<!-- Drools规则引擎包 start -->
<dependency>
    <groupId>org.kie</groupId>
    <artifactId>kie-api</artifactId>
    <version>6.5.0.Final</version>
</dependency>
<dependency>
    <groupId>org.drools</groupId>
    <artifactId>drools-compiler</artifactId>
    <version>6.5.0.Final</version>
</dependency>
<!-- Drools规则引擎包 end -->

案例

下面是一个增加积分的列子,主要实现用户购买的金额达到一定量后送积分,具体的规则如下:

100元以下, 不加分 
100元-500元 加100分 
500元-1000元 加500分 
1000元 以上 加1000分

有两种实现方案,一种是规则直接写在文件中,一种是规则写在数据库中。

规则写在文件中

Point.drl 文件

在src/main/resources目录下新建rules.point文件夹,新建Point.drl文件

//这里的package属性是一个逻辑区分,不需要与这个文件路径相对应
package point.rules

import com.xiaolyuh.domain.model.Order

rule "zero"
    no-loop true
    lock-on-active true
    salience 1
    when
        $s : Order(amout <= 100)
    then
        $s.setScore(0);
        System.out.println("不加积分");
        update($s);
end

rule "add100"
    no-loop true
    lock-on-active true
    salience 1
    when
        $s : Order(amout > 100 && amout <= 500)
    then
        $s.setScore(100);
        System.out.println("加100积分");
        update($s);
end

rule "add500"
    no-loop true
    lock-on-active true
    salience 1
    when
        $s : Order(amout > 500 && amout <= 1000)
    then
        $s.setScore(500);
        System.out.println("加500积分");
        update($s);
end

rule "add1000"
    no-loop true
    lock-on-active true
    salience 1
    when
        $s : Order(amout > 1000)
    then
        $s.setScore(1000);
        System.out.println("加500积分");
        update($s);
end

kmodule.xml

这里需要有一个配置文件告诉代码规则文件drl在哪里,在drools中这个文件就是kmodule.xml,放置到resources/META-INF目录下,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<kmodule xmlns="http://jboss.org/kie/6.0.0/kmodule">

    <!-- 这里的packages属性就是规则文件的文件路径 -->
    <kbase name="point_rule" packages="rules.point">
        <ksession name="point_ksession"/>
    </kbase>

</kmodule>

以下对配置说明进行简单说明:

DroolsUtil

通过该类加载kmodule.xml文件,并获得KieSession。

/**
 * @author yuhao.wang
 */
public class DroolsUtil {
    public static final Logger log = LoggerFactory.getLogger(DroolsUtil.class);

    /**
     * 线程安全单例
     */
    private static volatile KieServices kieServices = KieServices.Factory.get();
    /**
     * KieBase容器,线程安全单例
     */
    private static volatile KieContainer kieContainer;

    /**
     * 加载KieContainer容器
     */
    public static KieContainer loadKieContainer() throws RuntimeException {
        //通过kmodule.xml 找到规则文件,这个文件默认放在resources/META-INF文件夹
        log.info("准备创建 KieContainer");

        if (kieContainer == null) {
            log.info("首次创建:KieContainer");
            // 设置drools的日期格式
            System.setProperty("drools.dateformat", "yyyy-MM-dd HH:mm:ss");
            //线程安全
            synchronized (DroolsUtil.class) {
                if (kieContainer == null) {
                    // 创建Container
                    kieContainer = kieServices.getKieClasspathContainer();
                    // 检查规则文件是否有错
                    Results results = kieContainer.verify();
                    if (results.hasMessages(Message.Level.ERROR)) {
                        StringBuffer sb = new StringBuffer();
                        for (Message mes : results.getMessages()) {
                            sb.append("解析错误的规则:").append(mes.getPath()).append(" 错误位置:").append(mes.getLine()).append(";");
                        }
                        throw new RuntimeException(sb.toString());
                    }
                }
            }

        }
        log.info("KieContainer创建完毕");
        return kieContainer;
    }

    /**
     * 根据kiesession 名称创建KieSession ,每次调用都是一个新的KieSession
     * @param name kiesession的名称
     * @return 一个新的KieSession,每次使用后要销毁
     */
    public static KieSession getKieSessionByName(String name) {
        if (kieContainer == null) {
            kieContainer = loadKieContainer();
        }
        KieSession kieSession;
        try {
            kieSession = kieContainer.newKieSession(name);
        } catch (Exception e) {
            log.error("根据名称:" + name + " 创建kiesession异常");
            throw new RuntimeException(e);
        }
        return kieSession;
    }

}

执行规则

DroolsScoreExampleTest.java

/**
     * 计算额外积分金额 规则如下: 订单原价金额
     * 100以下, 不加分
     * 100-500 加100分
     * 500-1000 加500分
     * 1000 以上 加1000分
     *
     * @param args
     * @throws Exception
     */
    public static final void main(final String[] args) throws Exception {
        // 通过工具类去获取KieSession
        KieSession ksession = DroolsUtil.getKieSessionByName("point_ksession");

        List<Order> orderList = getInitData();
        try {
            for (int i = 0; i < orderList.size(); i++) {
                Order o = orderList.get(i);
                ksession.insert(o);
                ksession.fireAllRules();
                // 执行完规则后, 执行相关的逻辑
                addScore(o);
            }
        } catch (Exception e) {

        } finally {
            ksession.destroy();
        }

    }

基于数据库的方式

表结构

CREATE TABLE `rule` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `rule_key` varchar(255) NOT NULL DEFAULT '' COMMENT '规则编码',
  `version` varchar(255) NOT NULL DEFAULT '' COMMENT '规则编码',
  `content` varchar(2048) NOT NULL DEFAULT '' COMMENT '规则n内容',
  `create_time` datetime NOT NULL,
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_rule_key` (`rule_key`) USING BTREE,
  KEY `uk_update_time` (`update_time`) USING BTREE,
  KEY `uk_version` (`version`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

获取KieSession

RuleServiceImpl.java

@Override
public KieSession getKieSessionByName(String ruleKey) {
    StatefulKnowledgeSession kSession = null;
    try {
        long startTime = System.currentTimeMillis();
        // TODO 可以缓存到本地,不用每次都去查数据库
        Rule rule = ruleMapper.findByRuleKey(ruleKey);

        KnowledgeBuilder kb = KnowledgeBuilderFactory.newKnowledgeBuilder();
        // 装入规则,可以装入多个
        kb.add(ResourceFactory.newByteArrayResource(rule.getContent().getBytes("utf-8")), ResourceType.DRL);

        // 检查错误
        KnowledgeBuilderErrors errors = kb.getErrors();
        for (KnowledgeBuilderError error : errors) {
            System.out.println(error);
        }

        // TODO 没有找到替代方法
        KnowledgeBase kBase = KnowledgeBaseFactory.newKnowledgeBase();
        kBase.addKnowledgePackages(kb.getKnowledgePackages());

        // 创建kSession
        kSession = kBase.newStatefulKnowledgeSession();
        long endTime = System.currentTimeMillis();
        logger.info("获取kSession耗时:{}", endTime - startTime);
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }
    return kSession;
}

执行规则

RuleController.java

@ResponseBody
@RequestMapping("address")
public Object test(int num) {
    AddressCheckResult result = new AddressCheckResult();
    Address address = new Address();
    address.setPostcode(generateRandom(num));

    String ruleKey = "score";
    KieSession kieSession = ruleService.getKieSessionByName(ruleKey);
    int ruleFiredCount = 0;
    try {
        kieSession.insert(address);
        kieSession.insert(result);
        ruleFiredCount = kieSession.fireAllRules();
    } catch (Exception e) {
        logger.warn(e.getMessage(), e);
    } finally {
        if (kieSession != null) {
            kieSession.destroy();
        }
    }
    System.out.println("触发了" + ruleFiredCount + "条规则");

    if (result.isPostCodeResult()) {
        System.out.println("规则校验通过");
    }

    return "ok";
}

参考:

源码地址:
https://github.com/wyh-spring-ecosystem-student/spring-boot-student/tree/releases

spring-boot-student-drools 工程

上一篇下一篇

猜你喜欢

热点阅读