如何搭建合適的Web框架?

以前在Web開發框架推導一文中咱們一步步的搭建了一個開發框架。前端

如何搭建合適的Web框架?

在當時的狀況下,還算知足需求。可是隨着項目的逐漸完善,需求變動的頻度逐漸變得比新增需求的頻度高,原來框架的弊端愈來愈明顯,因此須要對框架進行升級改進。面試

咱們先來看原來框架的問題,而後基於這些問題,來對框架進行改進。sql

原框架的問題

  • 代碼生成問題
  • 參數傳遞問題
  • Service層問題
  • 測試依賴問題
  • Mapper.xml的問題

代碼生成問題數據庫

在原框架中,咱們基於各類約束,編寫了一個代碼生成組件,經過這個組件,咱們能夠針對選中的表來生成Controller,Service,Model,Mapper等一系列的類,也就是說,只要建完表,就能夠直接生成一套CRUD,直接就能夠啓動並測試。這在項目初期看起來很美,可是在需求變更時,仍是有不少的侷限性。markdown

首先,生成的代碼邏輯是固化的。若是稍微有些調整,就須要調整生成代碼的組件,而後從新打包,上傳到jar倉庫,項目修改組件版本,再進行代碼生成,整個流程過於繁瑣。app

其次,爲了方便代碼的生成,實際上是作了很多妥協的:框架

  • 爲了方便在修改表字段之後,可以從新生成,不少類都抽象了一個基類用於操做Model字段。這些基類不可以手動修改,由於每次生成都會覆蓋。這實際致使了類的數量的增多。
  • 生成的CRUD固化了,不能手動調整。若是生成的CRUD不知足需求,不能直接在代碼上修改。只能拷貝一份進行修改,由於再次生成時會覆蓋。這致使了代碼的冗餘。
  • Param和Result委託了Model,這在Model發生改變時,能在編譯期就能知道對應字段的調整。可是也引入了很多問題,咱們在「參數傳遞問題」一節單獨討論。

參數傳遞問題模塊化

當初爲了便於代碼的生成,決定Param和Result都繼承Model,這致使了以下的一些問題:工具

  • 使得Param和Result都依賴了Model。可是Param和Result是視圖層模型,而Model是持久層模型,二者的進化度並非一致的。可是如今的繼承關係致使了在默認狀況下視圖層模型的進化須要和持久層同步,固然你也能夠手動調整Param和Result,可是這又致使了代碼生成的優點沒有了。
  • Param和Result經過委託的方式來設置字段,也就是說,它們實際是沒有字段的,經過getter和setter將值設置到了Model中。這就無法使用lombok來簡化getter和setter,使得Param和Result代碼行數較多
  • 同時,對於swagger來講,有些註解須要基於字段,致使某些功能沒法實現(例如:ModelAttribute),只能基於額外手段來處理(例如:須要經過ApiImplicitParams來實現字段文檔)。
  • CRUD都是基於同一個Param和Result,致使前端的接口會顯示不少無用的字段,加大前端理解接口的難度

Service層問題oop

Service層有以下問題:

  • Service層的職責太重,包括了事務處理、參數設置、業務邏輯

  • 致使Service中的代碼是麪條代碼,不利於業務邏輯的理解

  • 同時事務註解是直接加在類上的,Spring的默認事務機制會致使相似以下代碼的邏輯調用不會拋出指望的異常

    // PostService public String savePost(Post post) { postRepository.save(post); for(PostDiscuss discuss : post.getDiscuss()) { // 這裏是抓不到RuntimeException異常的,會是一個TransactionRollBack的異常 discussService.save(discuss); } } // discussService public String savePost(PostDiscuss discuss) { throw new RuntimeException("保存失敗"); }

測試依賴問題

核心的業務邏輯在Service中,測試仍是須要依賴於Spring,當項目愈來愈大時,啓動項目的時間愈來愈長,可能要1分鐘甚至更長。這就致使單元測試效率愈來愈低。

Mapper.xml的問題

在面試的時候,我常常會問下面的一些問題:

  • Java裏面接口的做用是什麼?
  • Service、DAO爲何要編寫接口,再去實現這個接口?
  • 接口和實如今相同的模塊下,反正都要從新打包的。多寫個接口不是多寫了好幾行代碼嗎?
  • 和上面相似的問題,Mybatis裏面,聲稱將sql獨立到了Mapper.xml文件中,使得能夠不須要編譯直接修改sql。但Mapper.xml都是和Class放在一塊兒的,改了仍是須要從新打包,並且Mybatis是不能動態加載Mapper.xml的,那把sql獨立到XML裏,到底有什麼優點?

對於最後一個問題,個人答案是,對於大部分項目來講,沒什麼優點項目易不易於部署、擴展,不在於你使用的框架,而在於你的設計

就以Mapper.xml來講,Mybatis將sql與代碼分離了,可是你在項目裏仍是將Mapper.xml和代碼放在同一個模塊下,那這個優點就沒有了。既然沒有這個優點,咱們還有必要單獨寫Mapper.xml文件嗎?個人選擇是,那就不寫了,直接使用Mybatis提供的註解。

同時爲了解決Service層對DAO層(這裏也就是對Mybatis)的強依賴,對框架進行了一些改進,解耦Service和DAO層。具體見下面的改進方案。

框架改進方案

爲了解決上面這些問題,對框架進行了以下調整:

  • 分離Param、Result和Model
  • 替換代碼生成
  • 獨立業務邏輯
  • Model層優化

分離Param、Result和Model

上面已經提到了Param、Result和Model強耦合會有不少問題,因此這裏就將Param、Result和Model分離開。每一個都是獨立的Bean,這就解決了上面幾個問題。可是引入了兩個新問題:

  • 首先,很明顯的,增長了手動編碼的量。當一個表修改了字段,須要修改三個類甚至更多的類
  • 其次,增長了數據傳遞之間的代碼。即Param傳遞到Model,須要對字段賦值。若是一個字段一個字段的設值,會增長不少無聊的代碼。而使用反射的話會對性能有一些影響

那如何解決這兩個問題呢?首先,純手擼確定是不可能的。須要提供一些自動化手段。

對於賦值來講,Spring提供了BeanUtils來簡化處理,雖然是基於反射來設值的,可是對於現階段來講,這點性能損耗仍是沒什麼影響的。可是,BeanUtils對於不一樣類型的屬性不能進行拷貝,假設我有一個Domain對象Book,裏面有個字段Author,如今我要賦值給BookResult,其中有個字段AuthorResult,此時BeanUtils是沒法賦值的。因此我編寫了一個基於Gson的工具類來處理,性能測試10000次的屬性拷貝BeanUtils須要500多毫秒,基於Gson的工具類只須要300毫秒左右。

對於表字段的生成,若是使用的是IDEA的話,IDE默認提供了一個腳本,能夠從表來生成POJO!咱們可使用這個腳原本生成Model,而後將字段拷貝到Param和Result中,來簡化字段的編寫。我對這個腳本進行了修改,以符合項目需求。主要增長了lombok的支持,新增了類註釋和字段註釋。

替換代碼生成

對於上面代碼生成組件的問題,我調整了代碼生成的方式。再也不基於組件來生成,而是基於IDEA自己的FileTemplate、LiveTemplate以及Scripted Extensions來進行生成。雖然這樣的方式,不可以一次性生成多個文件,可是因爲生成邏輯基本是一次性的,因此影響不是很大。在初次生成代碼時,代碼生成組件的效率是高於FileTemplate、LiveTemplate以及Scripted Extensions的組合,可是後期調整的靈活性,明顯是FileTemplate、LiveTemplate以及Scripted Extensions的組合要高於代碼生成組件的:

  • 首先,當文件結構調整時,只須要修改FileTemplate,並將配置文件導出給項目組成員便可。
  • 一樣的,當LiveTemplate調整時,也只須要修改對應的LiveTemplate,並將配置文件導出給項目組成員便可。
  • 其次,想生成哪一個文件,只要針對這個文件生成便可
  • 第三,經過FileTemplate生成完整的文件後,能夠經過LiveTemplate快速的進行模塊化的編碼
  • 最後,FileTemplate能夠設置爲項目級別,即每一個項目能夠有獨立的FileTemplate

具體的操做流程,在下面演示。

獨立業務邏輯

針對Service和測試的問題,將原來的Controller、Service和Model三層,拆分爲四層:

  • Controller負責前端數據的接收和返回,以及統一異常處理
  • Service負責事務以及Domain層邏輯的組裝。這裏就不會出現事務嵌套問題,也就不會致使抓不到指望的異常的問題
  • Domain負責業務邏輯
  • Model負責數據持久

這樣Service的職責減輕了,同時再也不有事務嵌套的問題。

Model層優化

上面提到,框架中最終放棄了Mapper.xml,轉而使用Mybatis的註解來實現持久化操做。改用註解,規避了XML代碼的編寫,可是並無解決框架對Mybatis的強依賴。因此這裏在Domain中新增了Repository接口層,此層用於定義Domain的持久化操做,而Model層中對Repository進行實現,這裏的實現就是Mybatis實現。這樣作有兩個好處:

  • 依賴倒置:原來是Domain依賴Model層,而如今是Model層依賴Domain層,這樣當我要把Mybatis替換掉時,Domain徹底無感知。
  • 獨立測試:由於如今Domain不依賴於其它任何層,因此能夠脫離數據庫和容器來進行測試。使得測試的效率不會隨着項目的開發而愈來愈低

如何搭建合適的Web框架?

框架改進細節

如今已經知道了,如何對框架進行改進,咱們如今就開始着手進行改造。其實主要的改造是對代碼生成方式的改造,也就是編寫FileTemplate、LiveTemplate和ScriptedExtensions。下面對這三個功能進行簡單的說明,先說ScriptedExtensions。

Scripted Extensions

先來解釋一下,什麼是Scripted Extensions。咱們都知道,如今的IDE都是插件式的,也就是說,咱們能夠經過開發商提供的插件開發包來開發插件,擴展示有的IDE功能。可是編寫插件須要特定的開發環境,若是是一個很簡單的功能,還要費勁去搭開發環境,挺麻煩的。因此IDEA提供了Scripted Extensions,能夠理解爲一個簡化版的插件,就是能夠經過腳原本擴展IDE功能。

IDEA提供了Database功能,能夠鏈接數據庫進行相關操做。當你鏈接了數據庫,在表上右擊時,能夠看到Scripted Extensions這個選項,裏面有一個功能是能夠基於表來生成POJO的groovy腳本。

可是功能比較low:

  • 包名是寫死的:com.sample
  • 沒有生成table註釋
  • 沒有基於lombok來簡化getter和setter

不過好在,咱們能基於這個腳原本自行修改,在剛纔的Scripted Extensions菜單裏,有個Go to Scripts Directory選項,點擊後,能夠進入腳本目錄。

如何搭建合適的Web框架?

直接對這個groovy文件Ctrl+c,Ctrl-v,複製一份,重命名一下,基於這個腳本進行修改便可。具體怎麼修改,按照本身的需求來,裏面主要就是根據表信息對String的拼接而已。

FileTemplate

FileTemplate是IDEA提供的生成文件的模板,你在點擊菜單的File->New...之後,出現的各類文件,都是基於FileTemplate來實現的。咱們自定義的Controller、Service、Domain等類,均可以經過FileTemplate來簡化建立。

具體使用方式爲,按下Ctrl-Alt-S呼出設置菜單,點擊Editor->File And Code Template,在裏面新增Template便可。

如何搭建合適的Web框架?

幾點說明:

  • 下面的描述中列出了默認的一些參數以及做用
  • 你也能夠自定義變量,自定義的變量若是沒有賦值,在建立時會有輸入框提示輸入內容
  • 模板是基於Velocity的,因此若是你熟悉 Velocity,那就能夠直接上手
  • Enable Live Template選項是在FileTemplate激活LiveTemplate變量,不過須要使用#[[]]#包裹。可是對於建立Java,這個功能有bug,並不能定位到須要的位置,因此暫時沒使用

建立完成後,就能夠在New菜單中看到這個模板了。

LiveTemplate

LiveTemplate實際就是CodeSnippet。建立方式和FileTemplate相似。按下Ctrl-Alt-S呼出設置菜單,點擊Editor->Live Template,在裏面新增Template便可。

如何搭建合適的Web框架?

幾點說明:

  • 這裏的變量是使用$$包裹
  • 每一個變量就是一個佔位符,在使用tab展開後,能夠手動輸入值
  • 右下角的Edit variables,用於對變量賦值,IDEA提供了一些方法、也能夠設置默認值
  • 下面的change連接,能夠選擇LiveTemplate生效的位置,好比只在Java類聲明處生效

編碼流程

建立了上面的幾個模板後,編碼流程以下:

  • 在表上右擊,經過Scripted Extensions來生成Model
  • 經過FileTemplate來快速生成Controller、Service、Domain等類
  • 經過LiveTemplate來快速編寫代碼

總結

本文經過對原框架問題的梳理及解決,來對框架進行升級改造,以適應項目的發展和推動。

相關文章
相關標籤/搜索