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所示。
图2-4 规则执行结果
规则test002并没有输出,但规则本身没有任何问题,上述的内容中强调了一个概念性知识点—Fact对象。rule test002之所以没有输出,不是因为规则语法出了问题,而且规则执行时没有将实体插入工作内存中。既然规则体没有语法问题,就一定是在执行规则时出现了误差。定位到KieSession ks =kc.newKieSession("testhelloworld")这段代码,看看KieSession提供了哪些方法。
KieSession是一个接口,当然这里并不是要研究它的接口,而是找它操作Fact对象的方法。通过IDE代码提示功能发现insert(Object object)方法,如图2-5所示。
图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所示。
图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所示。
图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所示。
图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所示。
图2-9 规则执行结果
可以看出代码rule test005已被执行。在规则代码中,每一个RHS部分都输出$p,它的作用是为了证明Fact对象是引用,而非克隆的,并且可以方便地对引入对象进行操作。
从控制台可以看出来,规则代码添加update后rule test005就被执行了。其实这是因为Rete算法的问题,简单说明一下,Rete算法会将规则中的内容先全部加载出来,在规则中看似把Person的name属性改变了,但本质上只是引用发生了改变,Fact对象并没有真正改变。当Fact对象真正发生改变时,规则将再次被执行,但这样是有风险的,容易产生死循环,其解决方案会在后面的章节中详细说明。