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