URule Pro整個(gè)產(chǎn)品由兩部分構(gòu)成:一個(gè)是設(shè)計(jì)器部分;另一個(gè)是規(guī)則執(zhí)行引擎部分。設(shè)計(jì)器部分主要是由庫文件設(shè)計(jì)器以及具體的規(guī)則文件設(shè)計(jì)器兩部分構(gòu)成。
庫文件設(shè)計(jì)器包括變量庫設(shè)計(jì)器、參數(shù)庫設(shè)計(jì)器、常量庫設(shè)計(jì)器以及動(dòng)作庫設(shè)計(jì)器四個(gè)部分,通過這些庫文件設(shè)計(jì)器,可以將業(yè)務(wù)系統(tǒng)中使用的實(shí)體對(duì)象、枚舉值、常量以及需要在規(guī)則中調(diào)用的Java方法映射到引擎中備用。
規(guī)則文件設(shè)計(jì)器主要包括規(guī)則集、決策表、交叉決策表(決策矩陣)、決策樹、評(píng)分卡、復(fù)雜評(píng)分卡、規(guī)則流等八種類型的業(yè)務(wù)規(guī)則設(shè)計(jì)工具,這些設(shè)計(jì)器全部是以可視化圖形方式提供,使用者只需要通過鼠標(biāo)點(diǎn)擊或拖拽的方式即可完成業(yè)務(wù)規(guī)則的定義。通過在這些規(guī)則設(shè)計(jì)器中引入各種類型的已定義好的庫文件,就可以和業(yè)務(wù)系統(tǒng)結(jié)合起來,從而設(shè)計(jì)出符合需求的業(yè)務(wù)規(guī)則。
這里我們首先來了解一下URule Pro當(dāng)中提供的庫文件,學(xué)習(xí)其配置及使用方法。
在業(yè)務(wù)系統(tǒng)開發(fā)過程中,會(huì)用到大量包含Getter和Setter方法的簡(jiǎn)單的Java對(duì)象,它們被稱之為POJO(Plain Ordinary Java Object),或BOM(Business Object Model)對(duì)象,這些對(duì)象在開發(fā)中作為數(shù)據(jù)的載體,負(fù)責(zé)數(shù)據(jù)的傳遞。在URule Pro當(dāng)中,變量庫就是用來映射這些POJO對(duì)象,從而使得我們可以在具體的規(guī)則文件中使用它們,從而完成規(guī)則與業(yè)務(wù)數(shù)據(jù)的交互。
打開URule Pro的操作控制臺(tái),創(chuàng)建一個(gè)項(xiàng)目,在項(xiàng)目的“庫”的節(jié)點(diǎn)上點(diǎn)擊右鍵,在彈出的菜單中選擇“添加變量庫”就可以創(chuàng)建變量庫文件,如下圖所示:
果你創(chuàng)建的項(xiàng)目中資源節(jié)點(diǎn)下沒有“庫”節(jié)點(diǎn),那是因?yàn)槟阈薷牧隧?xiàng)目知識(shí)庫內(nèi)容的展示方式,點(diǎn)擊左上角,在彈出的菜單中選擇“分類展示”,這樣就可以看到“庫”節(jié)點(diǎn)。
在URule Pro當(dāng)中,通過這個(gè)功能可以修改項(xiàng)目知識(shí)庫內(nèi)容的展示方式,選擇“分類展示”,就可以看到上圖所示效果,將項(xiàng)目中的庫文件、決策集文件、決策表文件、評(píng)分卡文件、決策流文件分類存放展示;如果選擇“集中展示”,那么就不會(huì)對(duì)這些文件進(jìn)行分類,統(tǒng)一放在資源節(jié)點(diǎn)下。
需要注意的是,在“分類展示”模式下,如果創(chuàng)建有目錄,那么這個(gè)目錄會(huì)出現(xiàn)在所有的分類下,同樣如果在某個(gè)分類下刪除某個(gè)目錄,那么這個(gè)目錄也將會(huì)從所有的分類中刪除,創(chuàng)建目錄也是一樣。在“集中展示”模式下,因?yàn)椴贿M(jìn)行分類,所以目錄就不存在這種現(xiàn)象,這點(diǎn)需要注意。
在添加文件或目錄時(shí),目錄或文件名只能使用英文或中文,不支持其它非法字符。
創(chuàng)建好變量庫文件后,可以看到系統(tǒng)會(huì)用變量庫編輯器自動(dòng)打開這個(gè)文件。在這個(gè)編輯器中,首先需要添加變量的分類,然后再添加具體的變量字段。對(duì)應(yīng)到Java實(shí)體對(duì)象,就是要添加對(duì)應(yīng)的實(shí)體對(duì)象信息,再添加這個(gè)實(shí)體對(duì)象所擁有的屬性信息。如下圖所示:
添加一個(gè)分類,輸入名稱,這個(gè)名稱是對(duì)當(dāng)前分類的描述,會(huì)在規(guī)則中直接引用顯示,所以一般我們會(huì)使用中文描述來作為名稱,類路徑,就是這個(gè)分類對(duì)應(yīng)的實(shí)體類的完整路徑,比如上圖中的“com.bstek.entity.Customer”。
變量的類路徑是規(guī)則引擎在執(zhí)行過程中查找對(duì)應(yīng)對(duì)象的唯一標(biāo)識(shí),所以在定義時(shí)一定要讓其與實(shí)際業(yè)務(wù)中對(duì)應(yīng)的POJO對(duì)象的完整類路徑一致,否則在運(yùn)行時(shí)將會(huì)出現(xiàn)找不到類的情況,或者使得規(guī)則在計(jì)算時(shí)不會(huì)出現(xiàn)我們期望的結(jié)果。
這時(shí),變量的分類就定義好了,如果當(dāng)前定義的類路徑對(duì)應(yīng)的類在當(dāng)前應(yīng)用中存在的話,那么我們可以點(diǎn)擊“操作列“中的第一個(gè)按鈕,這樣系統(tǒng)就會(huì)通過Java的反射功能生成當(dāng)前類對(duì)應(yīng)的所有字段信息。上圖中com.bstek.entity.Customer類源碼如下所示:
package com.bstek.entity;
import java.util.Date;
import com.bstek.urule.model.Label;
/**
* @author Jacky.gao
* @since 2016年9月29日
*/
public class Customer {
@Label("名稱")
private String name;
@Label("年齡")
private int age;
@Label("出生日期")
private Date birthday;
@Label("等級(jí)")
private int level;
@Label("手機(jī)號(hào)")
private String mobile;
@Label("性別")
private boolean gender;
@Label("是否有車")
private boolean car;
@Label("婚否")
private boolean married;
@Label("是否有房")
private boolean house;
//省略上述所有屬性對(duì)應(yīng)的getter與setter方法......
}
在這個(gè)類當(dāng)中,可以看到每個(gè)屬性都有一個(gè)名為L(zhǎng)abel的annotation,它是一個(gè)由URule Pro提供的用來描述字段屬性的annotation,這里描述的值,在生成變量時(shí)會(huì)自動(dòng)寫到變量的“標(biāo)題”當(dāng)中,這里的標(biāo)題將會(huì)在規(guī)則中直接引用,讓標(biāo)題有意義,對(duì)于規(guī)則的清晰表達(dá)很有價(jià)值。
如果這個(gè)類在當(dāng)前所在的項(xiàng)目當(dāng)中,所以可以直接通過反射生成所有的屬性,生成后的效果如下:
可以看到上圖中Label這個(gè)annotation對(duì)應(yīng)的描述寫入到變量的“標(biāo)題”當(dāng)中。
在URule的服務(wù)器客戶端模式下,我們的規(guī)則都是在服務(wù)器上定義的,這就有可能定義變量的時(shí)候變量分類對(duì)應(yīng)的實(shí)體類在服務(wù)器上不存在,而只在客戶端上存在,這種情況下就不能通過反射來生成對(duì)應(yīng)的字段,這時(shí)我們可以在有這個(gè)實(shí)體類的客戶端應(yīng)用中通過URule Pro中提供的com.bstek.urule.ClassUtils來生成目標(biāo)實(shí)體類的xml描述文件,然后在到服務(wù)器上,點(diǎn)擊變量分類“操作列”上中間那個(gè)上傳圖標(biāo)將這個(gè)xml描述文件上傳,同樣可以生成對(duì)應(yīng)的字段信息。使用ClassUtils類生成描述文件的代碼如下所示:
public static void main(String[] args) {
File file=new File("d:/customer.xml");
ClassUtils.classToXml(Customer.class, file);
}
運(yùn)行這個(gè)main方法,就會(huì)在D盤下生成一個(gè)customer.xml的實(shí)體類描述文件,再上傳這個(gè)文件即可。
在定義變量庫文件時(shí),對(duì)應(yīng)的實(shí)體類不一定真實(shí)存在。在仿真測(cè)試中,在運(yùn)行規(guī)則時(shí),如果發(fā)現(xiàn)對(duì)應(yīng)的實(shí)體類不在當(dāng)前JVM的classpath中時(shí),引擎會(huì)使用一個(gè)名為GeneralEntity的類來代替這目標(biāo)實(shí)現(xiàn)類運(yùn)行。所以在URule Pro的客戶端服務(wù)器模式下,服務(wù)器上定義變量庫時(shí),對(duì)應(yīng)的實(shí)體類一般都不在服務(wù)器上,而位于調(diào)用規(guī)則運(yùn)行的客戶端上,但對(duì)于服務(wù)器上規(guī)則定義與測(cè)試是沒有影響的。
這到里,變量庫文件就定義好了,可以根據(jù)需要在一個(gè)文件中添加多個(gè)變量分類,相應(yīng)對(duì)應(yīng)到多個(gè)POJO類。
變量定義好了之后,會(huì)被其它類型的規(guī)則文件引用,如果后期變量需要需要修改,在設(shè)計(jì)界面中提供了變更名的重構(gòu)功能。在上面的操作中我們可以看到,無論是變量的分類名稱,還是具體的變量名在其操作列上都有一個(gè)圖標(biāo),點(diǎn)擊該圖標(biāo)就可以對(duì)當(dāng)前行的變量名進(jìn)行修改,這樣就可以同步修改所有引用當(dāng)前變量的規(guī)則文件,從而完成了變量名的重構(gòu)。
在后面介紹的常量、參數(shù)、動(dòng)作庫文件中,對(duì)于它們的名稱的修改,同樣提供重構(gòu)功能的支持,這里不再贅述。
在業(yè)務(wù)系統(tǒng)開發(fā)過程中,常常會(huì)用到一個(gè)枚舉數(shù)據(jù),比如用戶的性別、學(xué)歷等,在URule Pro當(dāng)中,通過定義常量庫文件,可以將系統(tǒng)中使用的這些枚舉數(shù)據(jù)映射到規(guī)則中使用,這樣就可以避免規(guī)則定義過程中枚舉數(shù)據(jù)手工輸入存在錯(cuò)誤的可能性。
選中我們的項(xiàng)目,在“庫”節(jié)點(diǎn)上右鍵,創(chuàng)建一個(gè)常量庫文件,如下圖所示:
與變量庫文件類似,常量也是由分類和具體的常量值構(gòu)成,比如性別有男女之分,那么這里的“性別”就屬性分類,“男”、“女”就屬性具體的常量值。在常量的分類中,“名稱”一般定義具體的分類名,“標(biāo)題”是一段描述(比如“性別”是標(biāo)題,“gender”是名稱),同樣這個(gè)標(biāo)題也會(huì)出現(xiàn)在規(guī)則引用當(dāng)中;加好分類后就可以添加這個(gè)分類下具體的常量值,常量值也有名稱和標(biāo)題之分,名稱是具體的常量值,標(biāo)題則是描述,比如“男”是標(biāo)題,"true"是名稱,同樣“女”是標(biāo)題,“false”是名稱,如下圖所示:
同樣,在一個(gè)變量庫文件中可以根據(jù)需要添加多個(gè)變量分類。
常量定義時(shí),其名稱值可使用Spring中加載的properties文件值,具體使用方法是將要引用的properties的key值用${...}包裹,這樣在具體規(guī)則運(yùn)行時(shí)會(huì)動(dòng)態(tài)查找這個(gè)包裹的屬性值作為具體的常量,如:${app.title}表示取spring中properties文件的名為app.title的屬性值。利用這一功能,可實(shí)現(xiàn)測(cè)試環(huán)境與生產(chǎn)環(huán)境的動(dòng)態(tài)切換。
從2.1.9版本開始,常量庫文件在定義時(shí)支持從一個(gè)具體的Java枚舉類中直接導(dǎo)入,方法是點(diǎn)擊圖標(biāo),在彈出的窗口中輸入完整的枚舉類路徑即可實(shí)現(xiàn)將枚舉類里的枚舉值導(dǎo)入到當(dāng)前常量分類之下。
在通過枚舉類導(dǎo)入具體常量時(shí),如果枚舉類中未定義getLabel方法,那么導(dǎo)入的枚舉信息則只采用其name屬性,如果定義了getLabel方法,那么導(dǎo)入后的枚舉信息則包含name和label,包含getLabel方法的枚舉類示例如下所示:
public enum TestEnum {
aaa("張三"),bbb("李四");
private String label;
private TestEnum(String label) {
this.label=label;
}
public String getLabel() {
return label;
}
}
在規(guī)則的條件判斷與計(jì)算過程當(dāng)中,難免會(huì)用到一些臨時(shí)的變量來存儲(chǔ)值,這些臨時(shí)變量數(shù)量和類型都可能是不固定的,對(duì)于這種類型的臨時(shí)變量,URule Pro以參數(shù)的形式提供,通過參數(shù)庫就可以定義這些在規(guī)則中要使用到的臨時(shí)變量。
在“庫”節(jié)點(diǎn)上右鍵創(chuàng)建一個(gè)參數(shù)庫文件,從參數(shù)庫文件編輯器來看,參數(shù)因?yàn)闆]有了分類,配置要為簡(jiǎn)單許多,如下圖所示:
參數(shù)庫在運(yùn)行時(shí)實(shí)際上是存儲(chǔ)在HashMap當(dāng)中,這里的“名稱”的值將作為Map的key,“標(biāo)題”則用在規(guī)則中顯示使用,定義參數(shù)庫時(shí)要保證“名稱”屬性的唯一性,因?yàn)樗荕ap的key值;同時(shí)如果一個(gè)規(guī)則文件里引入多個(gè)參數(shù)庫文件,那么每個(gè)參數(shù)庫文件里定義的參數(shù)的名稱值也要唯一,否則就會(huì)存在相互覆蓋的情況。
在URule Pro當(dāng)中,對(duì)于參數(shù)庫中定義的值,規(guī)則運(yùn)行時(shí),如果外部沒有對(duì)這些參數(shù)進(jìn)行初始化,那么引擎會(huì)自動(dòng)為部分沒有初始化的參數(shù)進(jìn)行初始化。下表中介紹了URule Pro中會(huì)自動(dòng)初始化的數(shù)據(jù)類型自動(dòng)初始化后的值。
數(shù)據(jù)類型 | 初始化值 |
---|---|
Integer | 0 |
Double | 0 |
Float | 0 |
Boolean | false |
List | new ArrayList() |
Set | new HashSet() |
Map | new HashMap() |
動(dòng)作庫文件的作用是對(duì)配置在spring中的bean方法進(jìn)行映射,使得我們可以直接在規(guī)則當(dāng)中調(diào)用這些方法。同樣在項(xiàng)目的“庫”節(jié)點(diǎn)下創(chuàng)建一個(gè)動(dòng)作庫文件,可以看到動(dòng)作庫文件內(nèi)容有三列,分別是動(dòng)作名稱,bean的id定義列,方法名定義列,以及方法對(duì)應(yīng)的參數(shù)定義列,如下圖所示:
為了演示這里定義方法的具體操作,下面是一個(gè)名為MethodTest的類源碼:
package rete.test;
import java.text.SimpleDateFormat;
import java.util.Date;
import com.bstek.urule.action.ActionId;
import com.bstek.urule.model.ExposeAction;
/**
* @author Jacky.gao
* @since 2015年1月14日
*/
public class MethodTest {
@ActionId("helloKey")
public String hello(String username){
System.out.println("hello "+username);
return "hello"+username;
}
@ExposeAction("方法1")
public boolean evalTest(String username){
if(username==null){
return false;
}else if(username.equals("張三")){
return true;
}
return false;
}
@ExposeAction("測(cè)試Int")
public int testInt(int a,int b){
return a+b;
}
public int testInteger(Integer a,int b){
return a+b*10;
}
@ExposeAction("打印內(nèi)容")
public void printContent(String username,Date birthday){
SimpleDateFormat sd=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
if(birthday!=null){
System.out.println(username+"今年已經(jīng)"+sd.format(birthday)+"歲了!");
}else{
System.out.println("Hello "+username+"");
}
}
@ExposeAction("打印Member")
public void printUser(Member m){
System.out.println("Hello "+m.getUsername()+", has house:"+m.isHouse());
}
}
在這個(gè)MethodTest類中,我們對(duì)需要在動(dòng)作庫中引用的方法上都添加了一個(gè)名為ExposeAction的annotation,這就表示這個(gè)方法可以暴露給規(guī)則引用,反之如果不加這個(gè)annotation,那么這個(gè)方法就不能在規(guī)則中引用。同樣我們也看到對(duì)于需要在動(dòng)作庫中引用的方法是不需要實(shí)現(xiàn)任何接口的,方法簽名也是任意的,只需要在方法上添加ExposeAction這個(gè)annotation即可,接下來,我們需要將這個(gè)類配置到spring中,讓其成為一個(gè)標(biāo)準(zhǔn)的bean,spring中的配置如下:
<bean id="methodTest" class="rete.test.MethodTest"></bean>
定義動(dòng)作庫的Bean時(shí),一定不要忘記給Bean定義一個(gè)Id,這樣才能保證規(guī)則在任何地方運(yùn)行都不會(huì)出錯(cuò),這點(diǎn)很關(guān)鍵。
回到動(dòng)作庫文件編輯器,點(diǎn)擊“添加Bean”按鈕,添加一條Bean定義信息,將"Bean Id"屬性值改為我們這里的“methodTest”,點(diǎn)擊當(dāng)前行“操作列”中第一個(gè)圖標(biāo)選擇這個(gè)bean中的方法,以實(shí)現(xiàn)bean方法的映射,如下圖所示:
所有添加了ExposeAction的annotation的方法都出同在這個(gè)列表中,我們可以根據(jù)選擇添加即可,可以看到添加的方法會(huì)自動(dòng)將這個(gè)方法所需要的參數(shù)加載進(jìn)去,添加成功后,為了在規(guī)則中更好的可讀性,我們可以修改“動(dòng)作名稱”、“方法名稱”以及“參數(shù)名稱”。
可以看到,所有的庫文件除了可以保存外,還提供了一個(gè)名為“保存為版本”的按鈕,通過這個(gè)按鈕,可以將當(dāng)前文件內(nèi)容保存為一個(gè)新的版本,這樣在規(guī)則中就可以引用特定版本,特定版本的庫文件不會(huì)被修改。實(shí)際上在URule Pro中所有的文件都可以保存為新版本。
“查看引用”按鈕,就是用來查看當(dāng)前庫文件被哪些規(guī)則文件使用了,會(huì)有個(gè)列表顯示出來,方便我們后續(xù)維護(hù)。
在動(dòng)作庫定義的時(shí)候需要注意,如果我們規(guī)則運(yùn)行方式采用的是客戶端服務(wù)器模式(參見第16小節(jié)內(nèi)容介紹),那么必須要保證調(diào)用知識(shí)包的客戶端Spring環(huán)境里有這個(gè)Bean,且Bean的Id要與動(dòng)作庫定義時(shí)的Id保證一至,否則調(diào)用會(huì)出現(xiàn)錯(cuò)誤。
變量庫、參數(shù)庫、動(dòng)作庫、常量庫這些庫文件定義好后,就可以在各種類型的規(guī)則文件中導(dǎo)入并使用它們,一旦某個(gè)庫文件在規(guī)則中被使用,我們就不能再隨便修改這些已定義好的庫文件的名稱、值或數(shù)據(jù)類型,如果因?yàn)闃I(yè)務(wù)調(diào)整需要必須要進(jìn)行修改,那么可以通過這些變量、常量、參數(shù)、動(dòng)作定義的操作列上的重構(gòu)圖標(biāo)來對(duì)它們進(jìn)行修改,這樣可以保證引用文件同步更新;如果不采用重構(gòu)功能而直接修改的話,那么引用的其它類型的規(guī)則文件就會(huì)出現(xiàn)打開報(bào)錯(cuò)的情況。
更多建議: