一、背景
當前自己開發的 Android 項目是一個智能推薦系統,用到 drools 規則引擎,於我來說是一個新知識點,以前都沒聽說過的東東,不過用起來也不算太難,經過一段時間學習,基本掌握。關於 drools 規則引擎的內容,后面再整理JBoss 官網上面有詳細的文檔,網上資料也比較多。學習 drools 規則引擎的傳送門:
Drools 官網首頁: https://www.drools.org/
Drools 官方文檔: https://docs.jboss.org/drools/release/7.12.0.Final/drools-docs/html_single/index.html
這里主要是因為自己使用 Android Studio 在編寫 drools 文件時,沒有了智能提示,IDE 不對語法進行檢查了,出現了兩次多寫 )
的錯誤。這就跟用記事本寫東西程序一樣,慌的不行,所以自己寫一個簡單的語法檢查的腳本。對 drools 文件進行一個初步的判斷。
二、Drools 規則引擎簡單介紹
Drools 規則引擎的使用場景
對於某些企業級應用,經常會有大量的、錯綜復雜的業務規則配置,用程序語言來描述,就形如:if-else
或者 switch-case
等。像這種不同的條件,做不同的處理就是一種規則。用通俗易懂的結構來表示:當 XXX 的時候,做 XXX 的事。理論上這樣的問題都可以用規則引擎來解決。但是我們也不是說為了使用規則引擎去使用它,我們視具體業務邏輯而定,一般來說,條件(規則)比較復雜,情況種類比較多,條件可能會經常變化等,這時候,選擇規則引擎去解決問題是比較明智的。
譬如隨着企業管理者的決策變化,某些業務規則也會隨之發生更改。對於我們開發人員來說,我們不得不一直處理軟件中的各種復雜問題,需要將所有數據進行關聯,還要盡可能快地一次性處理更多的數據,甚至還需要以快速的方式更新相關機制。
Drools 規則引擎的優點
Drools 規則引擎實現了將業務決策從應用程序中分離出來。
優點:
1、簡化系統架構,優化應用
2、方便系統的整合,它們是獨立的,允許不同背景的人進行合作
3、減少編寫“硬代碼”業務規則的成本和風險,每個規則控制所需的最小信息量
4、它們很容易更新,提高系統的可維護性,減小維護成本
Drools的基本工作工程
我們需要傳遞進去數據,用於規則的檢查,調用外部接口,同時還可能獲取規則執行完畢之后得到的結果
Fact對象:
指傳遞給drools腳本的對象,是一個普通的javabean,原來javaBean對象的引用,可以對該對象進行讀寫操作,並調用該對象的方法。當一個java bean插入到working Memory(內存存儲)中,規則使用的是原有對象的引用,規則通過對fact對象的讀寫,實現對應用數據的讀寫,對其中的屬性,需要提供get和set方法,規則中可以動態的前往working memory中插入刪除新的fact對象
Drl文件內容:
例子:
hello.drl文件如下:
package rules.testword
rule "test001"
when
//這里如果為空,則表示eval(true)
then
System.out.println("hello word");
end
Drools的基礎語法:
包路徑,引用,規則體 (其中包路徑和規則體是必須的)
package:
包路徑,該路徑是邏輯路徑(可以隨便寫,但是不能不寫,最好和文件目錄同名,以(.)的方式隔開),規則文件中永遠是第一行。
rule:
規則體,以rule開頭,以end結尾,每個文件可以包含多個rule ,規則體分為3個部分:LHS,RHS,屬性 三大部分。
LHS:
(Left Hand Side),條件部分,在一個規則當中“when”和“then”中間的部分就是LHS部分,在LHS當中,可以包含0~N個條件,如果 LHS 為空的話,那么引擎會自動添加一個eval(true)的條件,由於該條件總是返回true,所以LHS為空的規則總是返回true。
RHS:
(Right Hand Side),在一個規則中“then”后面的部分就是RHS,只有在LHS的所有條件都滿足的情況下,RHS部分才會執行。RHS部分是規則真正做事情的部分,滿足條件觸發動作的操作部分,在RHS可以使用LHS部分當中的定義的綁定變量名,設置的全局變量、或者是直接編寫的java代碼,可以使用import的類。不建議有條件判斷。
三、drools 文件的形式
Drools是一款基於Java的開源規則引擎,所以 Drools 文件語法完全兼容 java 語法,以 .drl
為后綴。大致形式如下:
package droolsexample
// list any import classes here.
import com.sample.ItemCity;
import java.math.BigDecimal;
// declare any global variables here
dialect "java"
/*
規則1
*/
rule "Pune Medicine Item"
when
item : ItemCity (purchaseCity == ItemCity.City.PUNE,
typeofItem == ItemCity.Type.MEDICINES)
then
BigDecimal tax = new BigDecimal(0.0);
item.setLocalTax(tax.multiply(item.getSellPrice()));
end
/**
規則2
*/
rule "Pune Groceries Item"
when
item : ItemCity(purchaseCity == ItemCity.City.PUNE,
typeofItem == ItemCity.Type.GROCERIES)
then
BigDecimal tax = new BigDecimal(2.0);
item.setLocalTax(tax.multiply(item.getSellPrice()));
end
和 java 文件類似,先聲明包名,然后導入相關的類。一個規則由:
rule "xxx"
when
xxx
then
xxx
end
這樣的形式構成。其中單行注釋以 //
開頭,多行注釋形如 /* */
。
四、Drools 文件語法初步檢查
目的
檢測 .drl 文件中 ( )
、 { }
、 " "
是否成對出現。如果出現錯誤,指出錯誤行數。
思路
利用 棧 的數據結構來進行檢測。
首先我們需要給出一個空棧,然后把待檢測的代碼中的字符一一入棧,在入棧的過程中,如果字符是一個開放符號a(也就是我們的左括號),則把它壓入棧中,如果是一個封閉符號(右括號)b,則此時先判斷一下棧是否為空,如果為空的話,則報錯(也就是待檢測的代碼中的括號不一一對應),如果棧不為空,則比較b和棧頂元素a,如果該封閉字符b和a字符匹配(也就是他們的括號能夠匹配),則彈出棧頂元素,如果不匹配,則報錯。當吧所有待檢測的代碼全部迭代完后,此時如果棧不為空,則報錯。
上面的思路中,我們還要將注釋中的符號排除在外。
步驟
1、讀取 drl 文件內容為字符串。
2、通過正則匹配,將 ://
替換為其它不受影響的字符或者字符串 ,避免誤判斷。
3、通過正則匹配,將 //
單行注釋替換為空格。正則表達式為 //.*
4、通過正則匹配,將 /*
和 */
替換為不受影響的字符,如 #
。(純粹是為了計算出錯行數)
5、借助棧的數據結構,進行判斷。
python 腳本實現
# coding=utf-8
import re
def remove_annotation(file_path):
with open(file_path, "r", encoding="UTF-8") as f:
text = f.read()
re_uri = "://"
text1 = re.sub(re_uri, "_____", text)
re_single = "//.*"
text2 = re.sub(re_single, " ", text1)
re_multi1 = "/\*"
text3 = re.sub(re_multi1, "#", text2)
re_multi2 = "\*/"
result = re.sub(re_multi2, "#", text3)
return result
def check_syntax(text):
try:
clist = []
row_num = 1
for c in text:
if c == '\n':
row_num = row_num + 1
if not clist or (clist[-2] != '\"' and clist[-2] != '#'):
if c == '\"':
clist.append(c)
clist.append(row_num)
if c == '#':
clist.append(c)
clist.append(row_num)
if c == '(':
clist.append(c)
clist.append(row_num)
if c == '{':
clist.append(c)
clist.append(row_num)
if c == ')':
if clist[-2] == '(':
clist.pop()
clist.pop()
else:
print("多余的 ) , 可能錯誤行數為 " + str(row_num))
return -1
if c == '}':
if clist[-2] == '{':
clist.pop()
clist.pop()
else:
print("多余的 } , 可能錯誤行數為 " + str(row_num))
return -1
else:
if c == '\"':
if clist[-2] == '\"':
clist.pop()
clist.pop()
if c == '#':
if clist[-2] == '#':
clist.pop()
clist.pop()
if clist:
print("存在多余的 ( 或者 { 或者 \" 可能的錯誤行數為:" + str(clist[-1]))
return -2
else:
print("語法檢查初步正確!")
return 0
except IndexError:
print("存在多余的 ) 或者 } 或者 \" 可能的錯誤行數為: " + str(row_num))
return -1
except Exception as e:
print(e)
print("其它錯誤!")
drl_path = input("請輸入 drools 文件路徑,可拖拽:")
content = remove_annotation(drl_path)
check_syntax(content)
演示實例:
drools 文件就取用上面的例子,命名為 test.drl
,如圖:
上面的腳本保存為 check_drl.py
文件。
結果如下:
下面將故意將 drools 文件第 40 行增加一個 )
,如下圖:
再次測試如下圖:
結語
本文簡單介紹了一下 Drools 規則引擎的使用場景以及 drool 文件的簡單語法檢測,主要是利用了棧數據結構后進先出的思想。本來是計划用 java 來寫這個工具的,后來想了一下,還是覺得 python 比較實在,有很多優勢,列舉一二:
python 的列表 list 直接可以代替 java 的 Stack<E>
;
python 結合正則表達式,處理字符串更方便;
python 獲取 list 倒數第二個元素,可以直接使用 list[-2]
,java 可能需要 stack.get(stack.size()-2)
;
...