最近项目需要增加风控系统,在经过一番调研以后决定使用Drools规则引擎。因为项目是基于SpringCloud的架构,所以此次学习使用了SpringBoot2.0版本结合Drools7.14.0.Final版本。
<dependency> <groupId>org.drools</groupId> <artifactId>drools-core</artifactId> <version>7.14.0.Final</version></dependency><dependency> <groupId>org.kie</groupId> <artifactId>kie-spring</artifactId> <version>7.14.0.Final</version></dependency>
@Configurationpublic 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(); return getKieServices().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(); return kieSession; }
@Bean @ConditionalOnMissingBean(KModuleBeanFactoryPostProcessor.class) public KModuleBeanFactoryPostProcessor kiePostProcessor() { return new KModuleBeanFactoryPostProcessor(); } public KieServices getKieServices() { System.setProperty("drools.dateformat","yyyy-MM-dd"); return KieServices.Factory.get(); }}
在这个时候我们的基本环境已经搭建好了,接下来我们一起来学习Drools吧
程序员的世界里,我们学习任何一门语言都是以HelloWord开启的,本次学习也不例外。
Drools的规则文件是以*.drl结尾的文件,我们来看一个最简单的规则文件中都是包含什么。
通常来说,我们会把规则文件放在resources资源文件夹下,这里呢我们在resources文件夹下新建一个rules文件夹,然后再新建一个HelloWord.drl文件
package rules;import cn.org.zhixiang.entity.User;import java.lang.String;import java.util.List;
rule "hello,word" when eval(true) then System.err.println("hello,word!");end
现在我们的规则文件写好以后就可以在Java中来进行调用了。
@RunWith(SpringRunner.class)@SpringBootTestpublic class DroolsApplicationHelloWordTests {
@Autowired KieSession kieSession;
}
上方注入的kieSession对象就是以后与Drools打交道最常用的一个对象了,通过它可以直接操作在配置类kieFileSystem方法中加载的所有的规则文件
@Testpublic void testHelloWord() { kieSession.fireAllRules();}
kieSession.fireAllRules方法是执行所有的规则,在运行了这个测试方法之后我们应该就可以看到控制台打印的一句hello,word!了
public class User { private String name; private int age;
public User(String name, int age) { this.name = name; this.age = age; } //省略getter,setter}
rule "user" when $u:User(name=="张三",age==18) then $u.setName("李四"); System.err.println("hello,word:"+$u.getName());end
$u:User(name==“张三”,age==18)的意思就是当存在一个user对象,并且它的name属性等于张三age等于18时就把这个对象赋值给$u。
在下方的then模块,如果上方的条件成立时就把$u的name属性更新一下,然后打印。
@Testpublic void testUser() { User user=new User("张三",18); kieSession.insert(user); kieSession.fireAllRules(); System.err.println("规则执行完毕后张三变为了:"+user.getName());}
我们可以使用kieSession.insert方法向规则文件中传参,然后在调用方法后你会发现在规则文件中更改的值在Java代码中也被更改了。
可能你会发现上方代码执行的时候连那句helloword也打印了,为什么呢,这是因为HelloWord那条规则没有验证条件再加上kieSession.fireAllRules()本来就是执行所有被加载的规则的。那么避免这种情况的办法就是执定本次执行的规则
@Test public void testOneRule() { User user=new User("张三",18); kieSession.insert(user); kieSession.fireAllRules(new RuleNameEndsWithAgendaFilter("user")); }
上方的user就是指定的本次执行的规则名称了。
上方我们通过RuleNameEndsWithAgendaFilter对象成功指定了需要执行的规则文件,其实通过查看此对象的源码我们发现这个对象是AgendaFilter的一个实现类,决定执不执行一个规则的条件是accept方法返回的boolean值决定的。 所以说如果我们希望可以一次批量匹配多个规则的话可以通过继承AgendaFilter重写accept方法哦
Drools中存在的三种连接符,上方的代码中我们已经使用过一个了,那就$u:User(name==“张三”,age==18)中的逗号,这里的逗号其实就是and的意思。另外的两个运算符就是&&和||,相信它们两个的意思不用我来介绍了吧。
不过有一点需要注意的是&&和|| 和逗号,不能同时出现。要不你选择用&&和||要不就只用逗号, 。
它们是配合eval使用的,比如上方我们使用的eval(true)就是直接返回的true。当我们比较常量时可以使用eval(u.age>b.age)
contains用于判断对象的某个字段是否包含另外一个对象
rule "contains" when $s:String() $u:User(name contains $s) then System.err.println("用户张三存在");end
@Test public void testContains() { String name="张三"; User user=new User("张三",18); kieSession.insert(name); kieSession.insert(user); kieSession.fireAllRules(new RuleNameEndsWithAgendaFilter("contains")); }
not contains顾明思议就是不包含
memberOf用于判断对象的某个字段是否存在一个集合中
rule "memberOf" when $list:List() $u:User(name memberOf $list) then System.err.println("用户李四存在");end
@Test public void testMemberOf() { List list=new ArrayList(); list.add("张三"); list.add("李四"); User user=new User("李四",18); kieSession.insert(list); kieSession.insert(user); kieSession.fireAllRules(new RuleNameEndsWithAgendaFilter("memberOf")); }
not memberOf顾明思议就是不存在
matches就是用于匹配正则表达式的了
rule "matches" when $u:User(name matches "张.*") then System.err.println("用户张xx存在");end
@Test public void testMatches() { User user=new User("张三",18); kieSession.insert(user); kieSession.fireAllRules(new RuleNameEndsWithAgendaFilter("matches")); }
not matches不用我说了吧