查爾斯·狄更斯在《雙城記》中寫(xiě)道:“這是一個(gè)最好的時(shí)代,也是一個(gè)最壞的時(shí)代?!币苿?dòng)互聯(lián)網(wǎng)的快速發(fā)展,出現(xiàn)了許多新機(jī)遇,很多創(chuàng)業(yè)者伺機(jī)而動(dòng);隨著行業(yè)競(jìng)爭(zhēng)加劇,互聯(lián)網(wǎng)紅利逐漸消失,很多創(chuàng)業(yè)公司九死一生。筆者在初創(chuàng)公司摸爬滾打數(shù)年,接觸了各式各樣的Java微服務(wù)架構(gòu),從中獲得了一些優(yōu)秀的理念,但也發(fā)現(xiàn)了一些不合理的現(xiàn)象。現(xiàn)在,筆者總結(jié)了一些創(chuàng)業(yè)公司存在的Java服務(wù)端亂象,并嘗試性地給出了一些不成熟的建議。 1.使用Controller基類(lèi)和Service基類(lèi)1.1.現(xiàn)象描述1.1.1.Controller基類(lèi)常見(jiàn)的Controller基類(lèi)如下: 常見(jiàn)的Controller基類(lèi)主要包含注入服務(wù)、靜態(tài)常量和靜態(tài)函數(shù)等,便于所有的Controller繼承它,并在函數(shù)中可以直接使用這些資源。 1.1.2.Service基類(lèi)常見(jiàn)的Service基類(lèi)如下: 常見(jiàn)的Service基類(lèi)主要包括注入DAO、注入服務(wù)、注入?yún)?shù)、靜態(tài)常量、服務(wù)函數(shù)、靜態(tài)函數(shù)等,便于所有的Service繼承它,并在函數(shù)中可以直接使用這些資源。 1.2.論證基類(lèi)必要性首先,了解一下里氏替換原則:
其次,了解一下基類(lèi)的優(yōu)點(diǎn):
所以,我們可以得出以下結(jié)論:
綜上所述,Controller基類(lèi)和Service基類(lèi)只是一個(gè)雜湊類(lèi),并不是一個(gè)真正意義上的基類(lèi),需要進(jìn)行拆分。 1.3.拆分基類(lèi)的方法由于Service基類(lèi)比Controller基類(lèi)更典型,本文以Service基類(lèi)舉例說(shuō)明如何來(lái)拆分“基類(lèi)”。 1.3.1.把注入實(shí)例放入實(shí)現(xiàn)類(lèi)根據(jù)“使用即引入、無(wú)用則刪除”原則,在需要使用的實(shí)現(xiàn)類(lèi)中注入需要使用的DAO、服務(wù)和參數(shù)。 1.3.2.把靜態(tài)常量放入常量類(lèi)對(duì)于靜態(tài)常量,可以把它們封裝到對(duì)應(yīng)的常量類(lèi)中,在需要時(shí)直接使用即可。 1.3.3.把服務(wù)函數(shù)放入服務(wù)類(lèi)對(duì)于服務(wù)函數(shù),可以把它們封裝到對(duì)應(yīng)的服務(wù)類(lèi)中。在別的服務(wù)類(lèi)使用時(shí),可以注入該服務(wù)類(lèi)實(shí)例,然后通過(guò)實(shí)例調(diào)用服務(wù)函數(shù)。 1.3.4.把靜態(tài)函數(shù)放入工具類(lèi)對(duì)于靜態(tài)函數(shù),可以把它們封裝到對(duì)應(yīng)的工具類(lèi)中,在需要時(shí)直接使用即可。 2.把業(yè)務(wù)代碼寫(xiě)在Controller中2.1.現(xiàn)象描述我們會(huì)經(jīng)常會(huì)在Controller類(lèi)中看到這樣的代碼: 編寫(xiě)人員給出的理由是:一個(gè)簡(jiǎn)單的接口函數(shù),這么寫(xiě)也能滿(mǎn)足需求,沒(méi)有必要去封裝成一個(gè)服務(wù)函數(shù)。 2.2.一個(gè)特殊的案例案例代碼如下: 訪(fǎng)問(wèn)結(jié)果如下: curl http://localhost:8080/test/access 為什么參數(shù)systemName(系統(tǒng)名稱(chēng))沒(méi)有被注入值?《Spring Documentation》給出的解釋是:
意思是說(shuō):@Value是通過(guò)BeanPostProcessor來(lái)處理的,而WebApplicationContex和ApplicationContext是單獨(dú)處理的,所以WebApplicationContex不能使用父容器的屬性值。 所以,Controller不滿(mǎn)足Service的需求,不要把業(yè)務(wù)代碼寫(xiě)在Controller類(lèi)中。 2.3.服務(wù)端三層架構(gòu)SpringMVC服務(wù)端采用經(jīng)典的三層架構(gòu),即表現(xiàn)層、業(yè)務(wù)層、持久層,分別采用@Controller、@Service、@Repository進(jìn)行類(lèi)注解。 表現(xiàn)層(Presentation):又稱(chēng)控制層(Controller),負(fù)責(zé)接收客戶(hù)端請(qǐng)求,并向客戶(hù)端響應(yīng)結(jié)果,通常采用HTTP協(xié)議。 業(yè)務(wù)層(Business):又稱(chēng)服務(wù)層(Service),負(fù)責(zé)業(yè)務(wù)相關(guān)邏輯處理,按照功能分為服務(wù)、作業(yè)等。 持久層(Persistence):又稱(chēng)倉(cāng)庫(kù)層(Repository),負(fù)責(zé)數(shù)據(jù)的持久化,用于業(yè)務(wù)層訪(fǎng)問(wèn)緩存和數(shù)據(jù)庫(kù)。 所以,把業(yè)務(wù)代碼寫(xiě)入到Controller類(lèi)中,是不符合SpringMVC服務(wù)端三層架構(gòu)規(guī)范的。 3.把持久層代碼寫(xiě)在Service中把持久層代碼寫(xiě)在Service中,從功能上來(lái)看并沒(méi)有什么問(wèn)題,這也是很多人欣然接受的原因。 3.1.引起以下主要問(wèn)題
3.2.把數(shù)據(jù)庫(kù)代碼寫(xiě)在Service中這里以數(shù)據(jù)庫(kù)持久化中間件Hibernate的直接查詢(xún)?yōu)槔?/p> 現(xiàn)象描述:建議方案:關(guān)于插件:阿里的AliGenerator是一款基于MyBatis Generator改造的DAO層代碼自動(dòng)生成工具。利用AliGenerator生成的代碼,在執(zhí)行復(fù)雜查詢(xún)的時(shí)候,需要在業(yè)務(wù)代碼中組裝查詢(xún)條件,使業(yè)務(wù)代碼顯得特別臃腫。 個(gè)人不喜歡用DAO層代碼生成插件,更喜歡用原汁原味的MyBatis XML映射,主要原因如下:
當(dāng)然,既然選擇了使用DAO層代碼生成插件,在享受便利的同時(shí)也應(yīng)該接受插件的缺點(diǎn)。 3.3.把Redis代碼寫(xiě)在Service中現(xiàn)象描述:建議方案:把一個(gè)Redis對(duì)象相關(guān)操作接口封裝為一個(gè)DAO類(lèi),符合面對(duì)對(duì)象的編程思想,也符合SpringMVC服務(wù)端三層架構(gòu)規(guī)范,更便于代碼的管理和維護(hù)。 4.把數(shù)據(jù)庫(kù)模型類(lèi)暴露給接口4.1.現(xiàn)象描述上面的代碼,看上去是滿(mǎn)足SpringMVC服務(wù)端三層架構(gòu)的,唯一的問(wèn)題就是把數(shù)據(jù)庫(kù)模型類(lèi)UserDO直接暴露給了外部接口。 4.2.存在問(wèn)題及解決方案存在問(wèn)題:
解決方案:
4.3.項(xiàng)目搭建的三種方式下面,將介紹如何更科學(xué)地搭建Java項(xiàng)目,有效地限制開(kāi)發(fā)人員把數(shù)據(jù)庫(kù)模型類(lèi)暴露給接口。 第1種:共用模型的項(xiàng)目搭建共用模型的項(xiàng)目搭建,把所有模型類(lèi)放在一個(gè)模型項(xiàng)目(example-model)中,其它項(xiàng)目(example-repository、example-service、example-website)都依賴(lài)該模型項(xiàng)目,關(guān)系圖如下:
風(fēng)險(xiǎn): 表現(xiàn)層項(xiàng)目(example-webapp)可以調(diào)用業(yè)務(wù)層項(xiàng)目(example-service)中的任意服務(wù)函數(shù),甚至于越過(guò)業(yè)務(wù)層直接調(diào)用持久層項(xiàng)目(example-repository)的DAO函數(shù)。 第2種:模型分離的項(xiàng)目搭建模型分離的項(xiàng)目搭建,單獨(dú)搭建API項(xiàng)目(example-api),抽象出對(duì)外接口及其模型VO類(lèi)。業(yè)務(wù)層項(xiàng)目(example-service)實(shí)現(xiàn)了這些接口,并向表現(xiàn)層項(xiàng)目(example-webapp)提供服務(wù)。表現(xiàn)層項(xiàng)目(example-webapp)只調(diào)用API項(xiàng)目(example-api)定義的服務(wù)接口。
風(fēng)險(xiǎn): 表現(xiàn)層項(xiàng)目(example-webapp)仍然可以調(diào)用業(yè)務(wù)層項(xiàng)目(example-service)提供的內(nèi)部服務(wù)函數(shù)和持久層項(xiàng)目(example-repository)的DAO函數(shù)。為了避免這種情況,只好管理制度上要求表現(xiàn)層項(xiàng)目(example-webapp)只能調(diào)用API項(xiàng)目(example-api)定義的服務(wù)接口函數(shù)。 第3種:服務(wù)化的項(xiàng)目搭建服務(wù)化的項(xiàng)目搭,就是把業(yè)務(wù)層項(xiàng)目(example-service)和持久層項(xiàng)目(example-repository)通過(guò)Dubbo項(xiàng)目(example-dubbo)打包成一個(gè)服務(wù),向業(yè)務(wù)層項(xiàng)目(example-webapp)或其它業(yè)務(wù)項(xiàng)目(other-service)提供API項(xiàng)目(example-api)中定義的接口函數(shù)。
說(shuō)明:Dubbo項(xiàng)目(example-dubbo)只發(fā)布API項(xiàng)目(example-api)中定義的服務(wù)接口,保證了數(shù)據(jù)庫(kù)模型無(wú)法暴露。業(yè)務(wù)層項(xiàng)目(example-webapp)或其它業(yè)務(wù)項(xiàng)目(other-service)只依賴(lài)了API項(xiàng)目(example-api),只能調(diào)用該項(xiàng)目中定義的服務(wù)接口。 4.4.一條不太建議的建議有人會(huì)問(wèn):接口模型和持久層模型分離,接口定義了一個(gè)查詢(xún)數(shù)據(jù)模型VO類(lèi),持久層也需要定義一個(gè)查詢(xún)數(shù)據(jù)模型DO類(lèi);接口定義了一個(gè)返回?cái)?shù)據(jù)模型VO類(lèi),持久層也需要定義一個(gè)返回?cái)?shù)據(jù)模型DO類(lèi)……這樣,對(duì)于項(xiàng)目早期快速迭代開(kāi)發(fā)非常不利。能不能只讓接口不暴露持久層數(shù)據(jù)模型,而能夠讓持久層使用接口的數(shù)據(jù)模型? 如果從SpringMVC服務(wù)端三層架構(gòu)來(lái)說(shuō),這是不允許的,因?yàn)樗鼤?huì)影響三層架構(gòu)的獨(dú)立性。但是,如果從快速迭代開(kāi)發(fā)來(lái)說(shuō),這是允許的,因?yàn)樗⒉粫?huì)暴露數(shù)據(jù)庫(kù)模型類(lèi)。所以,這是一條不太建議的建議。 后記“仁者見(jiàn)仁、智者見(jiàn)智”,每個(gè)人都有自己的想法,而文章的內(nèi)容也只是我的一家之言。 謹(jǐn)以此文獻(xiàn)給那些我工作過(guò)的創(chuàng)業(yè)公司,是您們?cè)?jīng)放手讓我去整改亂象,讓我從中受益頗深并得以技術(shù)成長(zhǎng)。 作者:中間件小哥 |
|
來(lái)自: 一守望先鋒一 > 《待分類(lèi)》