目前世面上中文的KIE DROOLS Workbench(JBOSS BRMS)的教程几乎没有,有的也只有灵灵碎碎的使用机器来翻译的(翻的不知所云)或者是基于老版本的JBOSS Guvnor即5.x的一些教程,而且这些教程都是”缺胳膊少腿“的,初学者看后不知道它到底在干吗?能干吗?能够解决自己系统中什么问题。
规则是让业务人士驱动整个企业过程的最佳实践
对系统的使用人员
对IT开发人员
免体检累积最高限额表在规则引擎中的实现:
回溯BRMS开发教程中的那张“业务变现加速器”架构图,考虑下面的问题
这个逻辑图有点复杂,很多人看了都会感觉“不知所云”,OK,不急!我们在后文中会来“回溯”它。
世面上成熟的规则引擎有很多,著名的如:IBM 的iLog,pegga rulz(飞马),我们在这边要介绍的也是开源中最著名的jboss rulz。
Jboss Rulz最早是只有基于.drools的规则文件的一个内嵌式规则引擎,后来它发展成了“规则管理系统”即BRMS,它的BRMS被称为Guvnor。后来在JBOSS Guvnor5.x后它又改名叫"KIE Drools WorkBench“。
目前世面上中文的KIE DROOLS Workbench(JBOSS BRMS)的教程几乎没有,有的也只有灵灵碎碎的使用机器来翻译的(翻的不知所云)或者是基于老板的JBOSS Guvnor即5.x的一些教程,而且这些教程都是”缺胳膊少腿“的,初学者看后不知道它到底在干吗?能干吗?能够解决自己系统中什么问题。
如下介绍Drools使用:
1.template
drools6开始提供模板的概念;
模板能为我们提供简单的规则替换;做到简单的规则动态加载;
本例子的demo基于最新稳定版drools6.4
2.项目结构
3.pom依赖
org.drools
drools-core
6.4.0.Final
org.drools
drools-decisiontables
6.4.0.Final
4.代码
Message.java
package com.caicongyang.drools.templates;
import java.io.Serializable;
/**
* @author caicongyang1
* @version id: Message, v 0.1 16/9/29 下午3:06 caicongyang1 Exp $$
*/
public class Message implements Serializable {
private static final long serialVersionUID = -3168739008574463191L;
public static final int HELLO = 0;
public static final int GOODBYE = 1;
private String message;
private int status;
public String getMessage() {
return this.message;
}
public void setMessage(String message) {
this.message = message;
}
public int getStatus() {
return this.status;
}
public void setStatus(int status) {
this.status = status;
}
}
MyDataProvider.java
package com.caicongyang.drools.templates;
import java.util.Iterator;
import java.util.List;
import org.drools.template.DataProvider;
/**
* @author caicongyang1
* @version id: Message, v 0.1 16/9/29 下午3:06 caicongyang1 Exp $$
*/
public class MyDataProvider implements DataProvider {
private Iterator iterator;
public MyDataProvider(List rows) {
this.iterator = rows.iterator();
}
public boolean hasNext() {
return iterator.hasNext();
}
public String[] next() {
return iterator.next();
}
}
DataProviderCompilerTest.java
package com.caicongyang.drools.templates;
import java.util.ArrayList;
import org.drools.template.DataProviderCompiler;
import org.kie.api.KieServices;
import org.kie.api.builder.KieBuilder;
import org.kie.api.builder.KieFileSystem;
import org.kie.api.builder.Message.Level;
import org.kie.api.builder.model.KieBaseModel;
import org.kie.api.builder.model.KieModuleModel;
import org.kie.api.io.KieResources;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
/**
*
* Drools模板实例应用
*
* @author caicongyang1
* @version id: Message, v 0.1 16/9/29 下午3:06 caicongyang1 Exp $$
*/
public class DataProviderCompilerTest {
public static void main(String[] args) {
ArrayList rows = new ArrayList();
rows.add(new String[] { "1", "status == 0" });
MyDataProvider tdp = new MyDataProvider(rows);
DataProviderCompiler converter = new DataProviderCompiler();
String drl = converter.compile(tdp, "/rules/rule_template_1.drl");
System.out.println(drl);
KieServices kieServices = KieServices.Factory.get();
KieResources resources = kieServices.getResources();
KieModuleModel kieModuleModel = kieServices.newKieModuleModel();//1
KieBaseModel baseModel = kieModuleModel.newKieBaseModel("FileSystemKBase").addPackage("rules");//2
baseModel.newKieSessionModel("FileSystemKSession");//3
KieFileSystem fileSystem = kieServices.newKieFileSystem();
String xml = kieModuleModel.toXML();
System.out.println(xml);//4
fileSystem.writeKModuleXML(xml);//5
String path = DataProviderCompilerTest.class.getClass().getResource("/").getPath();
fileSystem.write("src/main/resources/rules/rule1.drl", drl);
KieBuilder kb = kieServices.newKieBuilder(fileSystem);
kb.buildAll();//7
if (kb.getResults().hasMessages(Level.ERROR)) {
throw new RuntimeException("Build Errors:\n" + kb.getResults().toString());
}
KieContainer kContainer = kieServices.newKieContainer(kieServices.getRepository().getDefaultReleaseId());
KieSession kSession = kContainer.newKieSession("FileSystemKSession");
Message message = new Message();
message.setMessage("Hello World");
message.setStatus(Message.HELLO);
kSession.insert(message);
kSession.fireAllRules();
kSession.dispose();
}
}
rule_template_1.dl
template header
RULE_ID
RULE_KEY1
package com.caicongyang.drools.templates;
import com.caicongyang.drools.templates.Message;
template "RULE"
rule "RULE_@{RULE_ID}"
when
m: Message(@{RULE_KEY1})
then
System.out.println(m.getMessage());
end
end template
应用层->
DAOProxyFactory factory = new DAOProxyFactory();
StudentService aopService = (StudentService) factory.createProxyInstance(new StudentServiceImpl());
int age = aopService.getAge("ymk");
Service层
public int getAge(String userId) throws Exception {
UserInfoMapper userMapper = batisSession.getMapper(UserInfoMapper.class);
int age = userMapper.selelctUser(userId);
return age;
}
Dao层->myBatis哪有Dao层
mybatis-conf.xml文件内容
我们在此使用了阿里的Druid连接池
我们可以直接在此文件中写上值而不是“替换符”,因为对于drools上传一个jar包是无需重启服务的,上传完毕该jar后,KIE WB内部即发生了更改。
Mybatis的具体mapper文件-这个太简单了吧,给一个例子吧:
把业务、因子做成全动态
现在我们来书写我们的FACT
public class UserInfoBean implements Serializable
private int age = 0;
private String applicant;
private String userId;
private boolean validFlag = false;
}
没什么好多说的,我们为这个Bean提供了一组field,同时我们会为每个私有成员生成一对set/get方法。
关键在于getAge()方法的覆盖:
public int getAge() throws Exception {
int age = 0;
try {
DAOProxyFactory factory = new DAOProxyFactory();
UserInfoService stdService = (UserInfoService) factory.createProxyInstance(new UserInfoServiceImpl());
age = stdService.getAge(userId);
System.out.println(age);
} catch (Exception e) {
System.err.println(e.getMessage());
throw new Exception("mybatis error: " + e.getMessage(), e);
}
return age;
}
我们提供一个Service->UserInfoService
UserInfoServiceImpl具体内容
package org.sky.drools.service;
import org.apache.ibatis.session.SqlSession;
import org.sky.drools.dao.mapper.ApplicantListMapper;
import org.sky.drools.dao.mapper.UserInfoMapper;
import org.sky.drools.sql.datasource.IsSession;
public class UserInfoServiceImpl implements UserInfoService {
@IsSession
private SqlSession batisSession = null;
public void setBatisSession(SqlSession batisSession) {
this.batisSession = batisSession;
}
public int getAge(String userId) throws Exception {
UserInfoMapper userMapper = batisSession.getMapper(UserInfoMapper.class);
int age = userMapper.selelctUser(userId);
return age;
}
public int existInList(String userId) throws Exception {
ApplicantListMapper listMapper = batisSession.getMapper(ApplicantListMapper.class);
int result = listMapper.selelctUserInApplicant(userId);
return result;
}
}
可以看到这边对batisSession不做任何的动作,只是像Spring的IOC中的一个“注入”一样来使用。
把因子打包上传至规则引擎
增加规则:
package org.sky.drools.dbrulz;
no-loop
declare User
age : int;
validFlag : boolean;
end
rule "init studentbean"
salience 1000
when
u : UserInfoBean()
then
User user=new User();
user.setAge(u.getAge());
System.out.println("valid applicant for["+u.getUserId()+"] and validFlag is["+u.isValidFlag()+"]");
insert(user);
end
rule "less than < 17"
when
u : User ( age <17);
facts : UserInfoBean()
then
facts.setApplicant("0");
end
rule "less than < 45"
when
u : User ( age <46 && age >=17);
facts : UserInfoBean()
then
facts.setApplicant("7000000");
end
rule "less than < 70"
when
u : User ( age <70 && age >=46);
facts : UserInfoBean()
then
facts.setApplicant("5000000");
end
现在因为有了KIE SERVER,于是我们直接在此规则上多加一步
在规则开始处修改如下:
declare User
age : int;
validFlag : boolean;
end
rule "init studentbean"
salience 1000
when
$u:UserInfoBean(userId!=null)
//$u:UserInfoBean();
then
System.out.println("valid applicant for["+$u.getUserId()+"] and validFlag is["+$u.isValidFlag()+"]");
User userVO=new User();
userVO.setAge( $u.getAge());
userVO.setValidFlag($u.isValidFlag());
insert(userVO);
End
以下这段加在原有规则最后:
rule "not a valid applicant"
when
user:User(!validFlag) && u:UserInfoBean(userId!=null)
//u : UserInfoBean( !validFlag );
then
u.setApplicant("0");
end
package org.sky.drools.dbrulz;
declare User
age : int;
validFlag : boolean;
end
rule "init studentbean"
salience 1000
when
$u:UserInfoBean(userId!=null)
then
System.out.println("valid applicant for["+$u.getUserId()+"] and validFlag is["+$u.isValidFlag()+"]");
User userVO=new User();
userVO.setAge( $u.getAge());
userVO.setValidFlag($u.isValidFlag());
insert(userVO);
end
rule "less than < 17"
when
user: User(age<17) && u:UserInfoBean(userId!=null)
then
u.setApplicant("0");
end
rule "less than < 45"
when
(user: User(age<46 && age>=17)) && u:UserInfoBean(userId!=null)
then
System.out.println("set applicant for "+u.getUserId()+" to 7000000");
u.setApplicant("7000000");
end
rule "less than < 70"
when
(user:User(age<70 && age>=46)) && u:UserInfoBean(userId!=null)
then
System.out.println("set applicant for "+u.getUserId()+" to 5000000");
u.setApplicant("5000000");
end
rule "not a valid applicant"
when
user:User(!validFlag) && u:UserInfoBean(userId!=null)
then
u.setApplicant("0");
end
规则解释:
其实这个规则很简单,关键之处在于我们更改了Fact中的getAge(),那么它会在每一个规则处进行一次数据库操作。
虽然,我们的数据库操作用的是myBatis,在取得SessionFactory和Session时都已经做成了POOL和SINGLETON且ThreadSafe了。
但是每个getAge()它还是会作一次“迷你AOP”和一次数据库的DAO操作,对不对?
那么我们来说,我们取得一个输入人的UserId,拿到了它的年龄即可以用规则来计算它的保额了,所以我们说:通过userId取getAge()仅应该做一次数据库操作,对不对?
因此我们这边使用了Drools的declare语法,预声明了一个Object,该Object会运行在一条:
rule “init studentbean”且salience 1000的规则中。
这条规则中的salience 1000代表(salience后的数字越高运行优先级最高-即默认“主方法”),在这条规则中我们作了一件 事:
通过用户转入的fact的userId然后调用getAge()进行一次数据库操作并赋给declare的User,以后就用这个User进行全局规则匹配即可。
在《Drools7.0.0.Final规则引擎教程》之Springboot集成中介绍了怎样将Drools与Springboot进行集成,本篇博客介绍一下集成之后,如何实现从数据库读取规则并重新加载规则的简单demo。因本章重点介绍的是Drools相关操作的API,所以将查询数据库部分的操作省略,直接使用数据库查询出的规则代码来进行规则的重新加载。另外,此示例采用访问一个http请求来进行重新加载,根据实际需要可考虑定时任务进行加载等扩展方式。最终的使用还是要结合具体业务场景来进行分析扩展。
上图是基于intellij maven的项目结构。其中涉及到springboot的Drools集成配置类,重新加载规则类。一些实体类和工具类。下面会抽出比较重要的类进行分析说明。
@Configuration
public class DroolsAutoConfiguration {
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() {
public ReleaseId getReleaseId() {
return kieRepository.getDefaultReleaseId();
}
});
KieBuilder kieBuilder = getKieServices().newKieBuilder(kieFileSystem());
kieBuilder.buildAll();
KieContainer kieContainer = getKieServices().newKieContainer(kieRepository.getDefaultReleaseId());
KieUtils.setKieContainer(kieContainer);
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 {
KieSession kieSession = kieContainer().newKieSession();
KieUtils.setKieSession(kieSession);
return kieSession;
}
@Bean
@ConditionalOnMissingBean(KModuleBeanFactoryPostProcessor.class)
public KModuleBeanFactoryPostProcessor kiePostProcessor() {
return new KModuleBeanFactoryPostProcessor();
}
}
此类主要用来初始化Drools的配置,其中需要注意的是对KieContainer和KieSession的初始化之后都将其设置到KieUtils类中。
KieUtils类中存储了对应的静态方法和静态属性,供其他使用的地方获取和更新。
public class KieUtils {
private static KieContainer kieContainer;
private static KieSession kieSession;
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;
}
}
提供了reload规则的方法,也是本篇博客的重点之一,其中从数据库读取的规则代码直接用字符串代替,读者可自行进行替换为数据库操作。
@Service
public class ReloadDroolsRules {
public void reload() throws UnsupportedEncodingException {
KieServices kieServices = KieServices.Factory.get();
KieFileSystem kfs = kieServices.newKieFileSystem();
kfs.write("src/main/resources/rules/temp.drl", loadRules());
KieBuilder kieBuilder = kieServices.newKieBuilder(kfs).buildAll();
Results results = kieBuilder.getResults();
if (results.hasMessages(Message.Level.ERROR)) {
System.out.println(results.getMessages());
throw new IllegalStateException("### errors ###");
}
KieUtils.setKieContainer(kieServices.newKieContainer(getKieServices().getRepository().getDefaultReleaseId()));
System.out.println("新规则重载成功");
}
private String loadRules() {
// 从数据库加载的规则
return "package plausibcheck.adress\n\n import com.neo.drools.model.Address;\n import com.neo.drools.model.fact.AddressCheckResult;\n\n rule \"Postcode 6 numbers\"\n\n when\n then\n System.out.println(\"规则2中打印日志:校验通过!\");\n end";
}
private KieServices getKieServices() {
return KieServices.Factory.get();
}
}
提供了验证入口和reload入口。
@RequestMapping("/test")
@Controller
public class TestController {
@Resource
private ReloadDroolsRules rules;
@ResponseBody
@RequestMapping("/address")
public void test(){
KieSession kieSession = KieUtils.getKieSession();
Address address = new Address();
address.setPostcode("994251");
AddressCheckResult result = new AddressCheckResult();
kieSession.insert(address);
kieSession.insert(result);
int ruleFiredCount = kieSession.fireAllRules();
System.out.println("触发了" + ruleFiredCount + "条规则");
if(result.isPostCodeResult()){
System.out.println("规则校验通过");
}
}
@ResponseBody
@RequestMapping("/reload")
public String reload() throws IOException {
rules.reload();
return "ok";
}
}
其他类和文件内容参考springboot集成部分或demo源代码。
操作步骤如下:启动项目访问http://localhost:8080/test/address 会首先触发默认加载的address.drl中的规则。当调用reload之后,再次调用次方法会发现触发的规则已经变成重新加载的规则了。
CSDN demo下载地址:http://download.csdn.net/detail/wo541075754/9918297
GitHub demo下载地址:https://github.com/secbr/drools/tree/master/springboot-drools-reload-rules
点击链接关注《Drools博客专栏》