新聞中心
之前在Web開發(fā)框架推導(dǎo)一文中我們一步步的搭建了一個(gè)開發(fā)框架。

成都創(chuàng)新互聯(lián)是創(chuàng)新、創(chuàng)意、研發(fā)型一體的綜合型網(wǎng)站建設(shè)公司,自成立以來(lái)公司不斷探索創(chuàng)新,始終堅(jiān)持為客戶提供滿意周到的服務(wù),在本地打下了良好的口碑,在過(guò)去的10年時(shí)間我們累計(jì)服務(wù)了上千家以及全國(guó)政企客戶,如軟裝設(shè)計(jì)等企業(yè)單位,完善的項(xiàng)目管理流程,嚴(yán)格把控項(xiàng)目進(jìn)度與質(zhì)量監(jiān)控加上過(guò)硬的技術(shù)實(shí)力獲得客戶的一致贊揚(yáng)。
在當(dāng)時(shí)的情況下,還算滿足需求。但是隨著項(xiàng)目的逐漸完善,需求變更的頻度逐漸變得比新增需求的頻度高,原來(lái)框架的弊端越來(lái)越明顯,所以需要對(duì)框架進(jìn)行升級(jí)改進(jìn)。
我們先來(lái)看原來(lái)框架的問(wèn)題,然后基于這些問(wèn)題,來(lái)對(duì)框架進(jìn)行改進(jìn)。
原框架的問(wèn)題
- 代碼生成問(wèn)題
- 參數(shù)傳遞問(wèn)題
- Service層問(wèn)題
- 測(cè)試依賴問(wèn)題
- Mapper.xml的問(wèn)題
代碼生成問(wèn)題
在原框架中,我們基于各種約束,編寫了一個(gè)代碼生成組件,通過(guò)這個(gè)組件,我們可以針對(duì)選中的表來(lái)生成Controller,Service,Model,Mapper等一系列的類,也就是說(shuō),只要建完表,就可以直接生成一套CRUD,直接就可以啟動(dòng)并測(cè)試。這在項(xiàng)目初期看起來(lái)很美,但是在需求變動(dòng)時(shí),還是有很多的局限性。
首先,生成的代碼邏輯是固化的。如果稍微有些調(diào)整,就需要調(diào)整生成代碼的組件,然后重新打包,上傳到j(luò)ar倉(cāng)庫(kù),項(xiàng)目修改組件版本,再進(jìn)行代碼生成,整個(gè)流程過(guò)于繁瑣。
其次,為了方便代碼的生成,其實(shí)是做了不少妥協(xié)的:
- 為了方便在修改表字段以后,能夠重新生成,很多類都抽象了一個(gè)基類用于操作Model字段。這些基類不能夠手動(dòng)修改,因?yàn)槊看紊啥紩?huì)覆蓋。這實(shí)際導(dǎo)致了類的數(shù)量的增多。
- 生成的CRUD固化了,不能手動(dòng)調(diào)整。如果生成的CRUD不滿足需求,不能直接在代碼上修改。只能拷貝一份進(jìn)行修改,因?yàn)樵俅紊蓵r(shí)會(huì)覆蓋。這導(dǎo)致了代碼的冗余。
- Param和Result委托了Model,這在Model發(fā)生改變時(shí),能在編譯期就能知道對(duì)應(yīng)字段的調(diào)整。但是也引入了不少問(wèn)題,我們?cè)凇竻?shù)傳遞問(wèn)題」一節(jié)單獨(dú)討論。
參數(shù)傳遞問(wèn)題
當(dāng)初為了便于代碼的生成,決定Param和Result都繼承Model,這導(dǎo)致了如下的一些問(wèn)題:
- 使得Param和Result都依賴了Model。但是Param和Result是視圖層模型,而Model是持久層模型,兩者的進(jìn)化度并不是一致的。但是現(xiàn)在的繼承關(guān)系導(dǎo)致了在默認(rèn)情況下視圖層模型的進(jìn)化需要和持久層同步,當(dāng)然你也可以手動(dòng)調(diào)整Param和Result,但是這又導(dǎo)致了代碼生成的優(yōu)勢(shì)沒(méi)有了。
- Param和Result通過(guò)委托的方式來(lái)設(shè)置字段,也就是說(shuō),它們實(shí)際是沒(méi)有字段的,通過(guò)getter和setter將值設(shè)置到了Model中。這就沒(méi)法使用lombok來(lái)簡(jiǎn)化getter和setter,使得Param和Result代碼行數(shù)較多
- 同時(shí),對(duì)于swagger來(lái)說(shuō),有些注解需要基于字段,導(dǎo)致某些功能無(wú)法實(shí)現(xiàn)(例如:ModelAttribute),只能基于額外手段來(lái)處理(例如:需要通過(guò)ApiImplicitParams來(lái)實(shí)現(xiàn)字段文檔)。
- CRUD都是基于同一個(gè)Param和Result,導(dǎo)致前端的接口會(huì)顯示很多無(wú)用的字段,加大前端理解接口的難度
Service層問(wèn)題
Service層有如下問(wèn)題:
- Service層的職責(zé)過(guò)重,包括了事務(wù)處理、參數(shù)設(shè)置、業(yè)務(wù)邏輯
- 導(dǎo)致Service中的代碼是面條代碼,不利于業(yè)務(wù)邏輯的理解
- 同時(shí)事務(wù)注解是直接加在類上的,Spring的默認(rèn)事務(wù)機(jī)制會(huì)導(dǎo)致類似如下代碼的邏輯調(diào)用不會(huì)拋出期望的異常
- // PostService
- public String savePost(Post post) {
- postRepository.save(post);
- for(PostDiscuss discuss : post.getDiscuss()) {
- // 這里是抓不到RuntimeException異常的,會(huì)是一個(gè)TransactionRollBack的異常
- discussService.save(discuss);
- }
- }
- // discussService
- public String savePost(PostDiscuss discuss) {
- throw new RuntimeException("保存失敗");
- }
測(cè)試依賴問(wèn)題
核心的業(yè)務(wù)邏輯在Service中,測(cè)試還是需要依賴于Spring,當(dāng)項(xiàng)目越來(lái)越大時(shí),啟動(dòng)項(xiàng)目的時(shí)間越來(lái)越長(zhǎng),可能要1分鐘甚至更長(zhǎng)。這就導(dǎo)致單元測(cè)試效率越來(lái)越低。
Mapper.xml的問(wèn)題
在面試的時(shí)候,我經(jīng)常會(huì)問(wèn)下面的一些問(wèn)題:
- Java里面接口的作用是什么?
- Service、DAO為什么要編寫接口,再去實(shí)現(xiàn)這個(gè)接口?
- 接口和實(shí)現(xiàn)在相同的模塊下,反正都要重新打包的。多寫個(gè)接口不是多寫了好幾行代碼嗎?
- 和上面類似的問(wèn)題,Mybatis里面,聲稱將sql獨(dú)立到了Mapper.xml文件中,使得可以不需要編譯直接修改sql。但Mapper.xml都是和Class放在一起的,改了還是需要重新打包,而且Mybatis是不能動(dòng)態(tài)加載Mapper.xml的,那把sql獨(dú)立到XML里,到底有什么優(yōu)勢(shì)?
對(duì)于最后一個(gè)問(wèn)題,我的答案是,對(duì)于大部分項(xiàng)目來(lái)說(shuō),沒(méi)什么優(yōu)勢(shì)!項(xiàng)目易不易于部署、擴(kuò)展,不在于你使用的框架,而在于你的設(shè)計(jì)。
就以Mapper.xml來(lái)說(shuō),Mybatis將sql與代碼分離了,但是你在項(xiàng)目里還是將Mapper.xml和代碼放在同一個(gè)模塊下,那這個(gè)優(yōu)勢(shì)就沒(méi)有了。既然沒(méi)有這個(gè)優(yōu)勢(shì),我們還有必要單獨(dú)寫Mapper.xml文件嗎?我的選擇是,那就不寫了,直接使用Mybatis提供的注解。
同時(shí)為了解決Service層對(duì)DAO層(這里也就是對(duì)Mybatis)的強(qiáng)依賴,對(duì)框架進(jìn)行了一些改進(jìn),解耦Service和DAO層。具體見(jiàn)下面的改進(jìn)方案。
框架改進(jìn)方案
為了解決上面這些問(wèn)題,對(duì)框架進(jìn)行了如下調(diào)整:
- 分離Param、Result和Model
- 替換代碼生成
- 獨(dú)立業(yè)務(wù)邏輯
- Model層優(yōu)化
分離Param、Result和Model
上面已經(jīng)提到了Param、Result和Model強(qiáng)耦合會(huì)有很多問(wèn)題,所以這里就將Param、Result和Model分離開。每個(gè)都是獨(dú)立的Bean,這就解決了上面幾個(gè)問(wèn)題。但是引入了兩個(gè)新問(wèn)題:
- 首先,很明顯的,增加了手動(dòng)編碼的量。當(dāng)一個(gè)表修改了字段,需要修改三個(gè)類甚至更多的類
- 其次,增加了數(shù)據(jù)傳遞之間的代碼。即Param傳遞到Model,需要對(duì)字段賦值。如果一個(gè)字段一個(gè)字段的設(shè)值,會(huì)增加很多無(wú)聊的代碼。而使用反射的話會(huì)對(duì)性能有一些影響
那如何解決這兩個(gè)問(wèn)題呢?首先,純手?jǐn)]肯定是不可能的。需要提供一些自動(dòng)化手段。
對(duì)于賦值來(lái)說(shuō),Spring提供了BeanUtils來(lái)簡(jiǎn)化處理,雖然是基于反射來(lái)設(shè)值的,但是對(duì)于現(xiàn)階段來(lái)說(shuō),這點(diǎn)性能損耗還是沒(méi)什么影響的。但是,BeanUtils對(duì)于不同類型的屬性不能進(jìn)行拷貝,假設(shè)我有一個(gè)Domain對(duì)象Book,里面有個(gè)字段Author,現(xiàn)在我要賦值給BookResult,其中有個(gè)字段AuthorResult,此時(shí)BeanUtils是無(wú)法賦值的。所以我編寫了一個(gè)基于Gson的工具類來(lái)處理,性能測(cè)試10000次的屬性拷貝BeanUtils需要500多毫秒,基于Gson的工具類只需要300毫秒左右。
對(duì)于表字段的生成,如果使用的是IDEA的話,IDE默認(rèn)提供了一個(gè)腳本,可以從表來(lái)生成POJO!我們可以使用這個(gè)腳本來(lái)生成Model,然后將字段拷貝到Param和Result中,來(lái)簡(jiǎn)化字段的編寫。我對(duì)這個(gè)腳本進(jìn)行了修改,以符合項(xiàng)目需求。主要增加了lombok的支持,新增了類注釋和字段注釋。
替換代碼生成
對(duì)于上面代碼生成組件的問(wèn)題,我調(diào)整了代碼生成的方式。不再基于組件來(lái)生成,而是基于IDEA本身的FileTemplate、LiveTemplate以及Scripted Extensions來(lái)進(jìn)行生成。雖然這樣的方式,不能夠一次性生成多個(gè)文件,但是由于生成邏輯基本是一次性的,所以影響不是很大。在初次生成代碼時(shí),代碼生成組件的效率是高于FileTemplate、LiveTemplate以及Scripted Extensions的組合,但是后期調(diào)整的靈活性,明顯是FileTemplate、LiveTemplate以及Scripted Extensions的組合要高于代碼生成組件的:
- 首先,當(dāng)文件結(jié)構(gòu)調(diào)整時(shí),只需要修改FileTemplate,并將配置文件導(dǎo)出給項(xiàng)目組成員即可。
- 同樣的,當(dāng)LiveTemplate調(diào)整時(shí),也只需要修改對(duì)應(yīng)的LiveTemplate,并將配置文件導(dǎo)出給項(xiàng)目組成員即可。
- 其次,想生成哪個(gè)文件,只要針對(duì)這個(gè)文件生成即可
- 第三,通過(guò)FileTemplate生成完整的文件后,可以通過(guò)LiveTemplate快速的進(jìn)行模塊化的編碼
- 最后,F(xiàn)ileTemplate可以設(shè)置為項(xiàng)目級(jí)別,即每個(gè)項(xiàng)目可以有獨(dú)立的FileTemplate
具體的操作流程,在下面演示。
獨(dú)立業(yè)務(wù)邏輯
針對(duì)Service和測(cè)試的問(wèn)題,將原來(lái)的Controller、Service和Model三層,拆分為四層:
- Controller負(fù)責(zé)前端數(shù)據(jù)的接收和返回,以及統(tǒng)一異常處理
- Service負(fù)責(zé)事務(wù)以及Domain層邏輯的組裝。這里就不會(huì)出現(xiàn)事務(wù)嵌套問(wèn)題,也就不會(huì)導(dǎo)致抓不到期望的異常的問(wèn)題
- Domain負(fù)責(zé)業(yè)務(wù)邏輯
- Model負(fù)責(zé)數(shù)據(jù)持久
這樣Service的職責(zé)減輕了,同時(shí)不再有事務(wù)嵌套的問(wèn)題。
Model層優(yōu)化
上面提到,框架中最終放棄了Mapper.xml,轉(zhuǎn)而使用Mybatis的注解來(lái)實(shí)現(xiàn)持久化操作。改用注解,規(guī)避了XML代碼的編寫,但是并沒(méi)有解決框架對(duì)Mybatis的強(qiáng)依賴。所以這里在Domain中新增了Repository接口層,此層用于定義Domain的持久化操作,而Model層中對(duì)Repository進(jìn)行實(shí)現(xiàn),這里的實(shí)現(xiàn)就是Mybatis實(shí)現(xiàn)。這樣做有兩個(gè)好處:
- 依賴倒置:原來(lái)是Domain依賴Model層,而現(xiàn)在是Model層依賴Domain層,這樣當(dāng)我要把Mybatis替換掉時(shí),Domain完全無(wú)感知。
- 獨(dú)立測(cè)試:因?yàn)楝F(xiàn)在Domain不依賴于其它任何層,所以可以脫離數(shù)據(jù)庫(kù)和容器來(lái)進(jìn)行測(cè)試。使得測(cè)試的效率不會(huì)隨著項(xiàng)目的開發(fā)而越來(lái)越低
框架改進(jìn)細(xì)節(jié)
現(xiàn)在已經(jīng)知道了,如何對(duì)框架進(jìn)行改進(jìn),我們現(xiàn)在就開始著手進(jìn)行改造。其實(shí)主要的改造是對(duì)代碼生成方式的改造,也就是編寫FileTemplate、LiveTemplate和ScriptedExtensions。下面對(duì)這三個(gè)功能進(jìn)行簡(jiǎn)單的說(shuō)明,先說(shuō)ScriptedExtensions。
Scripted Extensions
先來(lái)解釋一下,什么是Scripted Extensions。我們都知道,現(xiàn)在的IDE都是插件式的,也就是說(shuō),我們可以通過(guò)開發(fā)商提供的插件開發(fā)包來(lái)開發(fā)插件,擴(kuò)展現(xiàn)有的IDE功能。但是編寫插件需要特定的開發(fā)環(huán)境,如果是一個(gè)很簡(jiǎn)單的功能,還要費(fèi)勁去搭開發(fā)環(huán)境,挺麻煩的。所以IDEA提供了Scripted Extensions,可以理解為一個(gè)簡(jiǎn)化版的插件,就是可以通過(guò)腳本來(lái)擴(kuò)展IDE功能。
IDEA提供了Database功能,可以連接數(shù)據(jù)庫(kù)進(jìn)行相關(guān)操作。當(dāng)你連接了數(shù)據(jù)庫(kù),在表上右擊時(shí),可以看到Scripted Extensions這個(gè)選項(xiàng),里面有一個(gè)功能是可以基于表來(lái)生成POJO的groovy腳本。
但是功能比較low:
- 包名是寫死的:com.sample
- 沒(méi)有生成table注釋
- 沒(méi)有基于lombok來(lái)簡(jiǎn)化getter和setter
不過(guò)好在,我們能基于這個(gè)腳本來(lái)自行修改,在剛才的Scripted Extensions菜單里,有個(gè)Go to Scripts Directory選項(xiàng),點(diǎn)擊后,可以進(jìn)入腳本目錄。
直接對(duì)這個(gè)groovy文件Ctrl+c,Ctrl-v,復(fù)制一份,重命名一下,基于這個(gè)腳本進(jìn)行修改即可。具體怎么修改,按照自己的需求來(lái),里面主要就是根據(jù)表信息對(duì)String的拼接而已。
FileTemplate
FileTemplate是IDEA提供的生成文件的模板,你在點(diǎn)擊菜單的File->New...以后,出現(xiàn)的各種文件,都是基于FileTemplate來(lái)實(shí)現(xiàn)的。我們自定義的Controller、Service、Domain等類,都可以通過(guò)FileTemplate來(lái)簡(jiǎn)化創(chuàng)建。
具體使用方式為,按下Ctrl-Alt-S呼出設(shè)置菜單,點(diǎn)擊Editor->File And Code Template,在里面新增Template即可。
幾點(diǎn)說(shuō)明:
- 下面的描述中列出了默認(rèn)的一些參數(shù)以及作用
- 你也可以自定義變量,自定義的變量如果沒(méi)有賦值,在創(chuàng)建時(shí)會(huì)有輸入框提示輸入內(nèi)容
- 模板是基于Velocity的,所以如果你熟悉 Velocity,那就可以直接上手
- Enable Live Template選項(xiàng)是在FileTemplate激活LiveTemplate變量,不過(guò)需要使用#[[]]#包裹。但是對(duì)于創(chuàng)建Java,這個(gè)功能有bug,并不能定位到需要的位置,所以暫時(shí)沒(méi)使用
創(chuàng)建完成后,就可以在New菜單中看到這個(gè)模板了。
LiveTemplate
LiveTemplate實(shí)際就是CodeSnippet。創(chuàng)建方式和FileTemplate類似。按下Ctrl-Alt-S呼出設(shè)置菜單,點(diǎn)擊Editor->Live Template,在里面新增Template即可。
幾點(diǎn)說(shuō)明:
- 這里的變量是使用$$包裹
- 每個(gè)變量就是一個(gè)占位符,在使用tab展開后,可以手動(dòng)輸入值
- 右下角的Edit variables,用于對(duì)變量賦值,IDEA提供了一些方法、也可以設(shè)置默認(rèn)值
- 下面的change鏈接,可以選擇LiveTemplate生效的位置,比如只在Java類聲明處生效
編碼流程
創(chuàng)建了上面的幾個(gè)模板后,編碼流程如下:
- 在表上右擊,通過(guò)Scripted Extensions來(lái)生成Model
- 通過(guò)FileTemplate來(lái)快速生成Controller、Service、Domain等類
- 通過(guò)LiveTemplate來(lái)快速編寫代碼
總結(jié)
本文通過(guò)對(duì)原框架問(wèn)題的梳理及解決,來(lái)對(duì)框架進(jìn)行升級(jí)改造,以適應(yīng)項(xiàng)目的發(fā)展和推進(jìn)。
網(wǎng)站標(biāo)題:如何搭建合適的Web框架?
瀏覽路徑:http://www.dlmjj.cn/article/dhsoeep.html


咨詢
建站咨詢
