Drools 使用总结和踩坑

6,735 阅读3分钟

最近的项目中需要使用规则引擎来解决问题。经过一番研究选择了Drools作为主要的技术栈。下面,将分享一下使用的心得和涉及到的技术的总结。

1、什么是Drools?

Drools is a Business Rules Management System (BRMS) solution. It provides a core Business Rules Engine (BRE), a web authoring and rules management application (Drools Workbench), full runtime support for Decision Model and Notation (DMN) models at Conformance level 3 and an Eclipse IDE plugin for core development.

Drools 是一种业务规则管理系统 (BRMS) 解决方案。Drools 提供了一套对于规则业务相关的解决方案。

2、为什么要使用Drools?

我们在写代码的时候肯定会常写如下这种充斥着逻辑判断条件的代码。如图2-1所示

image.png 图2-1

随着需求的迭代和优化,这样的if难免会不断增加,不论是判断的深度增加还是if/else增加,或者是判断的字段增加,每次更新庞大的代码总会让人头疼。 面对这个问题,有人可能会说用策略模式就能较好的解决问题。但我们今天要用的是另一种解决方式——规则引擎。

3、使用教程

3.1 关键pom.xml

     <properties>
        <drools-version>7.51.0.Final</drools-version>
     </properties>

    <dependencies>
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-core</artifactId>
            <version>${drools-version}</version>
        </dependency>
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-compiler</artifactId>
            <version>${drools-version}</version>
        </dependency>
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-decisiontables</artifactId>
            <version>${drools-version}</version>
        </dependency>
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-templates</artifactId>
            <version>${drools-version}</version>
        </dependency>
        <dependency>
            <groupId>org.kie</groupId>
            <artifactId>kie-api</artifactId>
            <version>${drools-version}</version>
        </dependency>
        <dependency>
            <groupId>org.kie</groupId>
            <artifactId>kie-internal</artifactId>
            <version>${drools-version}</version>
        </dependency>
    </dependencies>

3.2 Drools样例

Demo.drl:

import java.util.HashMap;
import java.util.Map;

rule "ruleName"
when
$map:Map(this['age'] > 20.1)
then
$map.put("result", 1); 

end

是不是觉得和if/else很像?当when中的条件满足时,就会执行then中的逻辑。

3.3 运行示范:

通常情况下,对于规则引擎,我们会先生成一个XXX.drls文件,再通过kmodule.xml配置找到drls存储路径,并且加载指定的drools文件运行。

但是这样有个问题,如果drools要修改,岂不是又要重新上传重新发布服务?

因此,我们来说说另一种加载drools规则的方法,动态加载的方式。

我们先看一段代码:

/**
 * 学生类
 */
public class Student {

    /**
     * 年龄
     */
    private Integer age;
    /**
     * 身高
     */
    private Integer weight;

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Integer getWeight() {
        return weight;
    }

    public void setWeight(Integer weight) {
        this.weight = weight;
    }
}
@Test
    public void droolsStudent() {

        String drools = "xxx.xxx.xxx.xxx.Student\n" +
                "\n" +
                "rule \"rule1\"\n" +
                "    when\n" +
                "       stu:Student(age>18 || weight < 180)\n" +
                "    then\n" +
                "       System.out.println(\"学生的年龄是:\" + stu.getAge());\n" +
                "       System.out.println(\"学生的体重是是:\" + stu.getWeight());\n" +
                "end";
        try {
            KnowledgeBuilder kb = KnowledgeBuilderFactory.newKnowledgeBuilder();
            kb.add(ResourceFactory.newByteArrayResource(drools.getBytes("utf-8")), ResourceType.DRL);  
            //如果是drools文件,这里需要使用ResourceType.DRL

            if (kb.hasErrors()) {
                String errorMessage = kb.getErrors().toString();
                System.out.println("规则语法异常---\n" + errorMessage);
                return;
            }
            InternalKnowledgeBase kBase = KnowledgeBaseFactory.newKnowledgeBase();
            kBase.addPackages(kb.getKnowledgePackages());

            Student student = new Student();
            student.setAge(20);
            student.setWeight(200);
            //加载指定知识库对应的可执行session实例
            StatelessKieSession kieSession = kBase.newStatelessKieSession();
            //加载入参
            kieSession.execute(student);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

执行结果:

image.png

这里,我们不再使用.drls文件,也不再需要kmodule.xml,只需要一个字符串,drools文件的字符串即可。 而字符串我们则可以存储到数据库中,有需要的时候,取出来就能用。即使需要修改,通过配置页面就能直接修改。

4、技术拓展。

可能有人会问了。Drools能够通过java运行,那么Java能不能通过Drools执行?

答案是:可以的。我们可以通过Drools文件,直接调用我们java中的一个方法。话不多说,直接上演示。

@Autowired
private DroolsService droolsService;

@Test
public void droolsStudent() {

      String drools = "xxx.xxx.xxx.xxx.Student;\n" +
           "global xxx.xxx.xxx.xxx.DroolsService droolsService;\n" + //这里引用我们的全局Service
                "\n" +
                "rule \"rule1\"\n" +
                "    when\n" +
                "       stu:Student(age>18 || weight < 180)\n" +
                "    then\n" +
                "       System.out.println(\"学生的年龄是:\" + stu.getAge());\n" +
                "       System.out.println(\"学生的体重是是:\" + stu.getWeight());\n" +
                "       droolsService.javaFunction(stu);\n" +
                "end\n";
        try {
            KnowledgeBuilder kb = KnowledgeBuilderFactory.newKnowledgeBuilder();
            kb.add(ResourceFactory.newByteArrayResource(drools.getBytes("utf-8")), ResourceType.DRL);

            if (kb.hasErrors()) {
                String errorMessage = kb.getErrors().toString();
                System.out.println("规则语法异常---\n" + errorMessage);
                return;
            }
            InternalKnowledgeBase kBase = KnowledgeBaseFactory.newKnowledgeBase();
            kBase.addPackages(kb.getKnowledgePackages());

            Student student = new Student();
            student.setAge(20);
            student.setWeight(200);
            //加载指定知识库对应的可执行session实例
            StatelessKieSession kieSession = kBase.newStatelessKieSession();
            //添加全局Service
            kieSession.setGlobal("droolsService", droolsService);

            //加载入参
            kieSession.execute(student);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
/**
*global 类
*/
@Service
public class DroolsService {
    /**
     * drl用调用service
     * @param student
     */
    public void javaFunction(Student student) {
        System.out.println("drools调用java"+JSONObject.toJSONString(student));
    }
}

执行结果如下:

image.png

这不就搞定了。 让我们的Drools加载一个global的类,通过这个类来运行指定的复杂的逻辑。

5、总结

这期,主要是因为最近的项目使用到了对我来说的新技术。Drools使得逻辑尤其是if这块能够单独的剥离出来,但是,如果if、else这块的代码太过于复杂,个人还是建议直接通过代码解决。

Drools不仅仅用于解决这些规则上的问题,像是规则集、决策树、评分卡之类的,都是可以或者推荐使用drools去做的,而且,它还有一个重要的运用场景,就是Decision Flow 决策流 ————将Drools和BPMN相结合使用。

关于这块有activiti等解决方案,但是咱不用,就用BPMN2.0和Drools这一套生态圈去做,后面有空会更新这快。

对于Drools的语法,像ruleflow-group、lock-on-active等,大家有兴趣可以单独去了解一下。