趙才文
(亞信科技(成都)有限公司,江蘇 南京 210003)
隨著軟件市場(chǎng)的不斷開(kāi)拓,企業(yè)開(kāi)發(fā)的軟件產(chǎn)品會(huì)在越來(lái)越多的市場(chǎng)進(jìn)行部署。一般情況下,為了減少成本,企業(yè)會(huì)盡量重復(fù)使用現(xiàn)有的開(kāi)發(fā)人員和產(chǎn)品代碼庫(kù)。使用一套產(chǎn)品進(jìn)行部署,雖然能夠滿足大多數(shù)市場(chǎng)的需求,但是某些市場(chǎng)會(huì)有自己的特殊需求,需要軟件企業(yè)為其定制開(kāi)發(fā)某些功能。由于企業(yè)的人力資源有限,不可能針對(duì)每個(gè)市場(chǎng)進(jìn)行完全定制化的研發(fā),這就必然要求企業(yè)具備一種靈活的軟件擴(kuò)展開(kāi)發(fā)方法,在支持產(chǎn)品化研發(fā)的同時(shí),也能夠支持業(yè)務(wù)的靈活擴(kuò)展,以達(dá)到通過(guò)最少的資源支撐最多市場(chǎng)的目的。
通過(guò)研究,目前比較通用的軟件擴(kuò)展方式主要包含表1所列示的六種方式。
通過(guò)對(duì)上述軟件擴(kuò)展方法的分析,我們可以得到一個(gè)結(jié)論:最佳的軟件擴(kuò)展方式既有一定的靈活性,又比較容易開(kāi)發(fā)和部署,容易落地,這樣的擴(kuò)展方式才能有效地被推廣。即一個(gè)好的軟件擴(kuò)展架構(gòu)應(yīng)該具有如下特點(diǎn):
第一,核心模塊和擴(kuò)展點(diǎn)邊界清晰。即軟件中的可擴(kuò)展點(diǎn)和不可擴(kuò)展點(diǎn)的邊界應(yīng)該清晰,開(kāi)發(fā)人員和運(yùn)維人員容易識(shí)別。
第二,擴(kuò)展點(diǎn)要足夠細(xì)粒度。要能夠提供業(yè)務(wù)邏輯任意分支點(diǎn)的擴(kuò)展功能,也就是說(shuō)擴(kuò)展點(diǎn)能對(duì)應(yīng)到代碼行。
第三,支持多層級(jí)的擴(kuò)展方式。軟件的擴(kuò)展應(yīng)能夠遞進(jìn)地被擴(kuò)展,即能夠?qū)崿F(xiàn)擴(kuò)展的擴(kuò)展,以達(dá)到能夠基于現(xiàn)有擴(kuò)展進(jìn)行再次擴(kuò)展的效果。
表1 常見(jiàn)軟件擴(kuò)展方式及特點(diǎn)
第四,容易實(shí)現(xiàn),易于配置。能夠提供簡(jiǎn)單的擴(kuò)展技術(shù)方案,適合現(xiàn)在初級(jí)開(kāi)發(fā)工程師的開(kāi)發(fā)水平,且能夠簡(jiǎn)單配置。
“插件化軟件開(kāi)發(fā)方法”能夠較好地解決軟件擴(kuò)展的控制粒度,具備清晰的軟件擴(kuò)展邊界。這種技術(shù)的主要特點(diǎn)如下:
(1)定義嚴(yán)謹(jǐn),邊界清晰:基于擴(kuò)展點(diǎn)(Extension Point)和擴(kuò)展(Extension)的定義實(shí)現(xiàn)軟件功能的擴(kuò)展,嚴(yán)格區(qū)分軟件的核心模塊和擴(kuò)展之間的邊界。
(2)與業(yè)務(wù)邏輯結(jié)合緊密且能靈活調(diào)整:業(yè)務(wù)邏輯中任意位置可以定義擴(kuò)展點(diǎn)以實(shí)現(xiàn)擴(kuò)展,并且能夠按照需求的變更進(jìn)行調(diào)整,非常靈活。
(3)支持多層級(jí)的擴(kuò)展方式:可以在擴(kuò)展的基礎(chǔ)上增加擴(kuò)展點(diǎn),實(shí)現(xiàn)多層級(jí)的擴(kuò)展。
(4)擴(kuò)展的開(kāi)發(fā)和管理比較簡(jiǎn)單:通過(guò)一個(gè)簡(jiǎn)單的對(duì)接口的實(shí)現(xiàn),即可完成功能的擴(kuò)展,通過(guò)一個(gè)配置文件實(shí)現(xiàn)不同插件的切換,比較容易落地。
插件化開(kāi)發(fā)方法和傳統(tǒng)擴(kuò)展方式的不同,在于當(dāng)遇到市場(chǎng)需求變化的時(shí)候,采用基于插件的開(kāi)發(fā)方式來(lái)進(jìn)行擴(kuò)展功能開(kāi)發(fā)。
插件化開(kāi)發(fā)中包括插件、擴(kuò)展點(diǎn)和擴(kuò)展等要素。插件(Plug-in)是指擴(kuò)展點(diǎn)和擴(kuò)展的集合。擴(kuò)展點(diǎn)(Extension Point)代表軟件可被擴(kuò)展的功能點(diǎn)。采用接口方式定義,可以定義缺省實(shí)現(xiàn),一個(gè)擴(kuò)展點(diǎn)可以有多個(gè)擴(kuò)展實(shí)現(xiàn)。擴(kuò)展(Extension)代表擴(kuò)展點(diǎn)接口的實(shí)現(xiàn),基于擴(kuò)展點(diǎn)定義。一個(gè)插件可以包含N個(gè)擴(kuò)展點(diǎn)和擴(kuò)展,一個(gè)擴(kuò)展點(diǎn)可以有N個(gè)擴(kuò)展實(shí)現(xiàn)。這些要素之間的關(guān)系如圖1所示。
圖1 插件要素之間的關(guān)系
插件通過(guò)自定義的類加載方式以支持動(dòng)態(tài)的類擴(kuò)展能力,具體類調(diào)用序列圖如圖2所示。
圖2 類調(diào)用序列圖
具體調(diào)用過(guò)程如下:PluginUtils類被調(diào)用獲取某個(gè)擴(kuò)展點(diǎn)(Extension Point)對(duì)應(yīng)的擴(kuò)展(Extension)的方法,此類直接調(diào)用對(duì)應(yīng)的擴(kuò)展(Extension)的實(shí)例;擴(kuò)展(Extension)從PluginClassLoader類加載器加載對(duì)應(yīng)的類對(duì)象;插件類加載器(PluginClassLoader)獲取對(duì)象實(shí)例后,返回給擴(kuò)展(Extension);擴(kuò)展(Extension)返回給PluginUtils,PluginUtils返回給調(diào)用者,調(diào)用者相當(dāng)于調(diào)用了定制化的插件實(shí)現(xiàn)。需要注意的是,其中的PluginClassLoader是一個(gè)繼承自URLClass-Loader的自定義類加載器,將插件的依賴在運(yùn)行期動(dòng)態(tài)加入到系統(tǒng)類加載路徑中,這樣就可以通過(guò)loadClass方法獲取擴(kuò)展(Extension)中定義的擴(kuò)展對(duì)象實(shí)例。
要配置一個(gè)新的插件,需要增加如下配置:
(1)增加plugin.xml文件,定義插件的信息,包括擴(kuò)展點(diǎn)的定義和擴(kuò)展的定義,每個(gè)擴(kuò)展點(diǎn)還可以定義缺省實(shí)現(xiàn)類。
(2)增加plugin.properties文件,定義項(xiàng)目中使用的插件列表。
對(duì)已有的產(chǎn)品,如果要使用插件化開(kāi)發(fā)方法,在設(shè)計(jì)上要注意下面幾點(diǎn):
(1)對(duì)可擴(kuò)展的業(yè)務(wù)邏輯抽象出接口,并將原有實(shí)現(xiàn)邏輯作為該接口的缺省實(shí)現(xiàn)。
(2)調(diào)用PluginUtils工具類執(zhí)行基于插件的業(yè)務(wù)邏輯。
在某個(gè)產(chǎn)品的賬號(hào)管理模塊中,賬號(hào)添加流程如下:用戶登錄后,點(diǎn)擊賬號(hào)管理,添加賬號(hào)完成后,點(diǎn)擊提交,賬號(hào)信息被提交到后臺(tái),直接保存到數(shù)據(jù)庫(kù),新賬號(hào)自動(dòng)啟用。如果此產(chǎn)品被部署到某個(gè)新增的市場(chǎng),用戶提出了一個(gè)新需求,在添加賬號(hào)的時(shí)候不要自動(dòng)啟用,要先發(fā)短信通知管理員,管理員確認(rèn)同意后才可以啟用此新增帳號(hào),以防繞過(guò)管理員隨意添加賬號(hào)。這個(gè)新增的流程就需要基于已有產(chǎn)品代碼進(jìn)行擴(kuò)展。
現(xiàn)有的類和實(shí)現(xiàn)主要包括三個(gè)部分:(1)領(lǐng)域?qū)ο髮觗omain層的Account實(shí)體類:代表賬號(hào)實(shí)體對(duì)象,包含id,name,enabled等基本屬性。(2)數(shù)據(jù)訪問(wèn)層dao層的AccountDao對(duì)象:代表數(shù)據(jù)庫(kù)訪問(wèn)對(duì)象,提供賬號(hào)對(duì)象的增加、刪除、修改和查詢方法。(3)業(yè)務(wù)實(shí)現(xiàn)層service層的AccountService對(duì)象:代表業(yè)務(wù)邏輯對(duì)象,為前臺(tái)頁(yè)面提供業(yè)務(wù)處理方法,例如增加賬號(hào)的業(yè)務(wù)邏輯。service層調(diào)用dao層提供的方法來(lái)實(shí)現(xiàn)賬號(hào)管理的數(shù)據(jù)庫(kù)入庫(kù)功能。
在未引入插件化開(kāi)發(fā)方法之前,通常采用的軟件擴(kuò)展方法是基于類繼承的擴(kuò)展方式。用戶提出了賬號(hào)新增流程的擴(kuò)展需求后,我們需要對(duì)AccountService.java進(jìn)行改造。(1)新建一個(gè)包:service_a包,用于存放A市場(chǎng)的定制需求。(2)在service_a包中新建類AccountService,實(shí)現(xiàn)定制化的新增賬號(hào)業(yè)務(wù)邏輯:即新增賬號(hào)缺省為不啟用狀態(tài),先發(fā)消息通知管理員,等管理員確認(rèn)短信后,再改為啟用狀態(tài)。(3)新增針對(duì)A市場(chǎng)的前端賬號(hào)管理頁(yè)面,調(diào)用新增的service_a包中的定制業(yè)務(wù)邏輯代碼。(4)打包部署到現(xiàn)場(chǎng)后,用戶需求得到滿足。
通過(guò)上述演示,我們很容易想到如下問(wèn)題:
(1)傳統(tǒng)方法采用的是先復(fù)制,再修改的方法進(jìn)行擴(kuò)展,而且復(fù)制的級(jí)別是類級(jí)別,導(dǎo)致大量的重復(fù)代碼。
(2)目前是A省提出定制開(kāi)發(fā)需求,如果B/C/D…..省也提出定制需求,那么需要復(fù)制的代碼就會(huì)越來(lái)越多,代碼重復(fù)的情況越來(lái)越嚴(yán)重,導(dǎo)致軟件越來(lái)越臃腫,難以維護(hù)。
(3)如果某省覺(jué)得A省提出的這個(gè)需求很好,也想在本省采用,那么要把這次代碼都拷貝一份到B/C/D……省的工程中,工作量很大。
(4)因?yàn)閺?fù)制了大量的代碼,如果在復(fù)制的代碼中發(fā)現(xiàn)了一個(gè)軟件缺陷(Bug),那么開(kāi)發(fā)人員必須在所有復(fù)制的代碼中修改一遍,修改量很大,然后測(cè)試人員也必須在每個(gè)版本中測(cè)試一遍,測(cè)試工作量也很大。
隨著市場(chǎng)的增加,這種基于類繼承復(fù)制的擴(kuò)展方式必然要求越來(lái)越多的人員投入,并且不能快速應(yīng)對(duì)需求變更,隨之而來(lái)的是用戶的滿意度降低甚至是市場(chǎng)份額的丟失。
使用插件化開(kāi)發(fā)方式,首先應(yīng)該將A省定制的賬號(hào)管理需求變?yōu)橐粋€(gè)可以重用的插件,并且這個(gè)插件是獨(dú)立的,可以按需配置,這樣其他省可以使用“通用版本+功能插件”的方式達(dá)到擁有此功能的目的。
為了使用插件開(kāi)發(fā),必須使用接口(即擴(kuò)展點(diǎn))的方式進(jìn)行方法調(diào)用,所以我們將AccountService這個(gè)要擴(kuò)展的類進(jìn)行接口化改造,增加一個(gè)IAccountService接口,同時(shí)增加一個(gè)插件工具類PluginUtils,提供獲取插件實(shí)例的方法。表2對(duì)每個(gè)新增、修改的類和接口進(jìn)行了基本介紹。
上述用戶管理的插件開(kāi)發(fā)場(chǎng)景調(diào)用過(guò)程如下:
(1)客戶端調(diào)用AccountService對(duì)象的addAccount方法;
(2)AccountService調(diào)用了PluginUtils的getExtensionInstance方法,獲取擴(kuò)展點(diǎn)IAccountService當(dāng)前配置的插件實(shí)例;
(3)AccountService獲取IAccountService擴(kuò)展點(diǎn)實(shí)例后,調(diào)用其addAccount方法實(shí)現(xiàn)具體業(yè)務(wù)邏輯。
通過(guò)上述分析,可以看到,調(diào)用者并不知道調(diào)用了哪個(gè)具體插件,只是通過(guò)PluginUtils工具類得到插件實(shí)例后調(diào)用其擴(kuò)展點(diǎn)接口方法。這樣就實(shí)現(xiàn)了調(diào)用者和實(shí)現(xiàn)者的解耦,便于切換不同的實(shí)現(xiàn)。
根據(jù)上面的描述,我們可以看出插件化軟件擴(kuò)展方法具備下面的一些優(yōu)點(diǎn):
表2 插件開(kāi)發(fā)場(chǎng)景關(guān)鍵類和接口
(1)插件的開(kāi)發(fā)可以和原工程的開(kāi)發(fā)分離,這樣就可以區(qū)分產(chǎn)品核心開(kāi)發(fā)團(tuán)隊(duì)和擴(kuò)展開(kāi)發(fā)團(tuán)隊(duì);也就是說(shuō),插件化整體工程的結(jié)構(gòu)分為一個(gè)主工程和多個(gè)插件工程,在主工程中定義了多個(gè)擴(kuò)展點(diǎn)和缺省實(shí)現(xiàn);而在其他獨(dú)立的插件工程中,則定義了一些擴(kuò)展點(diǎn)對(duì)應(yīng)的擴(kuò)展;這樣可以針對(duì)不同的開(kāi)發(fā)人員分配不同的代碼讀寫(xiě)權(quán)限,區(qū)分產(chǎn)品研發(fā)組和插件研發(fā)組,將定制開(kāi)發(fā)和主版本開(kāi)發(fā)嚴(yán)格區(qū)分開(kāi)來(lái),有利于進(jìn)行細(xì)粒度的產(chǎn)品研發(fā)管控,提升產(chǎn)品開(kāi)發(fā)效率和質(zhì)量。
(2)概念清晰,具備嚴(yán)格的插件、擴(kuò)展點(diǎn)、擴(kuò)展定義方式;在傳統(tǒng)擴(kuò)展方式中,因?yàn)闆](méi)有獨(dú)立的擴(kuò)展概念,開(kāi)發(fā)人員需要熟悉各種不同的擴(kuò)展方式,比如基于配置文件擴(kuò)展,需要開(kāi)發(fā)人員和維護(hù)人員熟悉配置文件的屬性定義方式,而這些定義沒(méi)有一定的規(guī)則,容易混亂。插件方式則不同,擴(kuò)展點(diǎn)和擴(kuò)展的定義都是在XML中采用固定格式定義,相關(guān)人員熟悉非??焖?。
(3)擴(kuò)展點(diǎn)隨意選擇,選擇后進(jìn)行重構(gòu)即可使用插件方式;傳統(tǒng)的開(kāi)發(fā)方式只能基于已有的API或者配置規(guī)則進(jìn)行擴(kuò)展,基于插件的擴(kuò)展方式則可以在任意代碼行定義擴(kuò)展點(diǎn),也就是說(shuō),插件擴(kuò)展點(diǎn)的控制力度可以在代碼的任意行,比傳統(tǒng)方式遞進(jìn)了一個(gè)層級(jí)。
(4)調(diào)試和部署簡(jiǎn)單方便;傳統(tǒng)方式下,擴(kuò)展代碼和產(chǎn)品代碼揉和在一起,難以區(qū)分和管理。插件擴(kuò)展方式則采用獨(dú)立的打包方式,也就是每個(gè)插件進(jìn)行單獨(dú)打包,然后部署,非常容易區(qū)分和部署。
當(dāng)然,插件開(kāi)發(fā)也不是非常完美的方案,目前還有下列的不足:首先是目前還不支持前臺(tái)頁(yè)面插件,當(dāng)前的插件開(kāi)發(fā)主要是針對(duì)后臺(tái)代碼進(jìn)行設(shè)計(jì)的,還不支持前端頁(yè)面的插件,這個(gè)問(wèn)題可以用打包的方式暫時(shí)解決,即將需要擴(kuò)展的頁(yè)面文件、圖片、腳本等資源在打包的時(shí)候拷貝到主工程。其次目前插件開(kāi)發(fā)還不支持熱部署,每次更新插件配置后需要重啟整個(gè)應(yīng)用才可以加載最新的插件,這個(gè)問(wèn)題可以通過(guò)增加額外的自動(dòng)定期加載程序?qū)崿F(xiàn)插件的自動(dòng)化熱加載。
綜上所述,插件化開(kāi)發(fā)是一種簡(jiǎn)便易行的軟件擴(kuò)展方法,通過(guò)在軟件中定義擴(kuò)展點(diǎn)和擴(kuò)展,進(jìn)而組織為插件進(jìn)行部署,可以實(shí)現(xiàn)軟件核心模塊和軟件定制模塊的分離。當(dāng)然,這種軟件擴(kuò)展方式也有各種不足,需要我們?cè)趯?shí)踐中不斷進(jìn)行完善。不同的軟件產(chǎn)品對(duì)擴(kuò)展方式的要求也是不同的,不能一切照搬,需要按照實(shí)際情況進(jìn)行選擇。
[1]劉婧.電信行業(yè)自助終端支撐系統(tǒng)架構(gòu)研究[D].西安:西安電子科技大學(xué),2009.
[2]王麗華,王治民,任雁銘,等.插件化I E C 61850通信模塊設(shè)計(jì)與實(shí)現(xiàn)[J].電力系統(tǒng)自動(dòng)化,,2012,36(5):82-85.