javaweb BaseServlet 自動封裝數據並調用service方法

這篇筆記是學習web開發時基於反射和泛型的產物,實際開發時不須要去造這種輪子,僅供參考html

引入

以前寫了一篇隨筆,說明了javaweb中如何自動封裝請求頭中的數據到指定實體類中
javaweb 自動封裝請求頭中的數據到指定實體類中
在隨筆中,前端請求的接口是肯定的,就是添加數據,如今咱們但願這個封裝能跟貼近實際使用一點,或者說跟自動化一點前端


整個項目要以數據庫的表爲基準,有多少張表,就要有多少個實體類,在以前的開發中,咱們都是單表開發,根據功能去拆分出多個servlet、service和dao。java

如圖就是典型的單表開發,根據功能書寫多個servlet、service和dao。
imageweb

可是如今有不少表,若是每一個表都要根據功能去寫servlet,整個項目就會很冗餘,也很不直觀。
因此咱們不該該根據功能去寫dao\service\servlet,而是應該根據表寫這些東西。
一張表對應一個實體類,對應一個dao和daoImpl,對應一個service和serviceImpl,對應一個servlet。
在dao中寫五個基本的方法(增刪改查、查全部)
而後在這個servlet中的doPost/doGet下用switch列出全部功能(增刪改查、查全部)ajax

若是這樣作,咱們就要在servlet中對瀏覽器的請求進行判斷,是執行增刪改查,仍是查全部
瀏覽器端表單提交請求類型:
image
服務器端servlet接收請求:
imagespring

1 Servlet封裝目標說明

封裝目標:數據庫

  1. 前端各個模塊發送的請求會指向後端不一樣的servlet,而且經過請求參數type的值說明須要執行的操做。
    後端每一個servlet上都經過使用switch-case調用對應的方法。
    以上的操做存在許多重複代碼,並且每次新增功能後,還得去維護switch-case,所以要下降代碼重複性,使用反射實現switch-case要實現的功能。json

  2. 每一個servlet中都直接將請求頭和響應頭傳給方法,在方法中完成請求頭的解析,一方面形成代碼重複,另外一方面也與「servlet只容許給方法傳遞對象」的代碼規範相違背
    由於方法通常都放在service裏,而servlet調用service的方法時,應該只傳遞對象,而不能傳遞請求頭、響應頭。
    所以咱們要下降重複性,消滅傳遞給方法的請求頭和響應頭,作到「只給方法傳遞對象」後端

  3. 若是」只給方法傳遞對象「,那麼就要求全部的方法在執行完畢後都要將結果返回給servlet,由servlet進行響應——這也會形成代碼重複,這也要解決。數組

2 封裝請求類型type 自動執行請求的操做 基礎版 便於理解

以前寫servle時,前端每一個模塊對應一個servlet,前端發送的請求被各模塊指定的servlet的doGet和doPost接收,從中取出type,使用switch-case進行判斷。
好比前端的模塊一須要執行查詢操做,那麼就須要後端的servlet_1取出請求參數type=show,而後進入case=show的分支,去調用指定的方法。
而後前端的模塊二須要執行添加操做,那麼就須要後端的servlet_2取出請求參數type=add,而後進入case=add的分支,去調用指定的方法。
這樣後期維護起來太麻煩,每加入一個新模塊就要重寫一次doGet、doPost,而且各個模塊裏還須要維護switch-case

例圖:ajax中實現級聯菜單,取出請求參數type,而後進入不一樣的分支
image

所以咱們如今要轉變整個項目的編寫思路。
首先前端保持不變,依然是根據不一樣的模塊指向後端不一樣的servlet,請求參數依然是根據不一樣的需求給出不一樣的type值
image

然後端則設立一個BaseServlet去實現doGet和doPost以及編碼設置,而後基於反射和泛型取出請求參數type的值,令servlet下的對應方法自動執行。
全部模塊的servlet經過繼承BaseServlet,實現代碼的降重,而servlet中根據模塊需求寫各自的方法。
核心方法——經過反射獲取類中的同名方法

語法 說明
Method 變量名 = Class對象 . getMethod ("方法名", 指向參數類型的Class對象) 獲取類中某個帶參的方法,因爲存在方法重載的可能,所以須要給定方法的參數表

2.1 BaseServlet代碼

代碼寫在父類上,這是爲了代碼降重,但其實是在子類中被執行,此時this代指的是各類BaseServlet類的子類。
②下圖代碼中沒有設置編碼格式,是由於這部分代碼被放在了過濾器Filter中
③做爲基礎類的BaseServlet繼承HttpServlet,別的Servlet則繼承該BaseServlet
④雖然目前沒見過,可是有時候doGet和doPost須要執行不一樣的代碼,到時候就在BaseServlet中修改一下doGet中的代碼便可
image

2.2 子類代碼

①子類直接繼承BaseServlet,簡化了doGet和doPost的操做
②子類中根據模塊需求編寫響應的方法,從父類繼承的反射方法會自動解析前端發來的請求調用對應的方法
③子類中要對請求頭進行數據解析,轉換爲對象,而後使用
image

3 封裝請求類型type 自動執行請求的操做 正式版 實際使用

在上面的代碼中,已經實現了自動根據type類型去執行同名的操做,而且寫在了父類servlet上,令全部子類servlet都具有該功能。
能夠說已經實現了整個項目的代碼降重以及使用反射替代switch-case的目的

3.1 基礎版存在的問題

可是實際上存在三個問題:
①若是找不到同名的type,會報錯,由於上面的代碼中並無進行同名判斷以及結果爲空時的處理
②使用invoke調用方法時傳入的參數順序是固定的,如今這種寫法要求子類中的方法參數表只能是 "請求頭,響應頭",但實際上可能會不一樣
③每一個子servlet都須要對請求頭進行解析,將其中存儲的json對象轉爲實體對象,再傳給各自的方法。對請求頭進行解析的代碼也應該進行降重,讓每一個子servlet只須要編寫方法。

3.2 解決type找不到方法的問題

在執行完下圖代碼後,咱們能夠得到一個指向肯定的方法的Method對象,若是沒有取到,則返回null,不會因爲空指針而報錯,解決了問題①
注意:代碼在子類中執行,獲取到的是子類Servlet的方法,建議子類servlet中的方法都設置爲公開方法,避免這裏還得暴力破解
image

3.3 解決使用Invoke執行方法時參數表的問題

此時若是想使用invoke調用該Method對象,須要給出該對象須要的參數,即解決Method對象.invoke(this,?)中的?

3.3.1 基礎版

經過自動識別方法的參數表須要的參數類型以及參數類型的順序,就能解決問題②,即?
經過自動封裝請求頭中的數據,就能解決問題③,即代碼降重,關於圖中的第五步操做——自動解析與封裝請求頭數據的進一步說明 點擊這裏瞭解
跳轉連接中能說明清楚圖中第五步對應實際開發中的哪一個場景需求,又是如何實現的
image

3.3.2 基礎版存在的問題

現狀分析
上圖解決了問題②和③,可是裏面存在一個問題:
對方法參數表中參數類型的判斷只侷限於請求頭、響應頭、指定路徑下的實體類,若是方法的參數表裏存在字符串或者需求的是其餘路徑下的實體類就會出問題

具體說明
其餘路徑:第四步判斷是否爲實體類時藉助了常量 "com.javasm.entity",若是咱們傳遞的實體類對象是vo類或者包名叫bean而不是entity,那就會出問題
存在字符串或者其餘數據類型:方法需求直接傳入一個或多個字符串數據或者其餘數據類型,因爲這種狀況沒有寫在循環判斷中,就會致使整個object數組元素與參數表對不上

解決辦法
針對存在字符串或者其餘數據類型:目前沒辦法把這部分完美解決,須要spring的四個包進行輔助,這些包的底層機制目前也還不清楚
針對路徑的狀況,須要JAVA動態識別方法中參數表須要的實體對象的全類名,獲取的具體實現原理 點擊這裏瞭解

3.3.3 正式版

下面給出正式版相比基礎版的改動,其餘沒給出的與基礎版一致,這些改動解決了實體類的路徑問題。
父類servlet
①設置父類爲泛型類

②聲明一個Class對象 用於儲存當前servlet類聲明的泛型類型
具體到應該聲明一個對象仍是一個數組得看父類聲明泛型時設置了幾個泛型,一般設置爲1個,最多設置2個。
若是隻設置了一個泛型,那麼此時子類的泛型個數就是一個,Class對象設置爲一個對象便可
若是設置了多個泛型,那麼此時子類的泛型個數就是多個,此時Class對象就要設置爲數組,而且下方對Class數組的使用就要逐個取出分別判斷

③建立一個構造函數,在裏面編寫獲取servlet聲明的泛型的代碼
也能夠建立一個非靜態代碼塊,總之要求獲取servlet聲明的泛型的代碼要在整個servlet類執行時就執行
image

④上圖代碼執行完畢後就能得到servlet在聲明泛型時的具體類型,此時就能對Class對的全類名進行一個斷定,若是相同,說明此時Class對指向的就是一個實體類而不是別的東西,此時就能實現條件都不知足時提示「你這方法須要的參數既不是請求頭又不是響應頭還不是實體類,我這個servlet封裝搞不定這種複雜狀況,方法中止」
image

子類Servlet
子類繼承父類時要聲明泛型的實際類型,能夠聲明多個,但通常最多聲明2個,就好比繼承Map集合時聲明K和V的數據類型,總之要包含子類中各類方法使用時的全部數據類型
image

4 封裝響應代碼 實現代碼降重

上面的代碼實現了自動讀取請求參數type,而後執行對應的方法,而且在方法的參數表須要傳入實體對象時,自動建立一個實體對象給到方法。
如今須要作的就是當servlet調用的方法執行完畢時,對前端的響應。
以前對前端的響應都是直接在各個子servlet下進行,各個子Servlet的響應方法都同樣,這無疑是重複代碼,所以能夠放在父類servlet下統一編寫。

子類Servlet
首先在子類Servlet的方法中,執行完畢後根據需求能夠分別請求轉發、重定向、異步請求,根據數據類型跳轉能夠分爲頁面、servlet、json數據,根據方法需求返回
返回時根據"響應類型:響應的數據"手動拼接
image

父類Servlet
在父類Servlet編寫的是全部servlet都能拿到的代碼,代碼的目的簡單來講就是判斷。
使用method對象.invoke以後得到的是一個Object對象
image

此時執行響應結果時,須要先把傳入的object強制轉換爲字符串,再進行後面的判斷
image

5 封裝響應代碼 封裝方式 - 註解

上面給出了響應代碼的封裝,如今更進一步,基於註解去封裝響應代碼

5.1 以前的版本存在的問題

以前的版本在使用時要求方法返回時要在原數據前加上響應類型,約定請求轉發加 f 、重定向加 r 、AJAX加 a
若是編寫方法時不按照這個要求來作,就會產生問題,而實際開發中,這種對於返回類型的要求很不友好,很容易被忽略
image

5.2 解決辦法

①建立註解類。

  1. 設置註解要加在serlvet類中的方法上
  2. 設置註解生成器爲全週期
  3. 根據業務狀況設置默認響應方式,若是前端基於VUE框架,那麼基本都是AJAX
    image

②註解所能選擇的值則放在枚舉類中,這樣數據的安全性就更高了。

  1. 設置枚舉的值以區分響應的方式(轉發、重定向、out)
  2. 注意枚舉的值所有都要大寫
    image

經過使用註解,能將響應方法的添加轉移到註解上,這樣方法的響應方式與方法的返回值實現瞭解耦
可是如今的問題是編寫方法時忘記加上註解了,就會致使沒法響應。
這種狀況目前沒辦法解決,由於當前的封裝手段只能儘可能保證數據的完整性,不能檢測是否添加了註解,或者添加的是不是須要添加的註解

5.3 子類Servlet上的變化

原來:
image

如今:
image

5.4 父類Servlet上的變化

image

相關文章
相關標籤/搜索