日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

那些年,我們見(jiàn)過(guò)的Java服務(wù)端亂象

 一守望先鋒一 2019-08-06

查爾斯·狄更斯在《雙城記》中寫(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)必要性

首先,了解一下里氏替換原則:

里氏代換原則(Liskov Substitution Principle,簡(jiǎn)稱(chēng)LSP):所有引用基類(lèi)(父類(lèi))的地方必須能透明地使用其子類(lèi)的對(duì)象。

其次,了解一下基類(lèi)的優(yōu)點(diǎn):

  1. 子類(lèi)擁有父類(lèi)的所有方法和屬性,從而減少了創(chuàng)建子類(lèi)的工作量;

  2. 提高了代碼的重用性,子類(lèi)擁有父類(lèi)的所有功能;

  3. 提高了代碼的擴(kuò)展性,子類(lèi)可以添加自己的功能。

所以,我們可以得出以下結(jié)論:

  1. Controller基類(lèi)和Service基類(lèi)在整個(gè)項(xiàng)目中并沒(méi)有直接被使用,也就沒(méi)有可使用其子類(lèi)替換基類(lèi)的場(chǎng)景,所以不滿(mǎn)足里氏替換原則;

  2. Controller基類(lèi)和Service基類(lèi)并沒(méi)有抽象接口函數(shù)或虛函數(shù),即所有繼承基類(lèi)的子類(lèi)間沒(méi)有相關(guān)共性,直接導(dǎo)致在項(xiàng)目中仍然使用的是子類(lèi);

  3. Controller基類(lèi)和Service基類(lèi)只關(guān)注了重用性,即子類(lèi)能夠輕松使用基類(lèi)的注入DAO、注入服務(wù)、注入?yún)?shù)、靜態(tài)常量、服務(wù)函數(shù)、靜態(tài)函數(shù)等資源。但是,忽略了這些資源的必要性,即這些資源并不是子類(lèi)所必須的,反而給子類(lèi)帶來(lái)了加載時(shí)的性能損耗。

綜上所述,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
系統(tǒng)(null)歡迎您訪(fǎng)問(wèn)!

為什么參數(shù)systemName(系統(tǒng)名稱(chēng))沒(méi)有被注入值?《Spring Documentation》給出的解釋是:

Note that actual processing of the @Value annotation is performed by a BeanPostProcessor.

BeanPostProcessor interfaces are scoped per-container. This is only relevant if you are using container hierarchies. If you define a BeanPostProcessor in one container, it will only do its work on the beans in that container. Beans that are defined in one container are not post-processed by a BeanPostProcessor in another container, even if both containers are part of the same hierarchy.

意思是說(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)題

  1. 業(yè)務(wù)層和持久層混雜在一起,不符合SpringMVC服務(wù)端三層架構(gòu)規(guī)范;

  2. 在業(yè)務(wù)邏輯中組裝語(yǔ)句、主鍵等,增加了業(yè)務(wù)邏輯的復(fù)雜度;

  3. 在業(yè)務(wù)邏輯中直接使用第三方中間件,不便于第三方持久化中間件的替換;

  4. 同一對(duì)象的持久層代碼分散在各個(gè)業(yè)務(wù)邏輯中,背離了面對(duì)對(duì)象的編程思想;

  5. 在寫(xiě)單元測(cè)試用例時(shí),無(wú)法對(duì)持久層接口函數(shù)直接測(cè)試。

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映射,主要原因如下:

  • 會(huì)在項(xiàng)目中導(dǎo)入一些不符合規(guī)范的代碼;

  • 只需要進(jìn)行一個(gè)簡(jiǎn)單查詢(xún),也需要導(dǎo)入一整套復(fù)雜代碼;

  • 進(jìn)行復(fù)雜查詢(xún)時(shí),拼裝條件的代碼復(fù)雜且不直觀,不如在XML中直接編寫(xiě)SQL語(yǔ)句;

  • 變更表格后需要重新生成代碼并進(jìn)行覆蓋,可能會(huì)不小心刪除自定義函數(shù)。

當(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)題:

  1. 間接暴露數(shù)據(jù)庫(kù)表格設(shè)計(jì),給競(jìng)爭(zhēng)對(duì)手競(jìng)品分析帶來(lái)方便;

  2. 如果數(shù)據(jù)庫(kù)查詢(xún)不做字段限制,會(huì)導(dǎo)致接口數(shù)據(jù)龐大,浪費(fèi)用戶(hù)的寶貴流量;

  3. 如果數(shù)據(jù)庫(kù)查詢(xún)不做字段限制,容易把敏感字段暴露給接口,導(dǎo)致出現(xiàn)數(shù)據(jù)的安全問(wèn)題;

  4. 如果數(shù)據(jù)庫(kù)模型類(lèi)不能滿(mǎn)足接口需求,需要在數(shù)據(jù)庫(kù)模型類(lèi)中添加別的字段,導(dǎo)致數(shù)據(jù)庫(kù)模型類(lèi)跟數(shù)據(jù)庫(kù)字段不匹配問(wèn)題;

  5. 如果沒(méi)有維護(hù)好接口文檔,通過(guò)閱讀代碼是無(wú)法分辨出數(shù)據(jù)庫(kù)模型類(lèi)中哪些字段是接口使用的,導(dǎo)致代碼的可維護(hù)性變差。

解決方案:

  1. 從管理制度上要求數(shù)據(jù)庫(kù)和接口的模型類(lèi)完全獨(dú)立;

  2. 從項(xiàng)目結(jié)構(gòu)上限制開(kāi)發(fā)人員把數(shù)據(jù)庫(kù)模型類(lèi)暴露給接口。

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)系圖如下:

序號(hào)項(xiàng)目名稱(chēng)打包類(lèi)型項(xiàng)目功能
1example-modeljar定義了所有模型類(lèi),包括DO類(lèi)和VO類(lèi)等
2example-repositoryjar對(duì)應(yīng)持久層,實(shí)現(xiàn)了MySQL、Redis相關(guān)DAO等
3example-servicejar對(duì)應(yīng)業(yè)務(wù)層,實(shí)現(xiàn)了Service、Job、Workflow等
4example-webappwar對(duì)應(yīng)表現(xiàn)層,實(shí)現(xiàn)了Controller、Interceptor、Filter等

風(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ù)接口。

序號(hào)項(xiàng)目名稱(chēng)打包類(lèi)型項(xiàng)目功能
1example-apijar業(yè)務(wù)層的表現(xiàn)層,定義了對(duì)外開(kāi)放接口和VO類(lèi)
2example-repositoryjar對(duì)應(yīng)持久層,定義了DO類(lèi)并實(shí)現(xiàn)了MySQL、Redis相關(guān)DAO等
3example-servicejar對(duì)應(yīng)業(yè)務(wù)層,實(shí)現(xiàn)了Service、Job、Workflow等
4example-webappwar對(duì)應(yīng)表現(xiàn)層,實(shí)現(xiàn)了Controller、Interceptor、Filter等

風(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ù)。

序號(hào)項(xiàng)目名稱(chēng)打包類(lèi)型項(xiàng)目功能
1example-apijar對(duì)應(yīng)業(yè)務(wù)層的表現(xiàn)層,定義了對(duì)外開(kāi)放接口和VO類(lèi)
2example-repositoryjar對(duì)應(yīng)持久層,定義了DO類(lèi)并實(shí)現(xiàn)了MySQL、Redis相關(guān)DAO等
3example-servicejar對(duì)應(yīng)業(yè)務(wù)層,實(shí)現(xiàn)了Service、Job、Workflow等
4example-dubbowar對(duì)應(yīng)業(yè)務(wù)層的表現(xiàn)層,通過(guò)Dubbo提供服務(wù)
5example-webappwar對(duì)應(yīng)表現(xiàn)層,實(shí)現(xiàn)了Controller等,通過(guò)Dubbo調(diào)用服務(wù)
6other-servicejar對(duì)應(yīng)其它項(xiàng)目的業(yè)務(wù)層,通過(guò)Dubbo調(diào)用服務(wù)

說(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)。

作者:中間件小哥

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶(hù)發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶(hù) 評(píng)論公約

    類(lèi)似文章 更多