Drools学习
1.springboot+drools+stringTemplate
1.1主要依赖导入
<!--使用drools的主要依赖-->
<dependency><groupId>org.drools</groupId><artifactId>drools-compiler</artifactId><version>7.73.0.Final</version>
</dependency>
<!--主要模板依赖-->
<dependency><groupId>org.antlr</groupId><artifactId>ST4</artifactId><version>4.3.4</version>
</dependency>
2主要方法
1.2.1 drools的工具方法
下面的大部分容器其实本质上还是key-value的HashMap
@Component
pulica class DroolsManager {private static Logger log = LoggerFactory.getLogger(DroolsManager.class);//drools服务类 后续kfs和kmm 以及 kie容器都是由它生成//这个类本身就是单例//KieServices.Factory.get();也可以生成 做了封装private static KieServices kieServices = KieServices.get();//文件系统-主要是存储 规则文件的 因此需要全局唯一一个 进行缓存,避免每次都要创建private static KieFileSystem kieFileSystem = kieServices.newKieFileSystem();//可以理解为构建kmodule.xmlprivate static KieModuleModel kieModuleModel = kieServices.newKieModuleModel();//需要全局唯一一个,如果每次价格规则都新创建一个,那么就需要销毁之前创建的看kiecontainer//public volatile static KieContainer kieContainer = kieServices.newKieContainer(kieServices.getRepository().getDefaultReleaseId());public volatile static KieContainer kieContainer;/*** 创建kieContainer* 调用方法前先进行非空判定 加锁后继续进行一次判空,避免多线程重复创建*/private static void createKieContainer() {synchronized (KieContainer.class) {if (kieContainer == null) {kieContainer = kieServices.newKieContainer(kieServices.getRepository().getDefaultReleaseId());}}}/*** 判定kbase,所有一切运行的前提是kieContainer和KieBase被创建* kbase是一个关键的类,它主要存放kieSession和kbasePackageName* Map<String, KieSessionModel> kSessions;* List<String> packages;* @param kieBaseName kie基本名称* @return*/private boolean existsKieBase(String kieBaseName) {if (null == kieContainer) {log.warn("需要创建kieContainer");return false;}if (kieContainer.getKieBaseNames().contains(kieBaseName)) {return true;}log.warn("需要创建KieBase:{}", kieBaseName);return false;}//添加和修改规则方法如下
}
/*** 添加或更新 drools规则* 更新drools规则 本质上是对KieFileSystem的更新 与其他的kie对象基本无关* 后续可以将这个方法进行拆分解耦* @param droolsRuleDTO*/public void addOrUpdateRule(DroolsRuleDTO droolsRuleDTO) {//获取kbase的名称//获取kbase包的名称//判断给kbase是否存在String kieBaseName = droolsRuleDTO.getKieBaseName();String kiePackageName = droolsRuleDTO.getKiePackageName();boolean existsKieBase = existsKieBase(kieBaseName);//该对象对应module.xml 中的kbase标签KieBaseModel kieBaseModel = null;//如果这个kbase不存在String packageName = kieBaseName + "." + kiePackageName;if (!existsKieBase) {//创建一个kBase 根据一个kiBaseName创建一个kieBaseModellog.info("first create kieBaseName");kieBaseModel = kieModuleModel.newKieBaseModel(kieBaseName);//不是默认的kieBasekieBaseModel.setDefault(false);//设置该KieBase需要加载的包路径kieBaseModel.addPackage(packageName);//设置kie的session为有状态sessionkieBaseModel.newKieSessionModel(kiePackageName + "-session")//不是默认session.setDefault(false);} else {//获取到已经存在的base对象//kieBaseName 唯一kieBaseModel = kieModuleModel.getKieBaseModels().get(kieBaseName);//获取packageList<String> packages = kieBaseModel.getPackages();//判定这个包是否已经存在//包不存在 >>//由于 这里kieSession和kieBasePackage使用同一个 name//不存在这个包意味着这个session也没有被添加//添加包的同时需要添加sessionif (!packages.contains(packageName)) {kieBaseModel.addPackage(packageName).newKieSessionModel(kiePackageName + "-session").setDefault(false);log.info("kieBase:{}添加一个新的包:{},添加一个新的session:{}-session", kieBaseName, kiePackageName, kiePackageName);} else {//存在就不用初始化他了kieBaseModel = null;}}String file =DroolCommon.DROOL_PACKAGE_PREFIX + packageName + DroolCommon.DROOL_PACKAGE_I + kiePackageName + DroolCommon.DROOL_PACKAGE_SUFFIX;log.info("加载虚拟规则文件:{}", file);//不为null则表示不是第一次if (kieBaseModel != null) {//只有当kieSession不存在的时候才需要进行 kieFileSystem的内容初始化String kmoduleXml = kieModuleModel.toXML();
// log.info("加载kmodule.xml:[\n{}]", kmoduleXml);kieFileSystem.writeKModuleXML(kmoduleXml);}// String ruleContent = droolsRuleDTO.getRuleContent();String ruleContent = rule(droolsRuleDTO);//2022/9/27 这里的写入应该不是追加 因为代码中并没有涉及到drl文件下的ruleName,所以本质上,无论是添加还是修改,本质上都是会涉及到所有已经存在该session下的规kieFileSystem.write(file, ruleContent);KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem);//通过KieBuilder构建KieModule下所有的KieBasekieBuilder.buildAll();//获取构建过程中的结果Results results = kieBuilder.getResults();//获取构建过程中的结果List<Message> messages = results.getMessages(Message.Level.ERROR);if (null != messages && !messages.isEmpty()) {for (Message message : messages) {log.error(message.getText());}throw new RuntimeException("加载规则出现异常");}//keiContainer只有第一次时才需要创建,之后就是使用这个if (null == kieContainer) {log.info("first create kieContainer");createKieContainer();} else {//实现动态更新log.info("update kieContainer");((KieContainerImpl) kieContainer).updateToKieModule((InternalKieModule) kieBuilder.getKieModule());}}
//kiesession调用
KieSession kieSession = DroolsManager.kieContainer.newKieSession(droolsRuleDTO.getKiePackageName() + "-session");
1.2.1-1 KieSession的创建和调用的关系梳理(部分源码梳理)?
KieContainer获取kieSession源码(部分)
//这里的KieSession map 是KieContainer的
private final Map<String, KieSession> kSessions; //获取
public KieSession getKieSession(String kSessionName)
{KieSession kieSession = (KieSession)this.kSessions.get(kSessionName);return kieSession != null ? kieSession : this.newKieSession(kSessionName);
}
//新建
public KieSession newKieSession(String kSessionName, Environment environment, KieSessionConfiguration conf)
{KieSessionModelImpl kSessionModel = kSessionName != null ? (KieSessionModelImpl)this.getKieSessionModel(kSessionName) : (KieSessionModelImpl)this.findKieSessionModel(false);KieBase kBase = this.getKieBaseFromKieSessionModel(kSessionModel);if (kBase == null) {return null;} else {KieSession kSession = kBase.newKieSession(conf != null ? conf : this.getKieSessionConfiguration((KieSessionModel)kSessionModel), environment);this.registerNewKieSession(kSessionModel, (InternalKnowledgeBase)kBase, kSession);return kSession;}
}
public KieSessionModel getKieSessionModel(String kSessionName) {return this.kProject.getKieSessionModel(kSessionName);
}//注册
private void registerNewKieSession(KieSessionModel kSessionModel, InternalKnowledgeBase kBase, KieSession kSession) {if (Drools.isJndiAvailable()) {InjectionHelper.wireSessionComponents(kSessionModel, kSession);}this.registerLoggers(kSessionModel, kSession);this.registerCalendars(kSessionModel, kSession);((StatefulKnowledgeSessionImpl)kSession).initMBeans(this.containerId, kBase.getId(), kSessionModel.getName());this.kSessions.put(kSessionModel.getName(), kSession);
}
KieContainer的创建和Kproject的引入 (部分)
class KieServiceImpl{public KieContainer newKieContainer(String containerId, ReleaseId releaseId, ClassLoader classLoader) {InternalKieModule kieModule = (InternalKieModule)this.getRepository().getKieModule(releaseId);//创建kprojectKieProject kProject = new KieModuleKieProject(kieModule, classLoader);KieContainerImpl newContainer;if (containerId == null) {newContainer = new KieContainerImpl(UUID.randomUUID().toString(), kProject, this.getRepository(), releaseId);return newContainer;} else if (this.kContainers.get(containerId) == null) {newContainer = new KieContainerImpl(containerId, kProject, this.getRepository(), releaseId);//CurrentHashMap kContainersKieContainer check = (KieContainer)this.kContainers.putIfAbsent(containerId, newContainer);if (check == null) {return newContainer;}}}
}
KieBaseModel创建KieSession 源码
//具体调用查看 drools-compiler 源码
class KieBaseModel{public KieSessionModel newKieSessionModel(String name) {KieSessionModel kieSessionModel = new KieSessionModelImpl(this, name);Map<String, KieSessionModel> newMap = new HashMap();newMap.putAll(this.kSessions);newMap.put(kieSessionModel.getName(), kieSessionModel);this.setKSessions(newMap);return kieSessionModel;}
}
待补充……
1.2.1-2 KModule.xml的大概规则
<?xml version="1.0" encoding="UTF-8"?>
<kmodule xmlns="http://jboss.org/kie/6.0.0/kmodule"><kbase name="SimpleRuleKBase" packages="com.us.person"><ksession name="simpleRuleKSession"/></kbase>
</kmodule>
1.2.1-3 kieService和其他kie容器的类图(部分)
待补充……
1.2.2 StringTemplate具体方法
书写关于drl的stg的前提是 了解drl文件规则组成
public String st4Util(DroolsRuleDTO droolsRuleDTO) {//通过模板文件读取URL resource = UUIDUtil.class.getClassLoader().getResource("templates/drools/people_rules.stg");STGroup group = new STGroupFile(Objects.requireNonNull(resource).getFile());//wordImport(rules,package,kieBaseName)ST stFile = group.getInstanceOf("wordImport");String kiePackageName = droolsRuleDTO.getKiePackageName();String kieBaseName = droolsRuleDTO.getKieBaseName();stFile.add("package", kiePackageName);stFile.add("kieBaseName", kieBaseName);List<DroolsRuleDTO.Rule> rules = droolsRuleDTO.getRule();for (DroolsRuleDTO.Rule rule : rules) {//ruleValue(ruleName,salience,lhs,value,remark)ST stRule = group.getInstanceOf("ruleValue");String ruleName = rule.getRuleName();String lhs = rule.getLhs();Integer salience = rule.getSalience();Object value = rule.getValue();String remark = rule.getRemark();stRule.add("ruleName", ruleName);stRule.add("salience", salience);stRule.add("lhs", lhs);stRule.add("value", value);stRule.add("remark", remark);stFile.add("rules", stRule);}return stFile.render();
}
test.stg
//stg => :=<< 表示一个group >>
//stg => wordImport groupName
wordImport(rules,package,kieBaseName) ::=<<//drl =>> package就是文件所在地址 可以是任意 但是在动态替换时 需要 xx.xx 不然报错 具体原因不清楚 也可能是别的地方引发的问题
package <kieBaseName>.<package>//drl => import就是导入包 和java中一样 rule规则体中用到哪个类 就需要导入相应的包
import com.lujy.drools_learning.pojo.User
import com.lujy.drools_learning.pojo.Account
import com.lujy.drools_learning.pojo.MyLog
import java.util.Date//drl => global 全局变量 对于引用数据类型而言 他们的变化只发生在规则内部 规则体外部还是原来的值,其他数据类型则内外同时变化
global java.util.List logList//drl => dialect属性用于指定当前规则使用的语言类型 可同时使用多个
//drl => ;可以不使用 但是不能随意使用除了 rule和function这类方体内 别的地方最好别加 会报错
dialect "java"
dialect "mvel"//drl => 方法体 对于某类重复方法的抽提
function MyLog addLog(User user,Account account,Number value,String remark){MyLog log=new MyLog();log.setUserId(user.getId());log.setCardId(account.getId());log.setContent(remark+":"+value+",账户余额:"+account.getMoney());log.setCardId(account.getId());//log.setCreateDate(new Date());return log;
}
//按照 换行来进行划分
<rules; separator="\n\n">
>>ruleValue(ruleName,salience,lhs,value,remark)::=<<
rule "<ruleName>"//drl => 跳出死循环no-loop true//drl => 设置优先级 Integer类型 数字越大 优先级别越高 同等优先级 按照顺序执行salience <salience>when//lhs 判定规则 可以为true$account:Account();$user:User(<lhs>);then//rhs 执行结果$account.setMoney($account.getMoney()+<value>);//更新对象的值 使得下个匹配的规则使用到的是更新过的值 不使用则还是原值update($account);MyLog log = addLog($user,$account,<value>,"<remark>");System.out.println(hasPass);logList.add(log);System.out.println("<remark>:<value>,账户余额:"+$account.getMoney());
end
>>
1.2.3测试
传入规则参数
{"createdTime":1664348350632,"id":1,"kieBaseName":"people","kiePackageName":"people01","rule":[{"lhs":"(sex==1&&age>=75)||(sex==0&&age>=70)","remark":"养老金发放","ruleName":"pension","salience":1,"value":200},{"lhs":"(sex==1&&age>=70)||(sex==0&&age>=65)","remark":"退休工资发放","ruleName":"Retirement_Salary","salience":9,"value":2000},{"lhs":"age<=16","remark":"独生子女补贴发放","ruleName":"One_child_allowance","salience":8,"value":100}]
}
传入测试参数
http://127.0.0.1:8081/drools/rule/fireRule?ruleId=1
ruleId是传入的参数Id 通过id获取到sessionName 调用对应session
{"id":12,"name":"zhangsan","age":80,"sex":1
}
//性别为男,年龄80岁的张三 符合两个规则 执行顺序为 退休工资->养老金
3.总结
规则的添加和更新其实是对KieFileSystem的变动.同一个session下,不论要添加或则修改一个或者多个规则,它的前提都是把这个范围下的所有rule提取出来,然后在这个里面修改,修改完毕后再一股脑的塞入到该session下