A场景引入
公司开发充值发放优惠券活动,具体规则如下:
100元,送10元优惠券·
200元,送25元优惠券
300元,送40元优惠券
Java后端攻城师在代码利用if-else代码将业务逻辑实现了功能,这样看似完全没有必要引入什么鬼规则引擎;
但问题出现了:几天后业务人员发现充值的人还是很少,就想修改发放优惠券活动:100元送15元优惠券等……
这时候攻城师忍气吞声修改后端代码,并经过一大堆发布流程进行上线;
一段时间过后客户量多了,业务人员评估后有要减少优惠券的发放金额…….这时候,一场硝烟滚滚而来
这时候处理这样的业务需求:规则集就可以隆重登场了。
1,什么是规则引擎
规则引擎由推理引擎发展而来,是一种嵌入在应用程序中的组件,实现了将业务决策从应用程序代码中分离出来,并使用预定义的语义模块编写业务决策。接受数据输入,解释业务规则,并根据业务规则做出业务决策。
2,为什么我们要使用规则引擎
1,对于我们的业务规则的匹配而言。以传统的命令式编码(即我们java代码条件判断等),如果业务规则发生了变化,我们需要重新从头缕逻辑,然后在合适的地方添加/删除、修改我们的条件判断。这带来就是业务逻辑的捆绑以及不可预知的Bug。而Drools提倡的是声明式编程,我们只需要像做流水线一样,单独做自己的业务规则即可,每个业务规则均是独立的,类似的,可以想象为if/else if/else变为了插拔式的多个if判断。
2,通过使用决策表,决策表是excel表格,业务人员将业务逻辑写入决策表中,后端开发可以读取决策表得到业务逻辑,这样可以达到业务与开发分离,将复杂的业务交给业务人员处理。
3,可以实现热加载
规则引擎的简易demo
drl文件
import com. neo . drools . HelloWorldExample . Message ;
global java. util . List list
m : Message ( status == Message. HELLO )
System. out . println ( m. getMessage () ) ;
modify ( m ) { message = "Goodbye cruel world" ,
status = Message. GOODBYE } ;
m: Message ( status == Message. GOODBYE )
System. out . println ( m. getMessage ()) ;
package rules
import com.neo.drools.HelloWorldExample.Message;
global java.util.List list
rule "Hello World"
dialect "mvel"
when
m : Message( status == Message.HELLO)
then
System.out.println( m.getMessage () );
modify ( m ) { message = "Goodbye cruel world",
status = Message.GOODBYE };
end
rule "Good Bye"
dialect "java"
when
m: Message( status == Message.GOODBYE)
then
System.out.println(m.getMessage ());
end
package rules
import com.neo.drools.HelloWorldExample.Message;
global java.util.List list
rule "Hello World"
dialect "mvel"
when
m : Message( status == Message.HELLO)
then
System.out.println( m.getMessage () );
modify ( m ) { message = "Goodbye cruel world",
status = Message.GOODBYE };
end
rule "Good Bye"
dialect "java"
when
m: Message( status == Message.GOODBYE)
then
System.out.println(m.getMessage ());
end
java代码
public static final void main ( final String [] args ) {
KieServices ks = KieServices. Factory . get () ;
KieContainer kc = ks. getKieClasspathContainer () ;
public static void execute ( KieContainer kc ) {
KieSession ksession = kc. newKieSession ( "HelloWorldKS" ) ;
final Message message = new Message () ;
message. setMessage ( "Hello World" ) ;
message. setStatus ( Message. HELLO ) ;
ksession. insert ( message ) ;
public static class Message {
public static final int HELLO = 0 ;
public static final int GOODBYE = 1 ;
public static final void main(final String[] args) {
KieServices ks = KieServices.Factory.get();
KieContainer kc = ks.getKieClasspathContainer();
execute( kc );
}
public static void execute( KieContainer kc ) {
KieSession ksession = kc.newKieSession("HelloWorldKS");
final Message message = new Message();
message.setMessage( "Hello World" );
message.setStatus( Message.HELLO );
ksession.insert( message );
ksession.fireAllRules();
ksession.dispose();
}
public static class Message {
public static final int HELLO = 0;
public static final int GOODBYE = 1;
private String message;
private int status;
public static final void main(final String[] args) {
KieServices ks = KieServices.Factory.get();
KieContainer kc = ks.getKieClasspathContainer();
execute( kc );
}
public static void execute( KieContainer kc ) {
KieSession ksession = kc.newKieSession("HelloWorldKS");
final Message message = new Message();
message.setMessage( "Hello World" );
message.setStatus( Message.HELLO );
ksession.insert( message );
ksession.fireAllRules();
ksession.dispose();
}
public static class Message {
public static final int HELLO = 0;
public static final int GOODBYE = 1;
private String message;
private int status;
规则引擎的关键字与api
package 与Java语言类似,drl的头部需要有package和import的声明,package不必和物理路径一致。 import 导出java Bean的完整路径,也可以将Java静态方法导入调用。 rule 规则名称,需要保持唯一 件,可以无限次执行。 no-loop 定义当前的规则是否不允许多次循环执行,默认是 false,也就是当前的规则只要满足条件,可以无限次执行。 lock-on-active 将lock-on-active属性的值设置为true,可避免因某些Fact对象被修改而使已经执行过的规则再次被激活执行。 salience 用来设置规则执行的优先级,salience 属性的值是一个数字,数字越大执行优先级越高, 同时它的值可以是一个负数。默认情况下,规则的 salience 默认值为 0。如果不设置规则的 salience 属性,那么执行顺序是随机的。 when 条件语句,就是当到达什么条件的时候 then 根据条件的结果,来执行什么动作 end 规则结束 insert 在我们规则语句中的then语句,规则可以推断出新的信息。更多的数据可以提供给工作中的内存,以便对使用insert关键字的所有规则进行进一步的评估 modify 修改工作中内存的值,所有规则进行进一步的评估 timer 开启定时任务
规则集引擎的扩展(sliding window):可以实现时间的控制与限流操作
$d : Double () over window: time ( 10s )
$d : Double () over window: length ( 2 )
package rules;
dialect "mvel"
declare Double
@role( event )
end
rule "test02"
when
//10s钟后处理fact
$d : Double() over window:time(10s)
then
System.out.println($d);
end
rule "test02 - 01"
when
//处理最后2个fact
$d : Double() over window:length(2)
then
System.out.println($d);
end
package rules;
dialect "mvel"
declare Double
@role( event )
end
rule "test02"
when
//10s钟后处理fact
$d : Double() over window:time(10s)
then
System.out.println($d);
end
rule "test02 - 01"
when
//处理最后2个fact
$d : Double() over window:length(2)
then
System.out.println($d);
end
引入决策表
决策表格
通过决策表生成drl文件
//generated from Decision Table
import org. drools . decisiontable . Person ;
// rule values at B11, header at B6
rule "Spreadsheet Example_11"
$person: Person ( Age > = 0 && Age < 18 )
$person. setCanBuyAlcohol ( false ) ;
// rule values at B12, header at B6
rule "Spreadsheet Example_12"
$person: Person ( Age > = 18 && Age < 150 )
$person. setCanBuyAlcohol ( true ) ;
package data;
//generated from Decision Table
import org.drools.decisiontable.Person;
// rule values at B11, header at B6
rule "Spreadsheet Example_11"
when
$person: Person(Age >= 0 && Age < 18)
then
$person.setCanBuyAlcohol(false);
end
// rule values at B12, header at B6
rule "Spreadsheet Example_12"
when
$person: Person(Age >= 18 && Age < 150)
then
$person.setCanBuyAlcohol(true);
end
package data;
//generated from Decision Table
import org.drools.decisiontable.Person;
// rule values at B11, header at B6
rule "Spreadsheet Example_11"
when
$person: Person(Age >= 0 && Age < 18)
then
$person.setCanBuyAlcohol(false);
end
// rule values at B12, header at B6
rule "Spreadsheet Example_12"
when
$person: Person(Age >= 18 && Age < 150)
then
$person.setCanBuyAlcohol(true);
end
实际开发:使用决策表
drools实战场景架构:业务人员在决策表进行编写规则,后端开启定时任务去将决策表转化成drl字符串,然后保存到redis,后端代码运行规则时判断redis有没有,有的话就读取用drl生成kiesession,然后获取drl脚本进行获取到kiesession,进而可以进行程序代码执行。
好处:
1.可以实现复杂业务交给业务人员
2,将开启定时任务将drl文本解析到redis中,获取提供个后台管理接口,将决策表格解析的文本保存到redis,降低服务器压力
3,实现代码与业务的分离,热部署业务逻辑
demo如下:
@ RequestMapping ( value = "/test01" , method = RequestMethod. GET )
public void test01 () throws FileNotFoundException {
Map < String, String > amountMap = new HashMap <>() ;
ScoreInfo info = new ScoreInfo () ;
String drl = ( String ) cacheManager. get ( "score_sign" ) ; //从redis获取drl脚本
drl = KieSessionUtils. getDRL ( "C:\\DROOLS\\score_sign.xls" ) ; //将决策表格解析成drl脚本
cacheManager. put ( "score_sign" , drl ) ; //放入redis
KieSession kieSession = KieSessionUtils. createKieSessionFromDRL ( drl ) ; //通过drl脚本创建kiesession
kieSession. getAgenda () . getAgendaGroup ( "score_sign" ) . setFocus () ; //开启score_sign议程
kieSession. setGlobal ( "amountMap" , amountMap ) ; //设置全区变量
kieSession. fireAllRules () ; //执行drl
System. out . println ( "评估规则ok" ) ;
String score = amountMap. get ( "score" ) ;
String coupon = amountMap. get ( "coupon" ) ;
System. out . println ( "获得积分奖励:" + score ) ;
System. out . println ( "获得美金奖励:" + coupon )
//controller层
@RequestMapping(value = "/test01", method = RequestMethod.GET)
public void test01() throws FileNotFoundException {
Map<String, String> amountMap = new HashMap<>();
ScoreInfo info = new ScoreInfo();
info.setCount(10);
String drl = (String) cacheManager.get("score_sign"); //从redis获取drl脚本
if (drl == null) {
drl = KieSessionUtils.getDRL("C:\\DROOLS\\score_sign.xls"); //将决策表格解析成drl脚本
cacheManager.put("score_sign", drl); //放入redis
}
System.out.println(drl);
KieSession kieSession = KieSessionUtils.createKieSessionFromDRL(drl); //通过drl脚本创建kiesession
kieSession.getAgenda().getAgendaGroup("score_sign").setFocus(); //开启score_sign议程
kieSession.insert(info);
kieSession.setGlobal("amountMap", amountMap); //设置全区变量
kieSession.fireAllRules(); //执行drl
System.out.println("评估规则ok");
String score = amountMap.get("score");
String coupon = amountMap.get("coupon");
System.out.println("获得积分奖励:" + score);
System.out.println("获得美金奖励:" + coupon)
}
//controller层
@RequestMapping(value = "/test01", method = RequestMethod.GET)
public void test01() throws FileNotFoundException {
Map<String, String> amountMap = new HashMap<>();
ScoreInfo info = new ScoreInfo();
info.setCount(10);
String drl = (String) cacheManager.get("score_sign"); //从redis获取drl脚本
if (drl == null) {
drl = KieSessionUtils.getDRL("C:\\DROOLS\\score_sign.xls"); //将决策表格解析成drl脚本
cacheManager.put("score_sign", drl); //放入redis
}
System.out.println(drl);
KieSession kieSession = KieSessionUtils.createKieSessionFromDRL(drl); //通过drl脚本创建kiesession
kieSession.getAgenda().getAgendaGroup("score_sign").setFocus(); //开启score_sign议程
kieSession.insert(info);
kieSession.setGlobal("amountMap", amountMap); //设置全区变量
kieSession.fireAllRules(); //执行drl
System.out.println("评估规则ok");
String score = amountMap.get("score");
String coupon = amountMap.get("coupon");
System.out.println("获得积分奖励:" + score);
System.out.println("获得美金奖励:" + coupon)
}
//决策表格
将决策表解析成drl脚本方法
public static String getDRL ( String realPath ) throws FileNotFoundException {
File file = new File ( realPath ) ; // 例如:C:\\abc.xls
InputStream is = new FileInputStream ( file ) ;
SpreadsheetCompiler compiler = new SpreadsheetCompiler () ;
return compiler. compile ( is, InputType. XLS ) ;
public static KieSession createKieSessionFromDRL ( String drl ) {
KieHelper kieHelper = new KieHelper () ;
kieHelper. addContent ( drl, ResourceType. DRL ) ;
Results results = kieHelper. verify () ;
if ( results. hasMessages ( Message. Level . WARNING , Message. Level . ERROR )) {
List < Message > messages = results. getMessages ( Message. Level . WARNING , Message. Level . ERROR ) ;
for ( Message message : messages ) {
System. out . println ( "Error: " +message. getText ()) ;
throw new IllegalStateException ( "Compilation errors were found. Check the logs." ) ;
return kieHelper. build () . newKieSession () ;
// 把xls文件解析为String
public static String getDRL (String realPath) throws FileNotFoundException {
File file = new File(realPath); // 例如:C:\\abc.xls
InputStream is = new FileInputStream(file);
SpreadsheetCompiler compiler = new SpreadsheetCompiler();
return compiler.compile(is, InputType.XLS);
}
使用drl脚本创建kiession方法
// drl为含有内容的字符串
public static KieSession createKieSessionFromDRL(String drl) {
KieHelper kieHelper = new KieHelper();
kieHelper.addContent(drl, ResourceType.DRL);
Results results = kieHelper.verify();
if (results.hasMessages(Message.Level.WARNING, Message.Level.ERROR)) {
List<Message> messages = results.getMessages(Message.Level.WARNING, Message.Level.ERROR);
for (Message message : messages) {
System.out.println("Error: "+message.getText());
}
throw new IllegalStateException("Compilation errors were found. Check the logs.");
}
return kieHelper.build().newKieSession();
}
// 把xls文件解析为String
public static String getDRL (String realPath) throws FileNotFoundException {
File file = new File(realPath); // 例如:C:\\abc.xls
InputStream is = new FileInputStream(file);
SpreadsheetCompiler compiler = new SpreadsheetCompiler();
return compiler.compile(is, InputType.XLS);
}
使用drl脚本创建kiession方法
// drl为含有内容的字符串
public static KieSession createKieSessionFromDRL(String drl) {
KieHelper kieHelper = new KieHelper();
kieHelper.addContent(drl, ResourceType.DRL);
Results results = kieHelper.verify();
if (results.hasMessages(Message.Level.WARNING, Message.Level.ERROR)) {
List<Message> messages = results.getMessages(Message.Level.WARNING, Message.Level.ERROR);
for (Message message : messages) {
System.out.println("Error: "+message.getText());
}
throw new IllegalStateException("Compilation errors were found. Check the logs.");
}
return kieHelper.build().newKieSession();
}
版权声明:本文为CSDN博主「gudaichaoren」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/gudaichaoren/article/details/90741599
关注公众号:程序新视界 ,一个让你软实力、硬技术同步提升的平台
除非注明,否则均为程序新视界 原创文章,转载必须以链接形式标明本文链接
本文链接:https://choupangxia.com/2020/11/28/drools/