Drools 7 基于XPath的OOPath用于DRL中简化对象或者内层嵌套的对象的条件约束配置。
OOPath表达式等价于from 遍历方式,但是较from对实体及其内部嵌套的实体或集合的遍历而言,可以更简洁的表达,同时也可以设置对象的条件约束(真拗口,这已经是我能想到的最好的文字表达了,笑 cry,还是看例子吧)。
OOPath表达式语法
//drools 7.x
OOPExpr = [ID ( ":" | ":=" )] ( "/" | "?/" ) OOPSegment ( "/" | "?/" | "." ) OOPSegment ;
OOPSegment = ID ["#" ID] ["[" ( Number | Constraints ) "]"]
//drools 7.46文档中为OOPExpr = [ID ( ":" | ":=" )] ( "/" | "?/" ) OOPSegment { ( "/" | "?/" | "." ) OOPSegment } ; 但是大括号"{}"或报错,这里我自己改成了上边,不对的话别打我。
//drools 6.X
OOPExpr = "/" OOPSegment { ( "/" | "." ) OOPSegment } ;
OOPSegment = [ID ( ":" | ":=" )] ID ["[" Number "]"] ["{" Constraints "}"];
以上可以看出drools 7 对OOPath表达式做了改动,增加了数据类型过滤(后边会介绍到),但是去掉了对于OOPath表达式中间位置数据对象的引用(即OOPSegment 中的[ID (":" | ":=")] 只能在表达式的开始位置,具体用法后边也会介绍),本文只介绍drools 7版本的OOPath。
以下将结合例子说明drools7 的 OOPath,实体类型仅构造了一个案例所需的数据结构用于说明OOPath的用法,并不考虑实际业务,下文中不做特殊说明的都采用以下实体案例,示例代码如下:
public class Good {
//商品实体
private String id;
private String name;
private String catergery;
private Double price;
private String unit;
private String discribe;
}
public class Order {
//订单实体
private String id;
private String custId;//顾客编码
private String mId;//门店编码
private Date date;//下单时间
private Double receive;//应收
private Double discount;//优惠金额
private Double pay;//实收
private Boolean settle;//是否结算
private Date settleTime;//结算时间
private List goods;//商品列表
}
public class Customer {
//客户实体
private String id;
private String name;
private String level;//会员级别
private Date birth;
private List orders;//(历史)订单列表
}
public class Market {
//门店实体
private String id;
private String name;
private Boolean open; //营业状态
private List customers;//会员表
private Address address;
}
数据准备如下:
public Market getMarket(){
//组装实体
Market m = new Market();
m.setId("1");
m.setName("hello");
m.setOpen(true);
Address address = new Address();
address.setName("阿尔法小区");
address.setCity("火星市");
address.setProvince("火星省");
m.setAddress(address);
List gs = new ArrayList(){{
add(new Good("1","苹果","果蔬",6.00,"kg","高端的水果,长在头顶的火星上"));
add(new Good("2","羊肉","肉类",70.00,"kg","高端的食材,都要在火星溜达一圈"));
add(new Good("2","后悔药","药品",700.00,"粒","出卖我的爱,逼着我离开,知道真相的我眼泪流下来"));
}};
List os = new ArrayList(){{
add(new Order("1","1","1",new Date(1591372800000l),776.00,77.6,698.4,true,new Date(1591372800000l),gs));
}};
List cs = new ArrayList(){{
add(new Customer("1","ET Baby","golden",new Date(925920000000l),os));
}};
m.setCustomers(cs);
return m;
}
1.OOPath表达式一般以 “/” 或 “?/”开始
▶举个例子:查看1号客户有没有到在1号门店消费过。
drl如下:
rule oopath_0
when
//遍历1号门店客户列表中是否有1号客户并有属于他的订单
Market(id == '1',/customers[id == '1']/orders[custId == '1'])
then
System.out.println("客户到过1号门店");
end
运行结果如下:
客户到过1号门店
▶通过OOPath表达式遍历深层嵌套的对象属性,drools对其属性的变更不会有反应。实体类extends AbstractReactiveObject类,setter方法中调用notifyModification()方法,然后drools就可以监控到嵌套对象的变更并触发相关规则。
drl规则如下:
rule oopath_0
when
Market(id == '1',$a:/address[name == '阿尔法小区'])
then
$a.setName("伽马小区");
System.out.println("修改了地址");
end
rule oopath_1
salience 10//高优先级,默认值为0
when
//这里的$a为OOPath表达式最后一个节点的引用(循环依次引用每个符合条件的元素),例如 $o:/customers[id == '1']/orders[custId == '1'] ,其中$o引用的是custId = '1' 的Order实例
Market(id == '1',$a:/address)
then
System.out.println("1号门店的地址:"+$a.getName());
end
内层嵌套实体修改如下:
//继承AbstractReactiveObject类
public class Address extends AbstractReactiveObject {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
//setter方法增加notifyModification() 方法,该方法会通知drools引擎对象属性值发生变更,进而再次触发规则
notifyModification();
}
}
执行结果如下:
1号门店的地址:阿尔法小区
修改了地址
1号门店的地址:伽马小区
以上结果与预期结果一致,但是有一个问题,当rule "oopath_0" 中address不带约束条件的时候,属性的赋值动作会一直触发其规则本身的重新评估和运行,死循环,增加 lock-on-active 属性设置为true也不管用,这个问题还没想明白,研究明白了再更新吧。
▶对于List类型的属性,其中的元素实体类继承 AbstractReactiveObject类以后,则元素属性的变更也可以被drools监控到,并重新出发规则,drl如下:
rule oopath_0
when
Market(id == '1',$c:/customers[id == '1'])
then
$c.setName("ET mum");
System.out.println("修改了名字");
end
rule oopath_1
salience 10//高优先级,默认值为0
when
//这里的$c为OOPath表达式最后一个节点的引用(循环依次引用每个符合条件的元素),例如 $o:/customers[id == '1']/orders[custId == '1'] ,其中$o引用的是custId = '1' 的Order实例
Market($c:/customers[id == '1'])
then
System.out.println("1号客户的姓名为:"+$c.getName());
end
运行结果如下:
1号客户的姓名为:ET Baby
修改了名字
1号客户的姓名为:ET mum
对于List、Set类型的属性,drools 想要监控 add、remove等操作对集合类属性的变更则需要其值为RectiveList 类、ReactiveSet类 或者 ReactiveCollection类,他们分别实现了List接口、Set接口和Collection接口,还实现了Iterator类和ListIterator类的add、remove等方法,实现中已添加了notifyModification 方法,因此准备数据调整如下:
List cs = new ReactiveList(){{
add(new Customer("1","ET Baby","golden",new Date(925920000000l),os));
}};
m.setCustomers(cs);
drl如下:
rule oopath_0
when
$m:Market(id == '1',$c:/customers[id == '1'])
then
Customer c1 = new Customer("2","ET Engel","diamond",new Date(925920000000l),null);
$m.getCustomers().add(c1);
System.out.println("增加了一个客户");
end
rule oopath_1
salience 10
when
$m:Market(/customers)
then
System.out.println("1号店的客户数量:"+$m.getCustomers().size());
end
运行结果如下:
1号店的客户数量:1
增加了一个客户
1号店的客户数量:2
▶‘?/’用于忽略对象属性的变更,例如:
rule rulename
when
//drools将忽略'?/'后的customers以及customer元素以及orders和order元素变更,没个OOPath表达式最多只能有一个'?/',否则会出现编译错误
Market(?/customers/orders)
then
/*RHS ...*/
end
2.数据类型转换和数据类型过滤
//创建一个实体类
public class MyRuleUnit {
private List
3.可以反向引用在当前迭代的对象之前已遍历的对象及其属性。
drl如下:
rule oopath_0
when
// 表达式中“orders[custId == ../id]”表示order 的custId属性值等当前customer元素的id值
//"goods[0]"是通过goods 列表的下标获取读取数据的,表示符合约束条件的order中的goods列表中下标值为0的数据对象
Market(id == '1',$g:/customers[id == '1']/orders[custId == ../id]/goods[0])
then
System.out.println("商品名称:"+$g.getName());
end
运行结果如下:
商品名称:苹果
4.OOPath表达式的嵌套
这里所说的嵌套是指OOPath表达式作为约束条件出现,例如,
rule oopath_0
when
//这里'/goods'作为order的筛选条件出现,表达的意思是,订单中最少包含一件商品
//注意这里‘$o’迭代的是外层OOPath表达式最后一个节点,即order的实体对象
Market(id == '1',$o:/customers[id == '1']/orders[custId == ../id,/goods])
then
System.out.println("订单编号:"+$o.getId());
end
文章开头的OOPath表达式语法可以看出,一个OOPath表达式最多只可以绑定一个变量,而且位置只能在表达式的开始位置,而开始位置绑定的也是最后一个节点的对象,若最后一个节点是集合则表示迭代该集合。
5.还有一点需要注意的是,规则中每个pattern(即每行)最多只能有一个OOPath表达式,这一点感觉表达式很难用,想要从多层嵌套的实体对象中获取多个内层对象及属性时需要写多行,与使用from遍历实体对象的方式也并没有方便到哪去,也或许是我没有想到它的好处吧。