Drools基础应用

1. 为什么要使用规则擎

if ("周一".equals(day)) {
      System.out.println("I will play basketball");
} else if ("周二".equals(day)) {
      System.out.println("I will do shopping");
} else if ("周三".equals(day)) {
      System.out.println("I will do shopping");
} else if ("周四".equals(day)) {
      System.out.println("I will do shopping");
} else if (...) {
  ...
} else {
  ...
}
  • 产品需求1:周二不应该do shopping,应该要play switch
  • 产品需求2:周五、周六、周日呢?

实际生产情况可能涉及的规则更为复杂多变,如何修改上述代码呢。可能有人会使用策略模式、模板方法、装饰器、工厂方法之类,但那仅仅是针对于代码层面的优化。但针对于复杂的业务逻辑,还需要做到以下几点:

  • 简化if else结构,让业务逻辑和数据分离!
  • 分理处的业务逻辑必须要易于编写,至少单独编写这些业务逻辑,要比写代码快!
  • 分离出的业务逻辑必须要比原来的代码更容易读懂!
  • 分离出的业务逻辑必须比原来的易于维护,至少改动这些逻辑,应用程序不用重启!

基于此种需求,规则引擎诞生了。规则引擎是一种嵌套在应用程序中的组件,它实现了将业务规则从应用程序代码中分离出来。规则引擎使用特定的语法编写业务规则,规则引擎可以接受数据输入、解释业务规则、并根据业务规则做出相应的决策。

规则引擎

市面上的规则引擎框架有很多,Aviator、Drools、RasyRules、RuleBook、Ikexpression、MVEL、JRules、JLisa、QuickRules等等。但是更多的公司还是选择Drools作为生产应用的规则引擎。

2.Drools简介

Drools是由Java语言开发的开源业务规则引擎,其具有一个易于访问企业策略、易于调整以及易于管理的开源业务规则引擎,符合业内标准、速度快、效率高。业务分析师或者审核人员可以利用它轻松查看业务规则,从而检验是否已编码的规则执行了所需的业务规则。

Drools具有以下优点:

  • 活跃的社区支持
  • 简洁易用
  • 快速的执行速度:Rete算法
  • 在Java开发人员中流行

2.1 HelloWorld

2.1.1引入Maven依赖
 <!-- drools -->
<dependency>
    <groupId>org.kie</groupId>
    <artifactId>kie-internal</artifactId>
    <version>7.10.0.Final</version>
</dependency>
<dependency>
    <groupId>org.kie</groupId>
    <artifactId>kie-api</artifactId>
    <version>7.10.0.Final</version>
</dependency>
<dependency>
    <groupId>org.drools</groupId>
    <artifactId>drools-core</artifactId>
    <version>7.10.0.Final</version>
</dependency>
<dependency>
    <groupId>org.drools</groupId>
    <artifactId>drools-compiler</artifactId>
    <version>7.10.0.Final</version>
</dependency>
2.1.2创建一个JavaBean
public class DailyProgram {
    int day;
    String plan = "无";

    public int getDay() {
        return day;
    }

    public void setDay(int day) {
        this.day = day;
    }

    public String getPlan() {
        return plan;
    }

    public void setPlan(String plan) {
        this.plan = plan;
    }
}
2.1.3创建kmodule.xml,放在resources目录的META-INF文件夹下
<?xml version="1.0" encoding="UTF-8"?>
<kmodule xmlns="http://jboss.org/kie/6.0.0/kmodule">
    <kbase name="daily_program" packages="rules">
        <ksession name="daily_program_session" type="stateful"/>
    </kbase>
</kmodule>
2.1.4创建规则文件dailyprogram.drl
package wang.myrule;

import wang.smalleyes.drools.bean.DailyProgram;

function String hello(String name){
    return "Hello " + name + "!";
}

rule "day1"

when
   $dailyProgram: DailyProgram(day == 1);
then

   $dailyProgram.setPlan("I will play basketball");
   System.out.println( "exec rule day1 ... " );
   System.out.println( hello("day1") );
end
2.1.5测试规则运行
public void testRules1(){
        //创建KieServices
        KieServices  kieServices = KieServices.get();

        //创建KieServices,classPathContainer默认获取META-INF下的kmodule.xml
      KieContainer kieContainer = kieServices.getKieClasspathContainer();

      //按名称指定获取KieSession
      KieSession kieSession = kieContainer.newKieSession("daily_program_session");

      DailyProgram dailyProgram = new DailyProgram();
      dailyProgram.setDay(1);
    
      kieSession.insert(dailyProgram);

       int fireCount = kieSession.fireAllRules();

         kieSession.dispose();

      System.err.println(dailyProgram.getPlan());
      System.out.println("规则执行击中" + fireCount + "条规则");
 }

运行结果如下:

规则引擎

2.2重要的API

  • KieServices:KIE整体的入口,可以用来创建下面这些API等。
  • KieContainer:KieBase的容器,根据kmodule.xml里描述的信息获取具体的KieSession、KieBase;
  • KieBase:就是一个知识仓库,包含了若干的规则、流程、方法等,在Drools中主要就是规则和方法,KieBase本身并不包含运行时的数据之类的,如果需要执行规则KieBase中的规则的话,就需要根据KieBase创建KieSession
  • KieSession:KieSession就是一个跟Drools引擎打交道的会话,其基于KieBase创建,它会包含运行时数据,包含“事实 Fact”,并对运行时数据事实进行规则运算
  • KieModule:是一个包含了多个kiebase定义的容器。一般用kmodule.xml来表示

2.3KieSession

KieSession在创建时可指定创建为有状态Session或无状态Session。

有状态Session:它可以在交互的同时,保持住状态。这就是有状态的会话。当我们不想再使用KieSession的时候,那么我们必须显示的调用dispose()方法来声明下。

有状态Session使用fireAllRules()方法执行规则。

无状态Session:无状态的KieSession会将这种会话描述为一个函数,具有无状态以及原子性。理想情况下,函数不应该具有任何间接影响。

无状态Session使用execute()方法执行规则。

在Kmodule.xml文件中定义Kbase下的Kession,由属性type可以控制无状态(stateless)或者有状态(stateful)。或者通过以下API创建

kieContainer.newStatelessKieSession(”daily_program_session“);

有状态和无状态的区别在于多次会话时,有状态会保留前次计算的信息,而无状态多次运算之间是不相互影响的。

2.4规则文件语法

2.4.1 package

在一个规则文件当中 package 是必须的,而且必须放置在文件的第一行。

package 的名字是随意的,不必必须对应物理路径,这里跟 java 的package 的概念不同,只是逻辑上的区分,但建议与文件路径一致。

同一的 package 下定义的 function 和 query 等可以直接使用。如:

package wang.myrule;
2.4.2 import

导入规则文件需要的外部变量,使用方法跟 java 相同。 和 java 的 import 一样, 还可以导入类中的某一个可访问的静态方法。

import wang.smalleyes.drools.bean.DailyProgram;
2.4.3 global

global用于定义全局变量。它可以让应用程序的对象在规则文件中能够被访问。通常,可以用来为规则文件提供数据或服务。在规则执行前由kieSession注入。

@Autowired
private EmailService emailService
  ...
kieSession.setGlobal("emailService", emailService);        

drl文件中的用法:

import wang.smalleyes.drools.EmailService;

global EmailService $emailService;

rule "rule1"

when 
  ...
then
  $emailService.sendEmail()
end
2.4.4 方法定义——function

将语义代码放置在规则文件中的一种方式,就相当于java类中的方法

package wang.myrule;
import wang.smalleyes.drools.bean.DailyProgram;

function String hello(String name){
  return "Hello " + name + "!"
}

rule "rule1"
  
when
  ...
then
  hello("tom");
end
2.4.5规则定义
rule "rule1"          //规则名称,全局唯一
  
attributes            //规则属性
 
when                                    
  LHS                   //规则执行条件
then
    RHS                                    //规则执行结果
end

常用的attributes规则属性:

  • no-loop:定义当前的规则是否不允许多次循环执行,默认是 false,也就是当前的规则只要满足条件,可以无限次执行。当执行Update、insert方法更新Fact对象时,会触发规则执行。
  • salience:规则执行顺序,值越大,越优先执行

3.Springboot整合问题

在本地运行时,规则中的globals引用的外部服务,始终无法被Drools找到,显示Null。经过Debug后,发现是引用了SpringBoot的dev-tools依赖,开启了热加载。

规则引擎

原因:

dev-tools开启后,被Spring管理的bean的类加载器使用了自定义的RestartClassLoader。而Drools在运行时,获取的类的类加载器是AppClassLoader。因为类加载器不一致,并不是由双亲委派模型的下的上级类加载器加载进来,对于一个类,由不同的类加载器实例加载的话,会在方法区产生两个不同的类,彼此不可见,并且在堆中生成不同Class实例(所有通过正常双亲委派模式的类加载器加载的classpath下的和ext下的所有类在方法区都是同一个类,堆中的Class实例也是同一个)。因为两者不一致,所以drools在workingmemory中使用的是AppAppClassLoader的实例自然无法找到由RestartClassLoader加载的实例。

解决方式:

  • 将Drools升级至7.23.0FINAL或更高版本。
  • 取出SpringBoot的dev-tools依赖。

4.Drools热部署——实时更新KieBase

@Test
public void testRules(){
    KieServices kieServices = KieServices.get()
    KieContainer kieContainer = kieServices.getKieclasspathContainer()
    KieSession kieSession = kiecontainer.newKieSession("daily_program_ session")

    KnowledgeBaseImpl kieBase(KnowledgeBaseImpl)kieContainer.getKieBase("daily_program");
      
      //实时更新规则
    insertRules(kieBase)

    DailyProgram dailyProgram = new DailyProgram();
    dailyProgram.setDay(1);
    kieSession.insert(dailyProgram)
    DailyProgram dailyProgram2 = new DailyProgram();
    dailyProgram2.setDay(2);
    kieSession.insert(dailyProgram2)

    int firecount = kiesession,fireAllRules()

    kieSession.dispose()

    System.err.println(dailyProgram.getPlan());
    System.err.println(dailyProgram2.getPlan());
    System.out.println("规则执行击中”+firecount +"条规则")
}
                       
private void insertRules(KnowledgeBaseImpl kieBase){
        String rule = "package com.myrule;\n"
                        + "\n" + "import wang.smalleyes.drools.bean.DailyProgram;\n"
                        + "\n" + "rule \"day2\"\n"
                        + "\n" + "when\n"
                        + "$dailyProgram: DailyProgram(day == 2);\n"
                  + "then\n"
                        + "$dailyProgram.setPlan(\"I will do shopping\");\n"
            + "System.err.println(\"exec rule day2 ...\");\n" + "end\n";
        KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(kieBase);
        kbuilder .add(ResourceFactory.newByteArrayResource(rule.getBytes())
                  .setSourcePath("com/myrule/new.drl")
                  ,ResourceType.DRL).
        if(kbuilder.hasErrors()){
          System.err.println("规则加载异常");
    }
        Collection<KiePackage> kiePackages = kbuilder.getKnowledgePackages();
      kieBase.addPackages(kiePackages);   
}
  • KnowledgeBuilder:在业务代码中收集已编写的规则,并对规则文件进行编译,生成编译好的KnowledgePackage集合,提供给其他API使用。
  • KiePackage:编译好的规则包

执行结果:

规则引擎

最后修改:2025 年 05 月 11 日
如果觉得我的文章对你有用,请随意赞赏