规则引擎Drools入门笔记(1)

开发环境:IntelliJ IDEA,Java 8,Gradle,SpringBoot,Drools。

新建 Gradle 工程后,配置 SpringBoot 和 Drools 的引入:

1
2
3
4
5
6
compile 'org.springframework.boot:spring-boot:1.5.9.RELEASE'
compile 'org.springframework.boot:spring-boot-starter:1.5.9.RELEASE'
compile 'org.springframework.boot:spring-boot-starter-log4j2:1.5.9.RELEASE'

compile 'org.kie:kie-api:7.5.0.Final'
compile 'org.drools:drools-compiler:7.5.0.Final'

配置 Main 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
package demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Main {

public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}

}

配置一个 Bean 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package demo;

public class Message {

public final static int HELLO = 1;
public final static int GOODBYE = 2;

private String message;
private int status;

public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}

public int getStatus() {
return status;
}

public void setStatus(int status) {
this.status = status;
}

}

配置 META-INF/kmodule.xml:

1
2
3
4
5
6
<?xml version="1.0" encoding="UTF-8" ?>
<kmodule xmlns="http://www.drools.org/xsd/kmodule">
<kbase name="demo" packages="rules">
<ksession name="demo_ksession"/>
</kbase>
</kmodule>

配置对应 packages「rules」下的规则文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
package rules;

import demo.Message;

rule "rule1"
salience 1
when
$message: Message(status == Message.HELLO)
then
System.out.println("Status is 'Hello', set 'Hello World!' to message");
$message.setMessage("Hello World!");
System.out.println("Message: " + $message.getMessage());
end

配置 Demo 运行类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package demo;

import org.kie.api.KieServices;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Order(1)
public class DroolsDemo implements CommandLineRunner{

@Override
public void run(String... args) throws Exception {

KieServices ks = KieServices.Factory.get();
KieContainer kieContainer = ks.getKieClasspathContainer();
KieSession kieSession = kieContainer.newKieSession("demo_ksession");

Message message = new Message();
message.setStatus(Message.HELLO);
kieSession.insert(message);
kieSession.fireAllRules();
kieSession.dispose();

}

}

启动运行,输出:

1
2
Status is 'Hello', set 'Hello World!' to message
Message: Hello World!

完成。

把规则文件原有的规则调整一下,再增加一个新规则,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package rules;

import demo.Message;

rule "rule1"
salience 1
when
$message: Message(status == Message.HELLO)
then
System.out.println("Status is 'Hello', set 'Hello World!' to message");
$message.setMessage("Hello World!");
System.out.println("Message: " + $message.getMessage());
System.out.println("Then set 'Goodbye' to status");
$message.setStatus(Message.GOODBYE);
update($message);
end

rule "rule2"
salience 1
when
$message: Message(status == Message.GOODBYE)
then
System.out.println("Status is 'Goodbye', set 'Goodbye World!' to message");
$message.setMessage("Goodbye World!");
System.out.println("Message: " + $message.getMessage());
end

再次运行,输出:

1
2
3
4
5
Status is 'Hello', set 'Hello World!' to message
Message: Hello World!
Then set 'Goodbye' to status
Status is 'Goodbye', set 'Goodbye World!' to message
Message: Goodbye World!

即 rule1 中的update($message)会使\$message 对象再次经过规则验证。

如果把 rule2 改成和 rule1 类似的状况,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package rules;

import demo.Message;

rule "rule1"
salience 1
when
$message: Message(status == Message.HELLO)
then
System.out.println("Status is 'Hello', set 'Hello World!' to message");
$message.setMessage("Hello World!");
System.out.println("Message: " + $message.getMessage());
System.out.println("Then set 'Goodbye' to status");
$message.setStatus(Message.GOODBYE);
update($message);
end

rule "rule2"
salience 1
when
$message: Message(status == Message.GOODBYE)
then
System.out.println("Status is 'Goodbye', set 'Goodbye World!' to message");
$message.setMessage("Goodbye World!");
System.out.println("Message: " + $message.getMessage());
System.out.println("Then set 'Hello' to status");
$message.setStatus(Message.HELLO);
update($message);
end<

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Status is 'Hello', set 'Hello World!' to message
Message: Hello World!
Then set 'Goodbye' to status
Status is 'Goodbye', set 'Goodbye World!' to message
Message: Goodbye World!
Then set 'Hello' to status
Status is 'Hello', set 'Hello World!' to message
Message: Hello World!
Then set 'Goodbye' to status
Status is 'Goodbye', set 'Goodbye World!' to message
Message: Goodbye World!
Then set 'Hello' to status
Status is 'Hello', set 'Hello World!' to message
Message: Hello World!
......

这样造成了验证一直在循环。如果需要不让它一直循环的话,需要在 rule 的开头配置no-looplock-on-active。这两个属性在没有配置的情况下,默认为false,即不会阻止循环。
但要注意,no-loop只会在 rule 调用自己的时候才会阻止循环,lock-on-active则是其它 rule 调用该 rule 的时候也会阻止循环,使该 rule 只执行一次。

no-loop 例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package rules;

import demo.Message;

rule "rule1"
salience 1
no-loop true
when
$message: Message(status == Message.HELLO)
then
System.out.println("Status is 'Hello', set 'Hello World!' to message");
$message.setMessage("Hello World!");
System.out.println("Message: " + $message.getMessage());

System.out.println("Then create a new Message and set 'Hello' to status");
Message message2 = new Message();
message2.setStatus(Message.HELLO);
insert(message2);
end

当把no-loop true一句注释掉时,输出结果为:

1
2
3
4
5
6
7
8
Status is 'Hello', set 'Hello World!' to message
Message: Hello World!
Then create a new Message and set 'Hello' to status
Status is 'Hello', set 'Hello World!' to message
Message: Hello World!
Then create a new Message and set 'Hello' to status
Status is 'Hello', set 'Hello World!' to message
......

造成了循环,将注释解开后,输出结果为:

1
2
3
Status is 'Hello', set 'Hello World!' to message
Message: Hello World!
Then create a new Message and set 'Hello' to status

没有出现循环。

lock-on-active 例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package rules;

import demo.Message;

rule "rule1"
salience 1
no-loop true
lock-on-active true
when
$message: Message(status == Message.HELLO)
then
System.out.println("Status is 'Hello', set 'Hello World!' to message");
$message.setMessage("Hello World!");
System.out.println("Message: " + $message.getMessage());
System.out.println("Then set 'Goodbye' to status");
$message.setStatus(Message.GOODBYE);
update($message);
end

rule "rule2"
salience 1
when
$message: Message(status == Message.GOODBYE)
then
System.out.println("Status is 'Goodbye', set 'Goodbye World!' to message");
$message.setMessage("Goodbye World!");
System.out.println("Message: " + $message.getMessage());
System.out.println("Then set 'Hello' to status");
$message.setStatus(Message.HELLO);
update($message);
end

当把lock-on-active true一句注释掉时,输出结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Status is 'Hello', set 'Hello World!' to message
Message: Hello World!
Then set 'Goodbye' to status
Status is 'Goodbye', set 'Goodbye World!' to message
Message: Goodbye World!
Then set 'Hello' to status
Status is 'Hello', set 'Hello World!' to message
Message: Hello World!
Then set 'Goodbye' to status
Status is 'Goodbye', set 'Goodbye World!' to message
Message: Goodbye World!
Then set 'Hello' to status
Status is 'Hello', set 'Hello World!' to message
Message: Hello World!
......

即 rule1 中的 no-loop true 并没有阻止其被 rule2 调用,仍然造成了循环。当把注释解开后,输出结果则为:

1
2
3
4
5
6
Status is 'Hello', set 'Hello World!' to message
Message: Hello World!
Then set 'Goodbye' to status
Status is 'Goodbye', set 'Goodbye World!' to message
Message: Goodbye World!
Then set 'Hello' to status

rule1 执行后调用了 rule2,rule2 没有再使 rule1 出现循环,rule1 仅执行了一次。


本 Demo 地址:https://github.com/HerbertGao/DroolsDemo