概述
前面我們實(shí)現(xiàn)了對規(guī)則文件的讀取和執(zhí)行,也知道了規(guī)則文件的基本結(jié)構(gòu)與相關(guān)函數(shù)及應(yīng)用,同時(shí)也學(xué)習(xí)了規(guī)則引擎的組件及規(guī)則的加載解析過程?,F(xiàn)在將解決最后一個(gè)問題,我們開發(fā)中的需求是不會(huì)事先寫好規(guī)則文件文件在啟動(dòng)項(xiàng)目的,如果是這樣當(dāng)規(guī)則文件修改新增或刪除時(shí)就必須重啟服務(wù)器,這樣才能使規(guī)則生效,這顯然是不合理的,接下來我們將在《Drools入門(一)——環(huán)境搭建》的基礎(chǔ)上進(jìn)行修改,使規(guī)則引擎動(dòng)態(tài)加載外部規(guī)則,并實(shí)現(xiàn)新增和刪除功能
添加KieModuleBuilder類
新建一個(gè)包命名為builder,并在builder包下新建KieModuleBuilder.java,并將下面內(nèi)容復(fù)制到該類中
package builder;
import entity.Rule;
import entity.RuleGroup;
import org.drools.compiler.compiler.io.memory.MemoryFileSystem;
import org.drools.compiler.kie.builder.impl.KieContainerImpl;
import org.drools.compiler.kie.builder.impl.KieProject;
import org.drools.compiler.kie.builder.impl.MemoryKieModule;
import org.drools.compiler.kie.builder.impl.ResultsImpl;
import org.drools.compiler.kproject.models.KieBaseModelImpl;
import org.drools.compiler.kproject.models.KieModuleModelImpl;
import org.kie.api.KieServices;
import org.kie.api.builder.KieFileSystem;
import org.kie.api.builder.KieRepository;
import org.kie.api.builder.ReleaseId;
import org.kie.api.builder.model.KieBaseModel;
import org.kie.api.builder.model.KieModuleModel;
import org.kie.api.builder.model.KieSessionModel;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.StatelessKieSession;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class KieModuleBuilder {
private static final String RESOURCES_ROOT = "src/main/resources/";
private static final String FILE_SEPARATOR = "/";
private static final String PACKAGE_NAME_PREFIX = "com.dome.rules.id_";
private static final String PACKAGE_PATH_PREFIX = PACKAGE_NAME_PREFIX.replaceAll("\\.", FILE_SEPARATOR);
private static final String KIE_BASE_MODEL_NAME_PREFIX = "kieBaseModelName_";
private static final String KIE_SESSION_MODEL_NAME_PREFIX = "kieSessionModelName_";
private static final String PACKAGE = "package ";
private static final String FILE_SUFFIX = ".drl";
/**
* 創(chuàng)建模塊化組件并添加到初始化文件系統(tǒng)中,初始化文件系統(tǒng)在完成加載后將摒棄
* @param kieServices
* @param kieFileSystem
* @param ruleGroups
* @return
*/
public static KieModuleModel initAndBuildKieModuleModel(KieServices kieServices, KieFileSystem kieFileSystem, List<RuleGroup> ruleGroups){
boolean isDefault = true;
KieModuleModel kieModuleModel = kieServices.newKieModuleModel();
for (RuleGroup ruleGroup : ruleGroups) {
if (ruleGroup.getRules() != null && ruleGroup.getRules().size() > 0){
buildKieBaseModel(kieModuleModel, ruleGroup, isDefault);
isDefault = false;
}
}
kieFileSystem.write(KieModuleModelImpl.KMODULE_SRC_PATH,kieModuleModel.toXML());
return kieModuleModel;
}
/**
* 添加規(guī)則文件到初始化文件系統(tǒng)中,初始化文件系統(tǒng)在完成加載后將摒棄
* @param kieFileSystem
* @param ruleGroups
* @return
*/
public static List<String> initAndBuildRules(KieFileSystem kieFileSystem, List<RuleGroup> ruleGroups){
List<String> rules = new ArrayList<>();
for (RuleGroup ruleGroup : ruleGroups) {
for (Rule rule : ruleGroup.getRules()) {
String ruleContent = buildRule(ruleGroup, rule);
rules.add(ruleContent);
kieFileSystem.write(RESOURCES_ROOT + getRuleFilePath(ruleGroup.getId(), rule.getId()), ruleContent);
}
}
return rules;
}
/**
* 初始化session組件,用于規(guī)則引擎使用入口
* @param kieContainer
* @param ruleGroups
* @return
*/
public static Map<RuleGroup, StatelessKieSession> initStatelessKieSession(KieContainer kieContainer, List<RuleGroup> ruleGroups){
Map<RuleGroup, StatelessKieSession> statelessKieSessionMap = new LinkedHashMap<>();
for (RuleGroup promotionRule : ruleGroups) {
statelessKieSessionMap.put(promotionRule, buildKieSession(kieContainer, promotionRule));
}
return statelessKieSessionMap;
}
/**
* 檢查規(guī)則庫中是否已經(jīng)存在該規(guī)則模塊
* @param kieRepository
* @param ruleGroup
* @param releaseId
* @return
*/
public static Boolean checkBaseModule(KieRepository kieRepository, RuleGroup ruleGroup, ReleaseId releaseId){
MemoryKieModule kieModule = (MemoryKieModule)kieRepository.getKieModule(releaseId);
return kieModule.getKieModuleModel().getKieBaseModels().containsKey(getKieBaseModelName(ruleGroup.getId()));
}
/**
* 添加規(guī)則文件及創(chuàng)建模塊化組件并添加到文件系統(tǒng)中,此處為后期動(dòng)態(tài)添加,該文件系統(tǒng)與初始化時(shí)的文件系統(tǒng)不是同一個(gè)
* @param kieContainer
* @param kieRepository
* @param ruleGroup
* @return
*/
public static StatelessKieSession addBaseModuleAndRule(KieContainer kieContainer, KieRepository kieRepository, RuleGroup ruleGroup){
MemoryKieModule kieModule = (MemoryKieModule)kieRepository.getKieModule(kieContainer.getReleaseId());
MemoryFileSystem memoryFileSystem = kieModule.getMemoryFileSystem();
for (Rule rule : ruleGroup.getRules()) {
String ruleContent = buildRule(ruleGroup, rule);
memoryFileSystem.write(getRuleFilePath(ruleGroup.getId(), rule.getId()), ruleContent.getBytes(Charset.defaultCharset()));
}
KieBaseModel kieBaseModel = buildKieBaseModel(kieModule.getKieModuleModel(), ruleGroup, false);
KieProject kieProject = ((KieContainerImpl) kieContainer).getKieProject();
((KieContainerImpl) kieContainer).updateToKieModule(kieModule);
kieProject.buildKnowledgePackages((KieBaseModelImpl) kieBaseModel,new ResultsImpl());
return buildKieSession(kieContainer, ruleGroup);
}
/**
* 從文件系統(tǒng)中刪除規(guī)則文件及刪除模塊化組件,此處為后期動(dòng)態(tài)添加,該文件系統(tǒng)與初始化時(shí)的文件系統(tǒng)不是同一個(gè)
* @param kieContainer
* @param kieRepository
* @param ruleGroup
*/
public static void removeBaseModuleAndRule(KieContainer kieContainer, KieRepository kieRepository, RuleGroup ruleGroup){
MemoryKieModule kieModule = (MemoryKieModule)kieRepository.getKieModule(kieContainer.getReleaseId());
MemoryFileSystem memoryFileSystem = kieModule.getMemoryFileSystem();
for (Rule rule : ruleGroup.getRules()) {
memoryFileSystem.remove(getRuleFilePath(ruleGroup.getId(), rule.getId()));
}
String kieBaseModelName = getKieBaseModelName(ruleGroup.getId());
String kieSessionModelName = getKieSessionModelName(ruleGroup.getId());
KieModuleModel kieModuleModel = kieModule.getKieModuleModel();
kieModuleModel.getKieBaseModels().get(kieBaseModelName).removeKieSessionModel(kieSessionModelName);
kieModuleModel.removeKieBaseModel(kieBaseModelName);
//偽刪除builder,其實(shí)刪不刪都一樣,同名時(shí)會(huì)覆蓋,源碼也沒有提供刪除方法
kieModule.cacheKnowledgeBuilderForKieBase(kieBaseModelName,null);
kieModule.getKnowledgeResultsCache().remove(kieBaseModelName);
((KieContainerImpl) kieContainer).updateToKieModule(kieModule);
}
/**
* 規(guī)則構(gòu)建
* @param ruleGroup
* @param rule
* @return
*/
private static String buildRule(RuleGroup ruleGroup, Rule rule){
StringBuilder ruleContent = new StringBuilder(PACKAGE);
ruleContent.append(getPackageName(ruleGroup.getId())).append("\n");
ruleContent.append(rule.getContent());
return ruleContent.toString();
}
/**
* Base模塊構(gòu)建
* @param kieModuleModel
* @param ruleGroup
* @param isDefault
* @return
*/
private static KieBaseModel buildKieBaseModel(KieModuleModel kieModuleModel, RuleGroup ruleGroup, Boolean isDefault){
KieBaseModel kieBaseModel = kieModuleModel.newKieBaseModel(getKieBaseModelName(ruleGroup.getId()));
kieBaseModel.setDefault(isDefault);
kieBaseModel.addPackage(getPackageName(ruleGroup.getId()));
//只要無狀態(tài)session
KieSessionModel kieSessionModel = kieBaseModel.newKieSessionModel(getKieSessionModelName(ruleGroup.getId()));
kieSessionModel.setDefault(isDefault);
kieSessionModel.setType(KieSessionModel.KieSessionType.STATELESS);
return kieBaseModel;
}
private static StatelessKieSession buildKieSession(KieContainer kieContainer, RuleGroup ruleGroup){
return kieContainer.newStatelessKieSession(getKieSessionModelName(ruleGroup.getId()));
}
private static String getPackageName(String id){
return PACKAGE_NAME_PREFIX + id;
}
private static String getRuleFilePath(String id, String ruleId){
return PACKAGE_PATH_PREFIX + id + FILE_SEPARATOR + ruleId + FILE_SUFFIX;
}
private static String getKieBaseModelName(String id){
return KIE_BASE_MODEL_NAME_PREFIX + id;
}
private static String getKieSessionModelName(String id){
return KIE_SESSION_MODEL_NAME_PREFIX + id;
}
}
添加DroolsManager類
新建manager包,并在manager包下新建DroolsManager.java,并將下面內(nèi)容復(fù)制到該類中
package manager;
import static builder.KieModuleBuilder.*;
import entity.Rule;
import entity.RuleGroup;
import org.kie.api.KieServices;
import org.kie.api.builder.KieBuilder;
import org.kie.api.builder.KieFileSystem;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.StatelessKieSession;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class DroolsManager {
public static Map<RuleGroup, StatelessKieSession> statelessKieSessionMap;
private static KieContainer kieContainer;
private static List<RuleGroup> ruleGroups;
/**
* 初始化
*/
public static void init() {
initData();
initSystemProperties();
KieServices kieServices = KieServices.Factory.get();
KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
if (ruleGroups != null && ruleGroups.size() > 0){
initAndBuildKieModuleModel(kieServices, kieFileSystem, ruleGroups);
initAndBuildRules(kieFileSystem, ruleGroups);
KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem);
kieBuilder.buildAll();
kieContainer = kieServices.newKieContainer(kieServices.getRepository().getDefaultReleaseId());
statelessKieSessionMap = initStatelessKieSession(kieContainer, ruleGroups);
}
}
/**
* 添加組規(guī)則
* @param ruleGroup
*/
public static void addModuleAndRule(RuleGroup ruleGroup){
if (kieContainer == null){ //規(guī)則引擎未被初始化
init();
return;
}
if (ruleGroup != null && !checkBaseModule(KieServices.Factory.get().getRepository(), ruleGroup, kieContainer.getReleaseId())){
statelessKieSessionMap.put(ruleGroup,addBaseModuleAndRule(kieContainer, KieServices.Factory.get().getRepository(), ruleGroup));
}
}
/**
* 刪除組規(guī)則
* @param id
* @return
*/
public static RuleGroup removeModuleAndRule(String id){
if (kieContainer == null) { //規(guī)則引擎未被初始化
init();
return getRuleGroupById(id);
}
RuleGroup ruleGroup = getRuleGroupById(id);
if (ruleGroup != null && checkBaseModule(KieServices.Factory.get().getRepository(), ruleGroup, kieContainer.getReleaseId())){
removeBaseModuleAndRule(kieContainer, KieServices.Factory.get().getRepository(), ruleGroup);
statelessKieSessionMap.remove(ruleGroup);
}
return ruleGroup;
}
/**
* 初始化drools時(shí)間格式化格式
*/
private static void initSystemProperties(){
System.setProperty("drools.dateformat","yyyy-MM-dd HH:mm:ss");
}
/**
* 初始化數(shù)據(jù)
*/
private static void initData(){
ruleGroups = new ArrayList<>();
//組一
RuleGroup ruleGroup1 = new RuleGroup("1","ruleGroupOne");
Rule rule1_1 = new Rule("1_1","ruleOne");
rule1_1.setContent("rule \"test1_1\"\n when\n eval(true)\n then\n System.out.println(\"規(guī)則中打印日志:校驗(yàn)通過!1_1\");\nend");
ruleGroup1.getRules().add(rule1_1);
Rule rule1_2 = new Rule("1_2","ruleOne");
rule1_2.setContent("rule \"test1_2\"\n when\n eval(true)\n then\n System.out.println(\"規(guī)則中打印日志:校驗(yàn)通過!1_2\");\nend");
ruleGroup1.getRules().add(rule1_2);
ruleGroups.add(ruleGroup1);
//組二
RuleGroup ruleGroup2 = new RuleGroup("2","ruleGroupTwo");
Rule rule2_1 = new Rule("2_1","ruleTwo");
rule2_1.setContent("rule \"test2_1\"\n when\n eval(true)\n then\n System.out.println(\"規(guī)則中打印日志:校驗(yàn)通過!2_1\");\nend");
ruleGroup2.getRules().add(rule2_1);
ruleGroups.add(ruleGroup2);
}
/**
* 根據(jù)組id獲取組信息
* @param id
* @return
*/
private static RuleGroup getRuleGroupById(String id){
for (Map.Entry<RuleGroup, StatelessKieSession> kieSessionEntry : statelessKieSessionMap.entrySet()) {
if (kieSessionEntry.getKey().getId().equals(id)) {
return kieSessionEntry.getKey();
}
}
return null;
}
}
創(chuàng)建Main方法運(yùn)行
新建一個(gè)類,并將下面內(nèi)容復(fù)制到該類中
public static void main(String[] args) {
//初始化規(guī)則引擎
DroolsManager.init();
execute();
System.out.println("------------------------------");
//移除組1
RuleGroup ruleGroup = DroolsManager.removeModuleAndRule("1");
execute();
System.out.println("------------------------------");
//添加組1
DroolsManager.addModuleAndRule(ruleGroup);
execute();
System.out.println("------------------------------");
}
/**
* 執(zhí)行規(guī)則引擎
*/
private static void execute(){
for (Map.Entry<RuleGroup, StatelessKieSession> kieSessionEntry : DroolsManager.statelessKieSessionMap.entrySet()) {
kieSessionEntry.getValue().execute(CommandFactory.newInsert(""));
}
}
運(yùn)行結(jié)果如下
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
規(guī)則中打印日志:校驗(yàn)通過!1_2
規(guī)則中打印日志:校驗(yàn)通過!1_1
規(guī)則中打印日志:校驗(yàn)通過!2_1
------------------------------
規(guī)則中打印日志:校驗(yàn)通過!2_1
------------------------------
規(guī)則中打印日志:校驗(yàn)通過!2_1
規(guī)則中打印日志:校驗(yàn)通過!1_2
規(guī)則中打印日志:校驗(yàn)通過!1_1
------------------------------
Process finished with exit code 0
注意:當(dāng)前只實(shí)現(xiàn)了將整個(gè)
KieBaseModel移除,前面章節(jié)說過一個(gè)KieBaseModel下面是包含多條規(guī)則的,也就是說當(dāng)我們移除組1的時(shí)候會(huì)把組1下面的兩條規(guī)則都進(jìn)行移除,所以移除組1后只會(huì)有一條規(guī)則被輸出。
針對一組里面單一規(guī)則的增刪改功能已經(jīng)實(shí)現(xiàn)但代碼目前沒有整理,后續(xù)再更新吧
場景
由于規(guī)則引擎的特性
when里面是判斷邏輯,then里面可以寫業(yè)務(wù)處理邏輯,因此規(guī)則引擎的使用場景目前個(gè)人認(rèn)為有兩種(僅為個(gè)人觀點(diǎn))
第一種是判斷條件簡單但存在多種可能性的且隨時(shí)都會(huì)進(jìn)行調(diào)整的場景,比如有5個(gè)屬性ABCDE,只要滿足條件(A=B || B=C || C=D || D=E || A=B=C || B=C=E....)的就返回結(jié)果,甚至有可能不同的條件搭配會(huì)生成特定的結(jié)果需要把他們的所有結(jié)果都收集起來,這時(shí)如果使用代碼是不現(xiàn)實(shí)的
第二種數(shù)據(jù)庫表邏輯查詢,現(xiàn)在有一張表里面有10個(gè)字段,且數(shù)據(jù)很少發(fā)生變化基本只有大量查詢,要求入?yún)BC去查這張表,表中數(shù)據(jù)類型為1的符合A=字段1即可,類型為2的要符合A=字段1且B=字段2,類型為3的要符合A=字段1且B=字段2且C=字段3....等等,SQL的查詢也是不現(xiàn)實(shí)的,這時(shí)可以選擇規(guī)則引擎,將表中的數(shù)據(jù)拼接成drl,when的條件判斷語句就是每條數(shù)據(jù)各自符合的條件,then邏輯則返回該數(shù)據(jù)的主鍵或數(shù)據(jù)本身,這時(shí)填充入?yún)⒈隳艿玫椒蠗l件的所有數(shù)據(jù)
總結(jié)
Drools是一個(gè)強(qiáng)大規(guī)范的規(guī)則引擎,以上只是它的部分功能應(yīng)用,它還支持工作流等功能,在使用前建議還是要對其有一定的了解,因?yàn)橛玫娜吮容^少網(wǎng)上資料并不多且大多數(shù)是幾年前的,否則只會(huì)寸步難行,為了配合規(guī)則引擎的使用而使用是一件令人蛋疼的事