springboot中使用规则引擎Drools
文章目錄
- 規則引擎的場景
- 規則引擎介紹
- 使用規則引擎的優勢
- 規則引擎應用場景
- rools介紹
- Drools入門案例
- 規則引擎構成
- Drools基礎語法
- springboot使用規則引擎
規則引擎的場景
問題引出
現有一個在線申請信用卡的業務場景,用戶需要錄入個人信息,如下圖所示:
通過上圖可以看到,用戶錄入的個人信息包括姓名、性別、年齡、學歷、電話、所在公司、職位、月收入、是否有房、是否有車、是否有信用卡等。錄入完成后點擊申請按鈕提交即可。
用戶提交申請后,需要在系統的服務端進行用戶信息合法性檢查(是否有資格申請信用卡),只有通過合法性檢查的用戶才可以成功申請到信用卡(注意:不同用戶有可能申請到的信用卡額度不同)。
檢查用戶信息合法性的規則如下:
規則編號 名稱 描述
1 檢查學歷與薪水1 如果申請人既沒房也沒車,同時學歷為大專以下,并且月薪少于5000,那么不通過
2 檢查學歷與薪水2 如果申請人既沒房也沒車,同時學歷為大?;虮究?#xff0c;并且月薪少于3000,那么不通過
3 檢查學歷與薪水3 如果申請人既沒房也沒車,同時學歷為本科以上,并且月薪少于2000,同時之前沒有信用卡的,那么不通過
4 檢查申請人已有的信用卡數量 如果申請人現有的信用卡數量大于10,那么不通過
用戶信息合法性檢查通過后,還需要根據如下信用卡發放規則確定用戶所辦信用卡的額度:
規則編號 名稱 描述
1 規則1 如果申請人有房有車,或者月收入在20000以上,那么發放的信用卡額度為15000
2 規則2 如果申請人沒房沒車,但月收入在10000~20000之間,那么發放的信用卡額度為6000
3 規則3 如果申請人沒房沒車,月收入在10000以下,那么發放的信用卡額度為3000
4 規則4 如果申請人有房沒車或者沒房但有車,月收入在10000以下,那么發放的信用卡額度為5000
5 規則5 如果申請人有房沒車或者是沒房但有車,月收入在10000~20000之間,那么發放的信用卡額度為8000
思考:如何實現上面的業務邏輯呢?
我們最容易想到的就是使用分支判斷(if else)來實現,例如通過如下代碼來檢查用戶信息合法性:
//檢查用戶信息合法性,返回true表示檢查通過,返回false表示檢查不通過 public boolean checkUser(User user){//如果申請人既沒房也沒車,同時學歷為大專以下,并且月薪少于5000,那么不通過if(user.getHouse() == null && user.getcar() == null && user.getEducation().equals("大專以下") && user.getSalary < 5000){return false;}//如果申請人既沒房也沒車,同時學歷為大專或本科,并且月薪少于3000,那么不通過else if(user.getHouse() == null && user.getcar() == null && user.getEducation().equals("大?;虮究?#34;) && user.getSalary < 3000){return false;}//如果申請人既沒房也沒車,同時學歷為本科以上,并且月薪少于2000,同時之前沒有信用卡的,那么不通過else if(user.getHouse() == null && user.getcar() == null && user.getEducation().equals("本科以上") && user.getSalary < 2000 && user.getHasCreditCard() == false){return false;}//如果申請人現有的信用卡數量大于10,那么不通過else if(user.getCreditCardCount() > 10){return false;}return true; }通過上面的偽代碼我們可以看到,我們的業務規則是通過Java代碼的方式實現的。這種實現方式存在如下問題:
1、硬編碼實現業務規則難以維護
2、硬編碼實現業務規則難以應對變化
3、業務規則發生變化需要修改代碼,重啟服務后才能生效
那么面對上面的業務場景,還有什么好的實現方式嗎?
答案是規則引擎。
規則引擎介紹
什么是規則引擎
規則引擎,全稱為業務規則管理系統,英文名為BRMS(即Business Rule Management System)。規則引擎的主要思想是將應用程序中的業務決策部分分離出來,并使用預定義的語義模塊編寫業務決策(業務規則),由用戶或開發者在需要時進行配置、管理。
需要注意的是規則引擎并不是一個具體的技術框架,而是指的一類系統,即業務規則管理系統。目前市面上具體的規則引擎產品有:drools、VisualRules、iLog等。
規則引擎實現了將業務決策從應用程序代碼中分離出來,接收數據輸入,解釋業務規則,并根據業務規則做出業務決策。規則引擎其實就是一個輸入輸出平臺。
系統中引入規則引擎后,業務規則不再以程序代碼的形式駐留在系統中,取而代之的是處理規則的規則引擎,業務規則存儲在規則庫中,完全獨立于程序。業務人員可以像管理數據一樣對業務規則進行管理,比如查詢、添加、更新、統計、提交業務規則等。業務規則被加載到規則引擎中供應用系統調用。
使用規則引擎的優勢
使用規則引擎的優勢如下:
1、業務規則與系統代碼分離,實現業務規則的集中管理
2、在不重啟服務的情況下可隨時對業務規則進行擴展和維護
3、可以動態修改業務規則,從而快速響應需求變更
4、規則引擎是相對獨立的,只關心業務規則,使得業務分析人員也可以參與編輯、維護系統的業務規則
5、減少了硬編碼業務規則的成本和風險
6、使用規則引擎提供的規則編輯工具,使復雜的業務規則實現變得的簡單
規則引擎應用場景
對于一些存在比較復雜的業務規則并且業務規則會頻繁變動的系統比較適合使用規則引擎,如下:
1、風險控制系統----風險貸款、風險評估
2、反欺詐項目----銀行貸款、征信驗證
3、決策平臺系統----財務計算
4、促銷平臺系統----滿減、打折、加價購
rools介紹
drools是一款由JBoss組織提供的基于Java語言開發的開源規則引擎,可以將復雜且多變的業務規則從硬編碼中解放出來,以規則腳本的形式存放在文件或特定的存儲介質中(例如存放在數據庫中),使得業務規則的變更不需要修改項目代碼、重啟服務器就可以在線上環境立即生效。
drools官網地址:https://drools.org/
drools源碼下載地址:https://github.com/kiegroup/drools
在項目中使用drools時,即可以單獨使用也可以整合spring使用。如果單獨使用只需要導入如下maven坐標即可:
<dependency><groupId>org.drools</groupId><artifactId>drools-compiler</artifactId><version>7.6.0.Final</version> </dependency>步驟如下
Drools入門案例
通過一個Drools入門案例來讓大家初步了解Drools的使用方式、對Drools有一個整體概念。
業務場景說明
業務場景:消費者在圖書商城購買圖書,下單后需要在支付頁面顯示訂單優惠后的價格。具體優惠規則如下:
規則編號 規則名稱 描述
1 規則一 所購圖書總價在100元以下的沒有優惠
2 規則二 所購圖書總價在100到200元的優惠20元
3 規則三 所購圖書總價在200到300元的優惠50元
4 規則四 所購圖書總價在300元以上的優惠100元
第一步:創建maven工程drools_quickstart并導入drools相關maven坐標
<dependency><groupId>org.drools</groupId><artifactId>drools-compiler</artifactId><version>7.10.0.Final</version> </dependency> <dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version> </dependency> 第二步:根據drools要求創建resources/META-INF/kmodule.xml配置文件<?xml version="1.0" encoding="UTF-8" ?> <kmodule xmlns="http://www.drools.org/xsd/kmodule"><!--name:指定kbase的名稱,可以任意,但是需要唯一packages:指定規則文件的目錄,需要根據實際情況填寫,否則無法加載到規則文件default:指定當前kbase是否為默認--><kbase name="myKbase1" packages="rules" default="true"><!--name:指定ksession名稱,可以任意,但是需要唯一default:指定當前session是否為默認--><ksession name="ksession-rule" default="true"/></kbase> </kmodule>注意:上面配置文件的名稱和位置都是固定寫法,不能更改
第三步:創建實體類Order
package com.itheima.drools.entity; ? /*** 訂單*/ public class Order {private Double originalPrice;//訂單原始價格,即優惠前價格private Double realPrice;//訂單真實價格,即優惠后價格 ?public String toString() {return "Order{" +"originalPrice=" + originalPrice +", realPrice=" + realPrice +'}';} ?public Double getOriginalPrice() {return originalPrice;} ?public void setOriginalPrice(Double originalPrice) {this.originalPrice = originalPrice;} ?public Double getRealPrice() {return realPrice;} ?public void setRealPrice(Double realPrice) {this.realPrice = realPrice;} }第四步:創建規則文件resources/rules/bookDiscount.drl
//圖書優惠規則 package book.discount import com.itheima.drools.entity.Order ? //規則一:所購圖書總價在100元以下的沒有優惠 rule "book_discount_1"when$order:Order(originalPrice < 100)then$order.setRealPrice($order.getOriginalPrice());System.out.println("成功匹配到規則一:所購圖書總價在100元以下的沒有優惠"); end ? //規則二:所購圖書總價在100到200元的優惠20元 rule "book_discount_2"when$order:Order(originalPrice < 200 && originalPrice >= 100)then$order.setRealPrice($order.getOriginalPrice() - 20);System.out.println("成功匹配到規則二:所購圖書總價在100到200元的優惠20元"); end ? //規則三:所購圖書總價在200到300元的優惠50元 rule "book_discount_3"when$order:Order(originalPrice <= 300 && originalPrice >= 200)then$order.setRealPrice($order.getOriginalPrice() - 50);System.out.println("成功匹配到規則三:所購圖書總價在200到300元的優惠50元"); end ? //規則四:所購圖書總價在300元以上的優惠100元rule "book_discount_4"when$order:Order(originalPrice >= 300)then$order.setRealPrice($order.getOriginalPrice() - 100);System.out.println("成功匹配到規則四:所購圖書總價在300元以上的優惠100元"); end第五步:編寫單元測試
@Test public void test1(){KieServices kieServices = KieServices.Factory.get();KieContainer kieClasspathContainer = kieServices.getKieClasspathContainer();//會話對象,用于和規則引擎交互KieSession kieSession = kieClasspathContainer.newKieSession(); ?//構造訂單對象,設置原始價格,由規則引擎根據優惠規則計算優惠后的價格Order order = new Order();order.setOriginalPrice(210D); ?//將數據提供給規則引擎,規則引擎會根據提供的數據進行規則匹配kieSession.insert(order); ?//激活規則引擎,如果規則匹配成功則執行規則kieSession.fireAllRules();//關閉會話kieSession.dispose(); ?System.out.println("優惠前原始價格:" + order.getOriginalPrice() +",優惠后價格:" + order.getRealPrice()); }通過上面的入門案例我們可以發現,使用drools規則引擎主要工作就是編寫規則文件,在規則文件中定義跟業務相關的業務規則,例如本案例定義的就是圖書優惠規則。規則定義好后就需要調用drools提供的API將數據提供給規則引擎進行規則模式匹配,規則引擎會執行匹配成功的規則并將計算的結果返回給我們。
可能大家會有疑問,就是我們雖然沒有在代碼中編寫規則的判斷邏輯,但是我們還是在規則文件中編寫了業務規則,這跟在代碼中編寫規則有什么本質的區別呢?
我們前面其實已經提到,使用規則引擎時業務規則可以做到動態管理。業務人員可以像管理數據一樣對業務規則進行管理,比如查詢、添加、更新、統計、提交業務規則等。這樣就可以做到在不重啟服務的情況下調整業務規則。
規則引擎構成
drools規則引擎由以下三部分構成:
Working Memory(工作內存)
Rule Base(規則庫)
Inference Engine(推理引擎)
其中Inference Engine(推理引擎)又包括:
Pattern Matcher(匹配器)
Agenda(議程)
Execution Engine(執行引擎)
Working Memory:工作內存,drools規則引擎會從Working Memory中獲取數據并和規則文件中定義的規則進行模式匹配,所以我們開發的應用程序只需要將我們的數據插入到Working Memory中即可,例如本案例中我們調用kieSession.insert(order)就是將order對象插入到了工作內存中。
Fact:事實,是指在drools 規則應用當中,將一個普通的JavaBean插入到Working Memory后的對象就是Fact對象,例如本案例中的Order對象就屬于Fact對象。Fact對象是我們的應用和規則引擎進行數據交互的橋梁或通道。
Rule Base:規則庫,我們在規則文件中定義的規則都會被加載到規則庫中。
Pattern Matcher:匹配器,將Rule Base中的所有規則與Working Memory中的Fact對象進行模式匹配,匹配成功的規則將被激活并放入Agenda中。
Agenda:議程,用于存放通過匹配器進行模式匹配后被激活的規則。
Execution Engine:執行引擎,執行Agenda中被激活的規則。
規則引擎執行過程
Drools基礎語法
規則文件構成
在使用Drools時非常重要的一個工作就是編寫規則文件,通常規則文件的后綴為.drl。
drl是Drools Rule Language的縮寫。在規則文件中編寫具體的規則內容。
一套完整的規則文件內容構成如下:
關鍵字 描述
package 包名,只限于邏輯上的管理,同一個包名下的查詢或者函數可以直接調用
import 用于導入類或者靜態方法
global 全局變量
function 自定義函數
query 查詢
rule end 規則體
Drools支持的規則文件,除了drl形式,還有Excel文件類型的。
規則體語法結構
規則體是規則文件內容中的重要組成部分,是進行業務規則判斷、處理業務結果的部分。
規則體語法結構如下:
rule “ruleName”
attributes
when
LHS
then
RHS
end
rule:關鍵字,表示規則開始,參數為規則的唯一名稱。
attributes:規則屬性,是rule與when之間的參數,為可選項。
when:關鍵字,后面跟規則的條件部分。
LHS(Left Hand Side):是規則的條件部分的通用名稱。它由零個或多個條件元素組成。如果LHS為空,則它將被視為始終為true的條件元素。
then:關鍵字,后面跟規則的結果部分。
RHS(Right Hand Side):是規則的后果或行動部分的通用名稱。
end:關鍵字,表示一個規則結束。
在drl形式的規則文件中使用注釋和Java類中使用注釋一致,分為單行注釋和多行注釋。
單行注釋用"//“進行標記,多行注釋以”/“開始,以”/"結束。
Pattern模式匹配
前面我們已經知道了Drools中的匹配器可以將Rule Base中的所有規則與Working Memory中的Fact對象進行模式匹配,那么我們就需要在規則體的LHS部分定義規則并進行模式匹配。LHS部分由一個或者多個條件組成,條件又稱為pattern。
pattern的語法結構為:綁定變量名:Object(Field約束)
其中綁定變量名可以省略,通常綁定變量名的命名一般建議以$開始。如果定義了綁定變量名,就可以在規則體的RHS部分使用此綁定變量名來操作相應的Fact對象。Field約束部分是需要返回true或者false的0個或多個表達式。
例如我們的入門案例中:
//規則二:所購圖書總價在100到200元的優惠20元
rule “book_discount_2”
when
//Order為類型約束,originalPrice為屬性約束
$order:Order(originalPrice < 200 && originalPrice >= 100)
then
order.setRealPrice(order.setRealPrice(order.setRealPrice(order.getOriginalPrice() - 20);
System.out.println(“成功匹配到規則二:所購圖書總價在100到200元的優惠20元”);
end
通過上面的例子我們可以知道,匹配的條件為:
1、工作內存中必須存在Order這種類型的Fact對象-----類型約束
2、Fact對象的originalPrice屬性值必須小于200------屬性約束
3、Fact對象的originalPrice屬性值必須大于等于100------屬性約束
以上條件必須同時滿足當前規則才有可能被激活。
LHS部分還可以定義多個pattern,多個pattern之間可以使用and或者or進行連接,也可以不寫,默認連接為and。
//規則二 rule "book_discount_2"when$order:Order($op:originalPrice < 200 && originalPrice >= 100) and$customer:Customer(age > 20 && gender=='male')thenSystem.out.println("$op=" + $op);$order.setRealPrice($order.getOriginalPrice() - 20);System.out.println("成功匹配到規則二"); endspringboot使用規則引擎
依賴:
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><dependency><groupId>commons-lang</groupId><artifactId>commons-lang</artifactId><version>2.6</version></dependency><!--drools規則引擎--><dependency><groupId>org.drools</groupId><artifactId>drools-core</artifactId><version>7.10.0.Final</version></dependency><dependency><groupId>org.drools</groupId><artifactId>drools-compiler</artifactId><version>7.10.0.Final</version></dependency><dependency><groupId>org.kie</groupId><artifactId>kie-api</artifactId><version>7.10.0.Final</version></dependency><dependency><groupId>org.kie</groupId><artifactId>kie-spring</artifactId><exclusions><exclusion><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId></exclusion><exclusion><groupId>org.springframework</groupId><artifactId>spring-beans</artifactId></exclusion><exclusion><groupId>org.springframework</groupId><artifactId>spring-core</artifactId></exclusion><exclusion><groupId>org.springframework</groupId><artifactId>spring-context</artifactId></exclusion></exclusions><version>7.10.0.Final</version></dependency></dependencies>第二步:創建/resources/application.yml文件
server:port: 8080 spring:application:name: drools_springboot第三步:創建規則文件/resources/rules/helloworld.drl
package helloworld rule "rule_helloworld"wheneval(true)thenSystem.out.println("規則:rule_helloworld觸發..."); end第四步:編寫配置類DroolsConfig
package com.itheima.drools.config; import org.kie.api.KieBase; import org.kie.api.KieServices; import org.kie.api.builder.KieBuilder; import org.kie.api.builder.KieFileSystem; import org.kie.api.builder.KieRepository; import org.kie.api.runtime.KieContainer; import org.kie.api.runtime.KieSession; import org.kie.internal.io.ResourceFactory; import org.kie.spring.KModuleBeanFactoryPostProcessor; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.core.io.Resource; import java.io.IOException; /*** 規則引擎配置類*/ @Configuration public class DroolsConfig {//指定規則文件存放的目錄private static final String RULES_PATH = "rules/";private final KieServices kieServices = KieServices.Factory.get();@Bean@ConditionalOnMissingBeanpublic KieFileSystem kieFileSystem() throws IOException {KieFileSystem kieFileSystem = kieServices.newKieFileSystem();ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();Resource[] files = resourcePatternResolver.getResources("classpath*:" + RULES_PATH + "*.*");String path = null;for (Resource file : files) {path = RULES_PATH + file.getFilename();kieFileSystem.write(ResourceFactory.newClassPathResource(path, "UTF-8"));}return kieFileSystem;}@Bean@ConditionalOnMissingBeanpublic KieContainer kieContainer() throws IOException {KieRepository kieRepository = kieServices.getRepository();kieRepository.addKieModule(kieRepository::getDefaultReleaseId);KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem());kieBuilder.buildAll();return kieServices.newKieContainer(kieRepository.getDefaultReleaseId());}@Bean@ConditionalOnMissingBeanpublic KieBase kieBase() throws IOException {return kieContainer().getKieBase();}@Bean@ConditionalOnMissingBeanpublic KModuleBeanFactoryPostProcessor kiePostProcessor() {return new KModuleBeanFactoryPostProcessor();} }第五步:創建RuleService類
package com.itheima.drools.service; ? import org.kie.api.KieBase; import org.kie.api.runtime.KieSession; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; ? @Service public class RuleService {@Autowiredprivate KieBase kieBase;public void rule(){KieSession kieSession = kieBase.newKieSession();kieSession.fireAllRules();kieSession.dispose();} }第六步:創建HelloController類
package com.itheima.drools.controller; ? import com.itheima.drools.service.RuleService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; ? @RestController @RequestMapping("/hello") public class HelloController {@Autowiredprivate RuleService ruleService;@RequestMapping("/rule")public String rule(){ruleService.rule();return "OK";} }第七步:創建啟動類DroolsApplication
package com.itheima.drools; ? import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; ? @SpringBootApplication public class DroolsApplication {public static void main(String[] args) {SpringApplication.run(DroolsApplication.class,args);} } 與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的springboot中使用规则引擎Drools的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 用百度开放地图api在代码中获得两地距离
- 下一篇: springboot整合kafka和ne