Drools规则引擎技术指南
上QQ阅读APP看书,第一时间看更新

2.2 对象引用

对象引用就是之前提到的import引用模块,是规则内容中非常重要的组成部分,即引入所需要的Java类或方法。关键字import是用来导入规则文件需要使用的外部对象,这里的使用方法与Java相似,与Java不同的是,import不仅可以引入类,也可以导入类中的某一个可访问的静态方法[8],其内容为:

import com.drools.demo.point.PointDomain;  导入类
import function com.drools.demo.point.PointDomain.getById; 导入

创建Person类文件,其内容为:

package com.pojo;

public class Person {
    private String name;//姓名
    private String age;//年龄

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }
}

重新编辑规则文件,其内容为:

 package rules.rulesHello
 import com.pojo.Person;
        rule "test001"
              when
              then
               System.out.println("hello world");
        end

   rule "test002"
     when
        $p:Person();
     then
        System.out.println("输出引用对象"+$p);
  end

关注的点是rule "test002"规则,包路径下引用了刚刚创建的Person类,现在再次执行测试类(不做任何变动),查看控制台输出,其代码如图2-4所示。

025-1

图2-4 规则执行结果

规则test002并没有输出,但规则本身没有任何问题,上述的内容中强调了一个概念性知识点—Fact对象。rule test002之所以没有输出,不是因为规则语法出了问题,而且规则执行时没有将实体插入工作内存中。既然规则体没有语法问题,就一定是在执行规则时出现了误差。定位到KieSession ks =kc.newKieSession("testhelloworld")这段代码,看看KieSession提供了哪些方法。

KieSession是一个接口,当然这里并不是要研究它的接口,而是找它操作Fact对象的方法。通过IDE代码提示功能发现insert(Object object)方法,如图2-5所示。

025-2

图2-5 KieSession操作Fact对象的方法

重新编辑代码,实例化Person类,并使用insert方法再次调用,其代码为:

package com.rulesHello;

import com.pojo.Person;
import org.kie.api.KieServices;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;

public class RulesHello {
    public static void main(String[] args) {
        KieServices kss = KieServices.Factory.get();
        KieContainer kc = kss.getKieClasspathContainer();
        KieSession ks =kc.newKieSession("testhelloworld");
        Person person=new Person();
        ks.insert(person);
        int count = ks.fireAllRules();
        System.out.println("总执行了"+count+"条规则");
        ks.dispose();
    }
}

执行RulesHello类main函数,执行结果如图2-6所示。

026-1

图2-6 调用规则后的结果

本节主要讲解引用对象,通过例子说明引用对象后如何使用其属性。

【例1】查看对象实例中是否有一个名字是张三且年龄为30岁的人。

一般Java代码写法,其内容为:

public static void main(String[] args) {
        //实际代码肯定是通过其他方法传入的,这里为了测试直接将值固定
        Person person=new Person();
        person.setName("张三");
        person.setAge("30");
        if("张三".equals(person.getName()) && "30".equals(person.ge-tAge())){
            System.out.println("输出 传入的参数中确实有一位叫张三且年龄在30岁的人");
            //... 再处理其他业务
        }
    }

规则引擎写法,其内容为:

    rule "test003"
        when
           $p:Person(name=="张三",age==30);
        then
           System.out.println("输出 传入的参数中确实有一位叫张三且年龄在30岁的人");
    end

为保证代码的完整性,在hello.drl下方直接追加代码rule"test003",相应的Java代码也需要在规则执行的类中为Person实例添加属性内容,其代码为:

public static void main(String[] args) {
        KieServices kss = KieServices.Factory.get();
        KieContainer kc = kss.getKieClasspathContainer();
        KieSession ks =kc.newKieSession("testhelloworld");
        Person person=new Person();
        person.setName("张三");
        person.setAge("30");
        ks.insert(person);
        int count = ks.fireAllRules();
        System.out.println("总执行了"+count+"条规则");
        ks.dispose();
    }

结果如图2-7所示。

027-1

图2-7 规则执行结果

图2-7中执行了3条规则,是因为rule test002也成功执行了,只不过rule test002的约束只有Fact对象,并没有针对Fact属性有额外的约束。通过上例可以反映出一个问题,一般Java代码的写法,如果再加一个功能,如判断传入的对象Person不为空,就不得不在Java代码中添加一个if比较语句进行判断。虽然该实例中只有两个约束条件,但是一个业务健全的程序又何止这些,如风控系统,其中业务场景少则几千,多则上万,如果都要写在Java代码中,程序员估计就要“疯”了。而规则引擎的出现恰好能解决这类问题,对规则进行分组管理,极大地提高了程序的可维护性,将业务场景做成可配置的动态规则[9],真正实现业务与代码的解耦合。

认真观察规则编写的Java代码,做年龄判断时代码是需要加上双引号的,否则无法通过编译。而规则对于数字型字符串是会自动转化的。但要注意的是,只限于使用==号!

介绍规则体RHS部分时,提到了RHS是真正处理业务的部分,既然已经成功地将引用对象使用在规则中,就必须真正把RHS部分利用起来,下面举例说明。

【例2】将名为张三且年龄为30岁的人改为40岁。

修改规则代码为:

    rule "test004"
        when
           $p:Person(name=="张三",age==30);
        then
           $p.setAge("40");
           System.out.println("将名为张三且年龄为30岁的人改为40岁");
    end

调用执行规则代码为:

    public static void main(String[] args) {
        KieServices kss = KieServices.Factory.get();
        KieContainer kc = kss.getKieClasspathContainer();
        KieSession ks = kc.newKieSession("testhelloworld");
        Person person = new Person();
        person.setName("张三");
        person.setAge("30");
        ks.insert(person);
        int count = ks.fireAllRules();
        System.out.println("总执行了" + count + "条规则");
        System.out.println("输出修改后的Person age" + person.getAge());
        ks.dispose();
    }

执行结果如图2-8所示。

028-1

图2-8 规则执行结果

控制台中输出修改后age=40的信息,这一切看似没有问题,但结果真的被改变了吗?可以做个实验,添加一个规则,其内容为:

    rule "test004"
        when
           $p:Person(name=="张三",age==30);
        then
           $p.setAge("40");
           System.out.println("将名为张三且年龄为30岁的人改为40岁");
    end

    rule "test005"
        when
           $p:Person(name=="张三",age==40);
        then
           System.out.println("规则test005执行成功");
    end

观察代码中加粗的部分,其他代码不变,再次执行调用规则的Java代码。再次观察控制台,输出结果与上一次是一样的,rule test005没有被执行。规则代码在编译过程中是正确的,这在RHS部分就有说明,该部分是处理实际业务的,经常处理的函数如update、insert、delete等。那么先从update函数开始,将规则稍作修改,在RHS部分加上update函数操作,其内容为:

    rule "test004"
        when
           $p:Person(name=="张三",age==30);
        then
           $p.setAge(40);
           update($p);
           System.out.println("将名为张三且年龄为30岁的人改为40岁"+$p);
    end

执行结果如图2-9所示。

029-1

图2-9 规则执行结果

可以看出代码rule test005已被执行。在规则代码中,每一个RHS部分都输出$p,它的作用是为了证明Fact对象是引用,而非克隆的,并且可以方便地对引入对象进行操作。

从控制台可以看出来,规则代码添加update后rule test005就被执行了。其实这是因为Rete算法的问题,简单说明一下,Rete算法会将规则中的内容先全部加载出来,在规则中看似把Person的name属性改变了,但本质上只是引用发生了改变,Fact对象并没有真正改变。当Fact对象真正发生改变时,规则将再次被执行,但这样是有风险的,容易产生死循环,其解决方案会在后面的章节中详细说明。