参考Drools官方文档(3.1 KIE Session和3.2 Inference and truth maintenance in the Drools engine),学习关于Kie Session和Truth maintenace的内容。这两节内容虽然很基础,但是感觉官方文档说的还是不够明了,尤其是Stateless Session和Stateful Session的区别,和insert()和insertLogical()的区别,官方文档给出的样例没有很好的体现出来,下面我尝试用我自己的例子来理解一下
1 Stateless Session和Stateful Session的区别
按照官方解释,Stateless Session每次调用会话是独立的,不保存上次调用的数据状态,而Stateful Session正相反。相对来说,Stateful Session更加容易理解,也更为常用,我们先写一个Stateful Session的例子。
先定义一个Person类
package drools.samples.domain;
public class Person {
private String name;
private int age;
public Person(String name,int age){
this.name=name;
this.age=age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
再定义一个规则文件statefulSampleRule.drl,逻辑很简单,如果一个person小于18岁,打印"xxx is a child",否则打印"xxx is a adult"。
package drools.samples.rules.statefulSampleRule
import drools.samples.domain.Person
rule "Age < 18"
when
$a : Person(age < 18)
then
System.out.println($a.getName()+" is a child");
end
rule "Age > 18"
when
$a : Person(age >= 18)
then
System.out.println($a.getName()+" is a adult");
end
在kmodule.xml里定义一个名为"statefulTest"的ksession
写个执行程序,我们先把这个person对象的年龄设成16,执行下rule,然后我们再动态地更新年龄为25,重新执行下rule
KieServices kieServices = KieServices.Factory.get();
KieContainer kContainer = kieServices.getKieClasspathContainer();
KieSession kSession = kContainer.newKieSession("statefulTest");
Person p1 = new Person("Tom", 16);
FactHandle factHandle=kSession.insert(p1);
kSession.fireAllRules();
p1.setAge(25);
kSession.update(factHandle, p1,"age");
kSession.fireAllRules();
运行结果
所以在一个stateful session里,允许我们更新fact的状态并重新触发规则evaluate。
我们继续写个stateless session的例子。定义一个规则文件statelessSampleRule.drl,其实和statefulSampleRule.drl一样
package drools.samples.rules.statelessSampleRule
import drools.samples.domain.Person
rule "Age < 18"
when
$a : Person(age < 18)
then
System.out.println($a.getName()+" is a child");
end
rule "Age > 18"
when
$a : Person(age >= 18)
then
System.out.println($a.getName()+" is a adult");
end
在kmodule.xml里定义一个名为"statelessTest"的ksession,主要加上"stateless"的属性
执行程序和结果
KieServices kieServices = KieServices.Factory.get();
KieContainer kContainer = kieServices.getKieClasspathContainer();
StatelessKieSession kSession = kContainer.newStatelessKieSession("statelessTest");
Person p1 = new Person("Tom", 16);
kSession.execute(p1);
你可能会问为什么我没有在stateless的例子里去更新age,这是因为查看StatelessKieSession的接口,你会发现根本就不提供更新fact的操作,并且stateless session的接口只提供了execute接口,因此stateless session的不同点就表现在这里,你无法在一个stateless session里去更新fact,一个fact只会有一次存入Working memory的操作。
我看网上有人认为stateless session在执行规则时会忽略fact的更新从而不重新触发rule,在我看来不是这样的。我们修改下statelessSampleRule.drl
package drools.samples.rules.statelessSampleRule
import drools.samples.domain.Person
rule "Age < 18"
when
$a : Person(age < 18)
then
System.out.println($a.getName()+"'age is less 18");
end
rule "Age 16"
when
$a : Person(age == 16)
then
System.out.println($a.getName()+"'age is 16");
modify($a){setAge(25)}
end
rule "Age 25"
when
$a : Person(age == 25)
then
System.out.println($a.getName()+"'age is 25");
modify($a){setAge(17)}
end
在这个规则文件里,我们特意指定fact进行更新,看看stateless session会不会处理这种状况。执行结果:
可以看到stateless session仍然正确处理了中间的fact变化,结果其实和stateful session是一样的,stateless session只是在你第二次调execute时不会和第一次调execute有关联。
2 insert()和insertLogical()的区别
2.1 Inference
在Drools规则语法中,insert操作的目的是为了进行推断(inference),即根据已有fact推断一些中间fact,由此进一步的决定action。站在程序员的角度,其实就相当于生成中间变量。例如我们现在要删选出名为Tom的child,我们可以这样写规则:
rule "A child named Tom"
when:
$p : Person(name=="Tom",age<18)
then
System.out.println("The child Tom is here");
end
这个规则的condition里有age和name两个判断条件,这样写在这个例子没问题,但是如果考虑规模的提升和后期的维护,我们应该拆成两个condition,那么我们就可以用insert在执行过程中生成中间fact。我们像下面这样写规则,注意IsChild我们同样需要在引擎外定义好。
rule "Age < 18"
when
$p : Person(age < 18)
then
insert(new IsChild($p));
System.out.println($p.getName()+" is a child");
end
rule "Name is Tom"
when:
$p : Person(name=="Tom")
IsChild(person==$p)
then
System.out.println("The child Tom is here");
end
2.2 insertLogical
简单情况下,用insert就足够了,但更多的情况下应该用insertLogical方法。我们看看官网的说明:insertLogical执行逻辑插入操作,通过Drools内置的truth maintenance手段,当逻辑插入的fact不再满足规则的条件时将自动撤销(retracted)。依旧有点难以理解,而且如果你自己写demo测试的话很多时候用insertLogical和insert的结果是一样的。按照我的理解,这两者的关键区别是在是否自动撤销,也就是说用insert插入到Working memory的fact,除非你手动撤销,不然的话是一直存在的,你在更新一个fact后,更新前的状态仍然存在;而insertLogical则会自动撤销掉所有不满足规则条件的fact。
我们先用insert写个这样的规则,在判断person是child还是adult后插入一个IsChild或者IsAdult的fact,接着判断当前空间是否存在IsChild。先定义好IsChild和IsAdult类:
public class IsChild {
private Person person;
public IsChild(Person person){
this.person=person;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
}
public class IsAdult {
private Person person;
public IsAdult(Person person){
this.person=person;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
}
规则文件:
rule "Age < 18"
when
$p : Person(age < 18)
then
insert(new IsChild($p));
System.out.println($p.getName()+" is a child");
end
rule "Age >= 18"
when
$p : Person(age >= 18)
then
insert(new IsAdult($p));
System.out.println($p.getName()+" is a adult");
end
rule "no child fact now"
when
not(IsChild())
then
System.out.println("There is no child fact now");
end
然后我们输入一个16岁的person,执行后再更新成25岁再次执行:
KieServices kieServices = KieServices.Factory.get();
KieContainer kContainer = kieServices.getKieClasspathContainer();
KieSession kSession = kContainer.newKieSession("insertTest");
Person p1 = new Person("Tom", 16);
FactHandle factHandle=kSession.insert(p1);
kSession.fireAllRules();
p1.setAge(25);
kSession.update(factHandle, p1,"age");
kSession.fireAllRules();
可以看到运行的结果没有符合我们的预期,明明Tom的年龄更新成了25岁,引擎却没有输出"There is no child fact now",也就是说仍然有IsChild的fact存在
我们再用insertLogicl重写下规则:
rule "Age < 18"
when
$p : Person(age < 18)
then
insertLogical(new IsChild($p));
System.out.println($p.getName()+" is a child");
end
rule "Age >= 18"
when
$p : Person(age >= 18)
then
insertLogical(new IsAdult($p));
System.out.println($p.getName()+" is a adult");
end
rule "no child fact now"
when
not(IsChild())
then
System.out.println("There is no child fact now");
end
同样的主程序运行一遍,其效果如下:
可见逻辑插入会自动撤销掉IsChild的fact,其结果符合我们的预期。