动态更新规则
上一章节讲述了 Drools7 和 Springboot2 集成使用,集成工作相对简单、可以快速开发。但是缺点也很明显,规则和配置文件绑定在项目中(耦合度太高)。如果你不需要修改规则文件,这种方式还是可以采纳的。此篇讲述如何从数据库中加载规则文件,动态加载更新规则。
KieHelper
Drools 中提供了 KieHelper ,可以动态添加规则,并且创建新的 kieContainer 返回。
// 借助 kieHelper 添加规则
KieHelper kieHelper = new KieHelper();
// 动态添加规则内容
kieHelper.addContent(content, ResourceType.DRL);
// 校验规则是否异常
Results results = kieHelper.verify();
if (results.hasMessages(Message.Level.ERROR)) {
System.out.println(results.getMessages());
throw new IllegalStateException("### errors ###");
}
// 重置 kieContainer
kieContainer = kieHelper.getKieContainer();
更新规则实现
添加依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.43</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-spring</artifactId>
<version>${drools.version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</exclusion>
</exclusions>
</dependency>
表结构设计
两条规则分别对 Person 名字和年龄进行判断; 第一条:如果name=“bob” 则打印规则名称,规则包名,和 name; 第二条:如果name=“bob” 且年龄是25和65之间则打印 is a work person;
DROP TABLE IF EXISTS `rule`;
CREATE TABLE `rule` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` char(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
INSERT INTO `rule` VALUES (1, '1.find target person', 'package rules.rules\r\n\r\nimport com.skystep.drools.person.PersonRuleAction\r\nimport com.skystep.drools.person.entity.Person\r\nimport com.skystep.drools.person.service.PersonService\r\nimport com.skystep.drools.person.service.PersonServiceImpl\r\n\r\n// 根据名字匹配指定的人\r\nrule \"1.find target person\"\r\n when\r\n $p : Person( name == \"bob\" )\r\n then\r\n PersonRuleAction.doParse($p, drools.getRule());\r\n System.out.println(\" Rule name is [\" + drools.getRule().getName() + \"] \");\r\n System.out.println(\" Rule package is [\" + drools.getRule().getPackageName() + \"] \");\r\n PersonService personService = new PersonServiceImpl();\r\n personService.hello(\" 我的名字是: \" + $p.getName());\r\nend');
INSERT INTO `rule` VALUES (2, '2.find the work person', 'package rules.rules\r\n\r\nimport com.skystep.drools.person.PersonRuleAction\r\nimport com.skystep.drools.person.entity.Person\r\nimport com.skystep.drools.person.service.PersonService\r\nimport com.skystep.drools.person.service.PersonServiceImpl\r\n\r\n// 根据年龄匹配找到打工人\r\nrule \"2.find the work person\"\r\n when\r\n $p : Person( age >= 25 && age < 65 )\r\n then\r\n System.out.println( $p + \" is a work person!\" );\r\nend');
默认配置 Drools
@Configuration
public class RuleEngineConfig {
private static final String RULES_PATH = "rules/";
@Bean
@ConditionalOnMissingBean(KieFileSystem.class)
public KieFileSystem kieFileSystem() throws IOException {
KieFileSystem kieFileSystem = getKieServices().newKieFileSystem();
for (Resource file : getRuleFiles()) {
kieFileSystem.write(ResourceFactory.newClassPathResource(RULES_PATH + file.getFilename(), "UTF-8"));
}
return kieFileSystem;
}
private Resource[] getRuleFiles() throws IOException {
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
return resourcePatternResolver.getResources("classpath*:" + RULES_PATH + "**/*.*");
}
@Bean
@ConditionalOnMissingBean(KieContainer.class)
public KieContainer kieContainer() throws IOException {
final KieRepository kieRepository = getKieServices().getRepository();
kieRepository.addKieModule(new KieModule() {
@Override
public ReleaseId getReleaseId() {
return kieRepository.getDefaultReleaseId();
}
});
KieBuilder kieBuilder = getKieServices().newKieBuilder(kieFileSystem());
kieBuilder.buildAll();
return getKieServices().newKieContainer(kieRepository.getDefaultReleaseId());
}
private KieServices getKieServices() {
return KieServices.Factory.get();
}
@Bean
@ConditionalOnMissingBean(KieBase.class)
public KieBase kieBase() throws IOException {
return kieContainer().getKieBase();
}
@Bean
@ConditionalOnMissingBean(KieSession.class)
public KieSession kieSession() throws IOException {
return kieContainer().newKieSession();
}
@Bean
@ConditionalOnMissingBean(KModuleBeanFactoryPostProcessor.class)
public KModuleBeanFactoryPostProcessor kiePostProcessor() {
return new KModuleBeanFactoryPostProcessor();
}
}
Drools 动态加载规则
RuleService 提供 getRules 方法从数据库中获取所有的规则,循环加载到 kieHelper。Spring 启动注入 RuleEngineService 时会提前初始化,执行 reload 函数。因此此时内容中存在的规则是从数据库中加载的规则。
@Service
@Slf4j
public class RuleEngineService {
@Autowired
private KieContainer kieContainer;
@Autowired
RuleService ruleService;
@PostConstruct
public void init() {
reload();
}
public KieContainer getKieContainer() {
return kieContainer;
}
public void reload() {
KieHelper kieHelper = new KieHelper();
List<Rule> rules = ruleService.getRules();
if (!CollectionUtils.isEmpty(rules)) {
for (Rule rule : rules) {
String content = rule.getContent();
kieHelper.addContent(content, ResourceType.DRL);
}
}
Results results = kieHelper.verify();
if (results.hasMessages(Message.Level.ERROR)) {
System.out.println(results.getMessages());
throw new IllegalStateException("### errors ###");
}
kieContainer = kieHelper.getKieContainer();
}
}
添加接口
@RestController
@RequestMapping("rule")
public class PersonRuleController {
@Autowired
private RuleEngineService ruleEngineService;
@PostMapping("test")
public void fireAllRules4One(@RequestBody Person person) {
KieSession kSession = ruleEngineService.getKieContainer().newKieSession();
try {
kSession.insert(person);
kSession.fireAllRules(new RuleNameEqualsAgendaFilter(""));
} finally {
kSession.dispose();
}
return;
}
@GetMapping("reload")
public void reload() {
ruleEngineService.reload();
}
}
验证
POST /rule/test,请求参数:
{"name": "bob"}
Rule name is [1.find target person]
Rule package is [rules.rules]
2022-07-02 23:31:58.580 INFO 23540 --- [nio-8080-exec-6] c.s.d.person.service.PersonServiceImpl : 我的名字是: bob
命中了数据库中的第一条规则。name=“bob”,
POST /rule/test,请求参数:
{"name": "bob", "age": 33}
Rule name is [1.find target person]
Rule package is [rules.rules]
2022-07-02 23:39:49.991 INFO 23540 --- [nio-8080-exec-9] c.s.d.person.service.PersonServiceImpl : 我的名字是: bob
Person(name=bob, age=33) is a work person!
命中了数据库中的两条记录
动态添加规则
数据库增加一条规则,判断名称是否是 skystep, 如果name=“skystep” 则打印规则名称,规则包名,和 name。
INSERT INTO `rule` VALUES (3, '3.find target person', 'package rules.rules\r\n\r\nimport com.skystep.drools.person.PersonRuleAction\r\nimport com.skystep.drools.person.entity.Person\r\nimport com.skystep.drools.person.service.PersonService\r\nimport com.skystep.drools.person.service.PersonServiceImpl\r\n\r\n// 根据名字匹配指定的人\r\nrule \"3.find target person\"\r\n when\r\n $p : Person( name == \"skystep\" )\r\n then\r\n PersonRuleAction.doParse($p, drools.getRule());\r\n System.out.println(\" Rule name is [\" + drools.getRule().getName() + \"] \");\r\n System.out.println(\" Rule package is [\" + drools.getRule().getPackageName() + \"] \");\r\n PersonService personService = new PersonServiceImpl();\r\n personService.hello(\" 我的名字是: \" + $p.getName());\r\n end');
GET /rule/reload 重新加载规则,
POST /rule/test,请求参数:
{"name": "skystep"}
Rule name is [3.find target person]
Rule package is [rules.rules]
2022-07-03 00:00:49.148 INFO 23540 --- [nio-8080-exec-2] c.s.d.person.service.PersonServiceImpl : 我的名字是: skystep
命中了数据库中的第条3规则,说明动态添加的第三条规则已经加载到内存,并且成功匹配。
源码下载
https://download.csdn.net/download/skystep/85870731 Drools7 + Springboot2 动态更新规则。 规则存储到数据库MYSQL,动态从数据库中获取规则进行加载。 运行前请自行修改MYSQL配置
|