持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第9天,点击查看活动详情
1、前言
在上一篇的分享中(juejin.cn/post/710504…)为大家介绍了Drools的一些基本知识,但在实际开发中,还是做了一些工具类进行了二次封装,同时也将规则存储到数据库中,而不是文件里。本篇就为大家介绍下。
2、封装Rules工具类
这里主要是将规则的执行、创建会话、创建知识库等常用方法进行了二次封装,方便后续傻瓜化开发。有了工具类,以后只需要关心规则的编写即可。
/**
* 规则和规则模板的为多对一关系,规则生成的规则及默认信息:
* 1)不指定参数,生成规则
* 规则名:规则模板名
* 规则容器名:规则名_KB
* 规则会话名:规则名_KS
* 2)指定参数,生成规则
* 规则名:规则模板名_规则参数名
* 规则容器名:规则名_KB
* 规则会话名:规则名_KS
*
* @ClassName: RuleUtil
* @Description: 规则引擎工具类
*/
public class RuleUtil {
public static KieContainer buildContainer(ReleaseId releaseId){
KieServices kieServices = KieServices.Factory.get();
return kieServices.newKieContainer(releaseId);
}
public static void buildKie(KieFileSystem kieFileSystem){
KieServices kieServices = KieServices.Factory.get();
KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem);
kieBuilder.buildAll();
}
public static KieFileSystem newKieFileSystem(){
KieServices kieServices = KieServices.Factory.get();
return kieServices.newKieFileSystem();
}
public static KieModuleModel buildKieModuleModel(String kieBaseName, String pkgName, String kieSessionName){
KieServices kieServices = KieServices.Factory.get();
KieModuleModel kieModuleModel = kieServices.newKieModuleModel();
KieBaseModel kieBaseModel = kieModuleModel.newKieBaseModel(kieBaseName).addPackage(pkgName);
kieBaseModel.newKieSessionModel(kieSessionName);
return kieModuleModel;
}
/**
* 有状态会话执行
* @param kieSession
* @param factList
*/
public static void execEval(KieSession kieSession, Object ...factList){
for (Object fact : factList) {
kieSession.insert(fact);
}
kieSession.fireAllRules();
for (FactHandle factHandle : kieSession.getFactHandles()) {
kieSession.delete(factHandle);
}
kieSession.dispose();
}
/**
* @description 启动定时器规则
* @param kieSession 规则会话
* @param factList 事实列表
* @return void
* @modified
* @throws
*/
public static void fireUntilHalt(KieSession kieSession, Object ...factList){
for (Object fact : factList) {
kieSession.insert(fact);
}
kieSession.fireUntilHalt();
}
/**
* @description 停止监视器规则,并销毁会话资源
* @param kieSession 规则回话
* @return void
* @modified
* @throws
*/
public static void haltAndDispose(KieSession kieSession){
kieSession.halt();
Collection<FactHandle> factHandles = kieSession.getFactHandles();
for (FactHandle factHandle : factHandles) {
kieSession.delete(factHandle);
}
kieSession.dispose();
}
/**
* 根据DRT文件构建会话
* @param drtClasspath
* @param templateName
* @param paramName
* @param packageName
* @param paramCollection
* @return
* @throws IOException
*/
public static KieSession buildSessionByDrtAndParam(String drtClasspath, String templateName, String paramName, String packageName, Collection<Map> paramCollection) throws IOException {
KieContainer kieContainer = buildKieContainerByDrtFile(drtClasspath, templateName, paramName, packageName, paramCollection);
return kieContainer.newKieSession(buildKieSessionName(templateName, paramName));
}
/**
* 根据DRT文件创建容器
* 将根据参数创建默认的DRL路径、知识库名、会话名
* @param drtClasspath
* @param templateName
* @param paramName
* @param templatePkgName
* @param paramCollection
* @return
* @throws IOException
*/
public static KieContainer buildKieContainerByDrtFile(String drtClasspath, String templateName, String paramName, String templatePkgName, Collection<Map> paramCollection) throws IOException {
String packageName = StringUtils.isEmpty(paramName) ? templatePkgName : (templatePkgName + DOT + paramName) ;
String drlPath = buildDrlPath(packageName, templateName, paramName);
String drl = buildDrlStringByDrtFile(drtClasspath, paramCollection);
String kieBaseName = buildKieBaseName(templateName, paramName);
String kieSessionName = buildKieSessionName(templateName, paramName);
return buildKieContainer(packageName, drlPath, drl, kieBaseName, kieSessionName);
}
/**
* 使用
* @param packageName
* @param drlPath
* @param drl
* @param kieBaseName
* @param kieSessionName
* @return
*/
public static KieContainer buildKieContainer(String packageName, String drlPath, String drl
, String kieBaseName, String kieSessionName) {
return buildKieContainer(packageName, drlPath, drl, kieBaseName, kieSessionName, DEFAULT_RELEASEID_ID);
}
/**
* 根据DRL创建容器,使用指定ReleaseId
* @param packageName
* @param drlPath
* @param drl
* @param kieBaseName
* @param kieSessionName
* @param releaseId 发布ID,不指定使用默认的
* @return
*/
public static KieContainer buildKieContainer(String packageName, String drlPath, String drl
, String kieBaseName, String kieSessionName, ReleaseId releaseId) {
KieServices kieServices = KieServices.Factory.get();
KieModuleModel kieModuleModel = kieServices.newKieModuleModel();
KieBaseModel kieBaseModel = kieModuleModel.newKieBaseModel(kieBaseName).addPackage(packageName);
KieSessionModel kieSessionModel = kieBaseModel.newKieSessionModel(kieSessionName);
KieFileSystem fileSystem = kieServices.newKieFileSystem();
fileSystem.write(drlPath, drl.getBytes(StandardCharsets.UTF_8));
String xml = kieModuleModel.toXML();
fileSystem.writeKModuleXML(xml);
fileSystem.generateAndWritePomXML(releaseId != null ? releaseId : DEFAULT_RELEASEID_ID);
KieBuilder kb = kieServices.newKieBuilder(fileSystem);
kb.buildAll();
if (kb.getResults().hasMessages(Message.Level.ERROR)) {
throw new RuntimeException("知识库构建发生错误:" + kb.getResults().toString());
}
return kieServices.newKieContainer(releaseId != null ? releaseId : DEFAULT_RELEASEID_ID);
}
/**
* 生成知识会话名
* @param templateName 模板名
* @param paramName 参数名
* @return
*/
public static String buildKieSessionName(String templateName, String paramName) {
return templateName + (StringUtils.isEmpty(paramName) ? "" : (UNDERLINE + paramName)) + UNDERLINE + KIE_SESSION_SUFIX;
}
/**
* 生成知识库名
* @param templateName 模板名
* @param paramName 参数名
* @return
*/
public static String buildKieBaseName(String templateName, String paramName) {
return templateName + (StringUtils.isEmpty(paramName) ? "" : (UNDERLINE + paramName)) + UNDERLINE + KIE_BASE_SUFIX;
}
/**
* 根据DRT输入流生成DRL字符串
* @param paramCollection
* @param drtInputStream
* @return
*/
public static String buildDrlStringByDrtInputStream(Collection<?> paramCollection, InputStream drtInputStream){
ObjectDataCompiler compiler = new ObjectDataCompiler();
return compiler.compile(paramCollection, drtInputStream);
}
/**
* 根据DRT字符串生成DRL
* @param drtStr
* @param paramCollection
* @return
*/
public static String buildDrlStringByDrtString(String drtStr, Collection<?> paramCollection){
ObjectDataCompiler compiler = new ObjectDataCompiler();
return compiler.compile(paramCollection, new ByteArrayInputStream(drtStr.getBytes(StandardCharsets.UTF_8)));
}
/**
* 根据DRT文件生成DRL
* @param drtClasspath 文件类路径
* @param paramCollection 参数
* @return
* @throws IOException
*/
public static String buildDrlStringByDrtFile(String drtClasspath, Collection<Map> paramCollection) throws IOException {
ObjectDataCompiler compiler = new ObjectDataCompiler();
InputStream inputStream = ResourceFactory.newClassPathResource(drtClasspath, RuleUtil.class).getInputStream();
return compiler.compile(paramCollection, inputStream);
}
/**
* 生成drl路径
* @param packageName 包名
* @param templateName 模板名
* @param paramName 参数名(一个模板,多组参数,使用参数名区分)
* @return
*/
public static String buildDrlPath(String packageName, String templateName, String paramName) {
String dir = RESOURCE_ROOT_PATH + FORWARD_SLASH + packageName.replace(DOT, FORWARD_SLASH) + FORWARD_SLASH;
String fileName = templateName
+ (StringUtils.isEmpty(paramName) ? "" : (UNDERLINE + paramName)) + DRL_FILE_SURFIX;
return dir + fileName;
}
/**
* 根据DRT文件获取会话
* @param drtClasspath
* @return
*/
public static KieSession getKieSessionByDrtFile(String drtClasspath, Collection<Map> paramCollection) throws IOException {
String drl = buildDrlStringByDrtFile(drtClasspath, paramCollection);
KieHelper kieHelper = new KieHelper();
kieHelper.addContent(drl, ResourceType.DRL);
return kieHelper.build().newKieSession();
}
/**
* 根据DRL文件获取会话
* @param drlClasspath
* @return
*/
public static KieSession getKieSessionByDrlFile(String drlClasspath) {
Resource resource = ResourceFactory.newClassPathResource(drlClasspath, StandardCharsets.UTF_8.toString());
KieHelper kieHelper = new KieHelper();
kieHelper.addResource(resource, ResourceType.DRL);
return kieHelper.build().newKieSession();
}
/**
* 获取规则名
* @param templateName 模板名
* @param templateParamName 参数名
* @return
*/
public static String buildRuleName(String templateName, String templateParamName){
return templateName + (StringUtils.isEmpty(templateParamName) ? "" : UNDERLINE + templateParamName);
}
/**
* 获取artifactid
* @param templateName
* @param templateParamName
* @return
*/
public static String buildDefaultArtifactId(String templateName, String templateParamName){
return templateName + (StringUtils.isEmpty(templateParamName) ? "" : UNDERLINE + templateParamName);
}
/**
* 根据规则名获取规则库名
* @param ruleName
* @return
*/
public static String buildBaseNameByRuleName(String ruleName){
return ruleName + UNDERLINE + KIE_BASE_SUFIX;
}
/**
* 根据规则名获取规会话名
* @param ruleName
* @return
*/
public static String buildSessionNameByRuleName(String ruleName){
return ruleName + UNDERLINE + KIE_SESSION_SUFIX;
}
/**
* @Title: getDrl
* @Description: 获取规则引擎模板
* @param: fileName
* @param: sc
* @param: className
* @return: String(返回类型)
* @throws
*/
public static String getDrl(String fileName, ServletContext sc, String className){
//根据文件名获取(本应用包中的)规则文件
File file = FileUtil.getFile(fileName, sc);
//根据包含全路径的类名获取(加载jar包中的)规则文件
File classPathFile = FileUtil.getFile(fileName, className);
if(null != classPathFile){
file = classPathFile;
}
//根据绝对路径获取规则文件
File absolutePathFile = FileUtil.getFile(fileName);
if(null != absolutePathFile){
file = absolutePathFile;
}
if(null != file){
return FileUtil.getStringByFile(file);
}
return "";
}
}
3、项目启动时加载所有Rules至KieContainer内
因为所有的规则内容存储到了数据库中,所以我们需要在项目启动的时候,将所有的Rules加载到规则容器内。这里我是使用的
@PostConstruct注解。
/**
* @ClassName: RuleBuilder
* @Description: 规则引擎创建类
*/
@Component
@Slf4j
public class RuleBuilder {
@Autowired
private RuleDao ruleDao;
@Autowired
private RuleTemplateDao ruleTemplateDao;
private static KieServices kieService = KieServices.Factory.get();
private static volatile Map<String, KieContainer> kieContainerMap = new ConcurrentHashMap<>();
@PostConstruct
public void initKieContainer(){
loadAllKieContainer();
}
/**
* 加载全部配置规则到容器
*/
public synchronized void loadAllKieContainer() throws Exception {
List<Rule> ruleList = ruleDao.findAll();
for (Rule rule : ruleList) {
String ruleName = RuleUtil.buildRuleName(rule.getRulename(), rule.getTemplateparamname());
try{
if(rule.getTemplatename() == null){
log.error("规则【{}】加载失败,未查询到规则模板。", ruleName);
continue;
}
Collection<Map> paramCollection = getParamCollection(rule);
Rule updateRule = loadKieContainer(rule, paramCollection);
ruleDao.update(updateRule);
}catch(Exception e){
log.error("规则【{}】加载发生异常。", ruleName, e);
}
}
}
private Collection<Map> getParamCollection(Rule rule) {
Collection<Map> paramCollection = new ArrayList<>();
Map<String, Object> param = new HashMap<>();
param.put("ruleName", RuleUtil.buildRuleName(rule.getTemplatename().getTemplatename(), rule.getTemplateparamname()));
paramCollection.add(param);
return paramCollection;
}
/**
* @description 根据规则模板名(必须)、规则模板参数名,使用默认配置构造规则库(详见RuleUtil),目前不支持自定义
* @param rule :
* @return org.cpms.common.rules.entity.Rule
* @modified
* @throws
*/
public Rule loadKieContainer(Rule rule, Collection<?> paramCollection) {
// 容器参数
String templateName = rule.getTemplatename().getTemplatename();
String templateParamName = rule.getTemplateparamname();
String templatePkgName = rule.getTemplatename().getTemplatepkgname();
String drtString = rule.getTemplatename().getDrt();
// 生成容器
String ruleName = RuleUtil.buildRuleName(templateName, templateParamName);
String packageName = StringUtils.isEmpty(templateParamName) ? templatePkgName : (templatePkgName + RuleUtil.DOT + templateParamName) ;
String drlPath = RuleUtil.buildDrlPath(packageName, templateName, templateParamName);
String drl = RuleUtil.buildDrlStringByDrtString(drtString, paramCollection);
String kieBaseName = RuleUtil.buildKieBaseName(templateName, templateParamName);
String kieSessionName = RuleUtil.buildKieSessionName(templateName, templateParamName);
String artifactId = RuleUtil.buildDefaultArtifactId(templateName, templateParamName);
ReleaseId releaseId = KieServices.Factory.get().newReleaseId(RuleUtil.DEFAULT_GROUP_ID, artifactId, RuleUtil.DEFAULT_VERSION);
KieContainer kieContainer = RuleUtil.buildKieContainer(packageName, drlPath, drl
, kieBaseName, kieSessionName, releaseId);
//存在则覆盖
kieContainerMap.put(ruleName, kieContainer);
// 规则信息保存
rule.setGroupid(RuleUtil.DEFAULT_GROUP_ID);
rule.setArtifactid(artifactId);
rule.setVersion(RuleUtil.DEFAULT_VERSION);
rule.setRulename(ruleName);
rule.setDrl(drl);
rule.setKbasename(kieBaseName);
rule.setKsessionname(kieSessionName);
rule.setPkgname(packageName);
rule.setUpdatetime(new Date());
return rule;
}
/**
* @description 根据规则模板名称、模板参数名称生成或更新规则,并加载规则容器
* @param templateName :
* @param templateParamName :
* @return void
* @modified
* @throws
*/
public void loadKieContainer(String templateName, String templateParamName) throws Exception {
String ruleName = RuleUtil.buildRuleName(templateName, templateParamName);
Rule rule = ruleDao.findByRuleName(ruleName);
boolean updateRule = true;
if(rule == null){
rule = new Rule();
updateRule = false;
}
rule.setTemplateparamname(templateParamName);
RuleTemplate ruleTemplate = ruleTemplateDao.findByTemplateName(templateName);
String ruleTemplateStr = ruleTemplate.toString();
if(ruleTemplate == null){
String errMsg = MessageFormat.format("加载规则容器失败,未查询到规则模板【{0}】。"
, templateName);
log.error(errMsg);
throw new Exception(errMsg);
}
String drt = ruleTemplate.getDrt();
rule.setTemplatename(ruleTemplate);
rule = loadKieContainer(rule, getParamCollection(rule));
if(updateRule){
ruleDao.update(rule);
}else{
ruleDao.save(rule);
}
log.info("规则容器【{}】加载成功。", rule.getRulename());
}
public void removeContainerAndRule(String ruleName) throws Exception {
removeRuleByName(ruleName);
removeKieContainerByRuleName(ruleName);
}
/**
* @description 移除容器
* @param ruleName 规则名称
* @return boolean
* @modified
* @throws
*/
public static void removeKieContainerByRuleName(String ruleName){
KieContainer disposedKieContainer = kieContainerMap.remove(ruleName);
if(disposedKieContainer != null){
disposedKieContainer.dispose();
KieServices.Factory.get().getRepository().removeKieModule(disposedKieContainer.getReleaseId());
}
}
/**
* @description 移除规则条目
* @param ruleName 规则名称
* @return void
* @modified
* @throws
*/
public void removeRuleByName(String ruleName) throws Exception {
Rule rule = ruleDao.findByRuleName(ruleName);
if(rule != null){
ruleDao.delete(rule);
}
}
public static synchronized KieSession newKieSession(String ruleName){
KieContainer kieContainer = getKieContainer(ruleName);
if(kieContainer == null){
log.info("未查询到规则名为【{}】的规则容器。", ruleName);
return null;
}
return kieContainer.newKieSession(RuleUtil.buildSessionNameByRuleName(ruleName));
}
public static KieContainer getKieContainer(String ruleName) {
return kieContainerMap.get(ruleName);
}
}
经过上述的二次封装,大大的方便了其他组员的开发,只需专注规则的编写,同时也方便规则引擎的统一管理与升级。
好了、本期就先介绍到这里,有什么需要交流的,大家可以随时私信我。😊