简介
Drools 是用 Java 语言编写的开放源码规则引擎,使用 Rete 算法对所编写的规则求值。Drools 允许使用声明方式表达业务逻辑。可以使用非 XML 的本地语言编写规则,从而便于学习和理解。并且,还可以将 Java 代码直接嵌入到规则文件中。
详细可见开源业务规则引擎:Drools中文网
一、项目目录结构
二、集成drools
1、引入依赖
<!--drools规则引擎-->
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-core</artifactId>
<version>7.59.0.Final</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
<version>7.59.0.Final</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-templates</artifactId>
<version>7.59.0.Final</version>
</dependency>
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-api</artifactId>
<version>7.59.0.Final</version>
</dependency>
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-spring</artifactId>
<version>7.59.0.Final</version>
</dependency>
2、新建规则文件
rule1.drl
package com.leopard.drools
import com.leopard.drools.pojo.QueryParam
import com.leopard.drools.service.RuleEngineService
dialect "java"
rule "boy"
when queryParam : QueryParam(paramId != null && paramSign.equals("+"))
then
RuleEngineService ruleEngineService = new RuleEngineService();
ruleEngineService.executeAddRule(queryParam);
System.out.println("参数:getParamId="+queryParam.getParamId()+";getParamSign="+queryParam.getParamSign());
end
rule2.drl
package com.leopard.drools
import com.leopard.drools.pojo.QueryParam
dialect "java"
rule "girl"
when queryParam : QueryParam(paramId != null && paramSign.equals("-"))
then
System.out.println(queryParam.getParamId() + "是女孩");
end
3、创建KieUtils(因要做热加载,需要重载规则文件,规则引擎容器要支持变动)
public class KieUtils {
private static KieContainer kieContainer;
private static KieSession kieSession;
private static KModuleBeanFactoryPostProcessor kModuleBeanFactoryPostProcessor;
public static KieContainer getKieContainer() {
return kieContainer;
}
public static void setKieContainer(KieContainer kieContainer) {
KieUtils.kieContainer = kieContainer;
kieSession = kieContainer.newKieSession();
}
public static KieSession getKieSession() {
return kieSession;
}
public static void setKieSession(KieSession kieSession) {
KieUtils.kieSession = kieSession;
}
public static KieServices getKieServices() {
return KieServices.Factory.get();
}
public static KModuleBeanFactoryPostProcessor getkModuleBeanFactoryPostProcessor() {
return kModuleBeanFactoryPostProcessor;
}
public static void setkModuleBeanFactoryPostProcessor(KModuleBeanFactoryPostProcessor kModuleBeanFactoryPostProcessor) {
KieUtils.kModuleBeanFactoryPostProcessor = kModuleBeanFactoryPostProcessor;
}
}
4、添加初始化配置
@Slf4j
@Configuration
public class RuleEngineConfig {
public static final String RULES_PATH = "droolsRule/";
public static final String BASE_RULES_PATH = "classpath*:";
private final KieServices kieServices = KieServices.Factory.get();
/**
* @return
* @throws IOException
* @ConditionalOnMissingBean,它是修饰bean的一个注解, 主要实现的是,当你的bean被注册之后,如果而注册相同类型的bean,就不会成功,
* 它会保证你的bean只有一个,即你的实例只有一个,当你注册多个相同的bean时,会出现异常
*/
@Bean
@ConditionalOnMissingBean(KieFileSystem.class)
public KieFileSystem kieFileSystem() throws IOException {
KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
//获取初始化规则文件所在路径
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
Resource[] files = resourcePatternResolver.getResources(BASE_RULES_PATH + RULES_PATH + "*.*");
String path = null;
for (Resource file : files) {
path = RULES_PATH + file.getFilename();
log.info("path=" + path);
//将规则文件写规则引擎系统内
kieFileSystem.write(ResourceFactory.newClassPathResource(path, "UTF-8"));
}
return kieFileSystem;
}
/**
* 创建KIE内部容器
*
* @return
* @throws IOException
*/
@Bean
@ConditionalOnMissingBean(KieContainer.class)
public KieContainer kieContainer() throws IOException {
final KieRepository kieRepository = kieServices.getRepository();
kieRepository.addKieModule(kieRepository::getDefaultReleaseId);
KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem());
kieBuilder.buildAll();
KieContainer kieContainer = kieServices.newKieContainer(kieRepository.getDefaultReleaseId());
KieUtils.setKieContainer(kieContainer);
return kieServices.newKieContainer(kieRepository.getDefaultReleaseId());
}
@Bean
@ConditionalOnMissingBean(KieBase.class)
public KieBase kieBase() throws IOException {
return kieContainer().getKieBase();
}
@Bean
@ConditionalOnMissingBean(KieSession.class)
public KieSession kieSession() throws IOException {
KieSession kieSession = kieContainer().newKieSession();
KieUtils.setKieSession(kieSession);
return kieSession;
}
@Bean
@ConditionalOnMissingBean(KModuleBeanFactoryPostProcessor.class)
public KModuleBeanFactoryPostProcessor kModuleBeanFactoryPostProcessor() {
return new KModuleBeanFactoryPostProcessor();
}
}
5、重载实现,可通过链接数据库刷新规则
@Slf4j
@Service
public class ReloadDroolsRules {
@Autowired
private KieSession kieSession;
@Autowired
private KieContainer kieContainer;
/**
* 重新加载规则
* @param drlName 规则名称
* @throws Exception
*/
public void reload(String drlName) throws Exception {
KieFileSystem kfs = KieUtils.getKieServices().newKieFileSystem();
// loadDBRules(drlName, kfs);
loadFileRules(drlName, kfs);
KieBuilder kieBuilder = KieUtils.getKieServices().newKieBuilder(kfs).buildAll();
Results results = kieBuilder.getResults();
if (results.hasMessages(Message.Level.ERROR)) {
System.out.println(results.getMessages());
throw new IllegalStateException("### errors ###");
}
KieContainer kieContainer = KieUtils.getKieServices().newKieContainer(KieUtils.getKieServices().getRepository().getDefaultReleaseId());
KieUtils.setKieContainer(kieContainer);
System.out.println("新规则重载成功");
}
/**
* 重新读取数据库配置内容
* @param drlName
* @param kfs
* @throws IOException
*/
private void loadDBRules(String drlName, KieFileSystem kfs) throws IOException {
// String path = "src/main/resources/rules/address.drl";
String path = "src/main/resources/" + RuleEngineConfig.RULES_PATH + "/" + drlName + ".drl";
// 从数据库加载的规则
kfs.write(path, "package plausibcheck.adress\n\n import com.leopard.drools.pojo.QueryParam;\n\n rule \"Postcode 6 numbers\"\n\n when\n then\n System.out.println(\"打印日志:更新rules成功!\");\n end");
}
/**
* 重新配置文件
* @param drlName
* @param kfs
* @throws IOException
*/
private void loadFileRules(String drlName, KieFileSystem kfs) throws IOException {
// 从classess/rules加载的规则
//获取初始化规则文件所在路径
String path = null;
for (Resource file : getRuleFiles(drlName)) {
path = RuleEngineConfig.RULES_PATH + file.getFilename();
log.info("path=" + path);
//将规则文件写规则引擎系统内
kfs.write(ResourceFactory.newClassPathResource(path, "UTF-8"));
}
}
private Resource[] getRuleFiles(String drlName) throws IOException {
if (StringUtils.isEmpty(drlName)) {
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
return resourcePatternResolver.getResources(RuleEngineConfig.BASE_RULES_PATH + RuleEngineConfig.RULES_PATH + "**/*.*");
}
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
return resourcePatternResolver.getResources(RuleEngineConfig.BASE_RULES_PATH + RuleEngineConfig.RULES_PATH + "**/" + drlName + ".*");
}
}
三、验证测试
1、创建测试实体类
@Data
public class QueryParam {
private String paramId;
private String paramSign;
}
2、添加规则实现
@Slf4j
@Service
public class RuleEngineService {
/**
* 插入规则
*
* @param param
*/
public void executeAddRule(QueryParam param) {
log.info("参数数据:" + param.getParamId() + ";" + param.getParamSign());
log.info("插入规则");
}
/**
* 移除规则
*
* @param param
*/
public void executeRemoveRule(QueryParam param) {
log.info("参数数据:" + param.getParamId() + ";" + param.getParamSign());
log.info("移除规则");
}
}
3、API调用实现
@RestController
@RequestMapping(value = "test")
public class TestController {
@Autowired
private RuleEngineService ruleEngineService;
@Autowired
private ReloadDroolsRules reloadDroolsRules;
@RequestMapping("/param")
public void param (){
QueryParam queryParam1 = new QueryParam() ;
queryParam1.setParamId("1");
queryParam1.setParamSign("+");
QueryParam queryParam2 = new QueryParam() ;
queryParam2.setParamId("2");
queryParam2.setParamSign("-");
QueryParam queryParam3 = new QueryParam() ;
queryParam3.setParamId("3");
queryParam3.setParamSign("-");
// 入参
KieUtils.getKieSession().insert(queryParam2) ;
KieUtils.getKieSession().insert(queryParam3) ;
KieUtils.getKieSession().insert(queryParam1) ;
KieUtils.getKieSession().insert(this.ruleEngineService) ;
// 返参
KieUtils.getKieSession().fireAllRules() ;
}
@RequestMapping("/reload")
public String reload (String ruleName) throws Exception {
// 返参
reloadDroolsRules.reload(ruleName);
return "新规则重载成功";
}
}
运行服务,查看结果
调用 localhost:9666/api/v1/test/param
修改规则文件,调用重新加载配置 localhost:9666/api/v1/test/reload,不指定配置规则文件名,默认重新加载全部规则,然后重新调用请求
需要注意点:修改规则配置,是修改加载后的文件,也就是 运行项目时,生成的 target目录下加载的规则文件,而不是项目本身resources下的。
参考文献: