學(xué)習(xí)記錄之Spring MVC
Spring MVC的作用
????Spring MVC框架主要解決了接收請求、響應(yīng)結(jié)果的相關(guān)問題。
開發(fā)環(huán)境
在使用Spring MVC框架時,需要在項目中添加`spring-webmvc`的依賴項。
如果使用的是Spring Boot工程,只需要添加`spring-boot-starter-web`依賴項即可。
> 說明:在`spring-boot-starter-web`依賴項中,包含了`spring-boot-starter`,所以,在Spring Boot項目中,只需要將原有`spring-boot-starter`改為`spring-boot-starter-web`即可。
簡單的接收請求
通常,會使用“控制器”組件來接收請求,這類組件通常使用`Controller`作為類名的后綴,例如類名為`CategoryController`、`BrandController`等。
控制器組件必須添加`@Controller`注解才會被框架視為控制器,才可以用于接收請求、響應(yīng)結(jié)果。
在Spring MVC中,當(dāng)需要接收請求時,只需要在控制器中:
- 自定義處理請求的方法
- 在方法上使用`@RequestMapping`系列注解配置請求路徑
關(guān)于處理請求的方法:
- 訪問權(quán)限:應(yīng)該是`public`
- 返回值類型:當(dāng)返回值類型為`String`時,表示返回“視圖”的名稱,這不是前后端分離的做法;當(dāng)使用了“響應(yīng)正文”的模式后,返回的字符串將作為“正文”響應(yīng)到客戶端,這是前后端分離的做法
- 方法名稱:自定義
關(guān)于響應(yīng)正文
當(dāng)處理請求的方法是響應(yīng)正文的,則方法的返回值會響應(yīng)到客戶端。
在處理請求的方法上添加`@ResponseBody`,則此方法響應(yīng)的方式就是響應(yīng)正文的。
在控制器類上添加`@ResponseBody`,則此**控制器類中所有方法**響應(yīng)的方式都是響應(yīng)正文的。
更**推薦**在控制器類上使用`@RestController`,它同時使用`@Controller`和`@ResponseBody`作為元注解,所以,同時具有這2個注解的效果!
附:`@RestController`源代碼:
關(guān)于@RequestMapping
此注解的主要作用是配置“請求路徑”與“處理請求的方法”的映射關(guān)系。
此注解還可以添加在控制器類上,例如:
則此類中任何一個處理請求的路徑都必須以此為前綴,例如:
在配置路徑時,其實,路徑值并不需要使用`/`作為第1個字符,除非路徑值只有`/`這1個字符,否則,配置值(字符串)兩端的`/`都是可以無視的,最終拼接出來的完整URL會自動在中間添加`/`。
**推薦**每個配置值都使用`/`作為第1個字符(盡管可以不寫)。
在`@RequestMapping`注解中,還有`method`屬性,可以限制請求方式(GET / POST等),其語法大概是:
@RequestMapping(value = "/add-new", method = RequestMethod.POST)
以上配置表示“只允許使用POST方式提交請求”,如果使用其它請求方式,將出現(xiàn)405錯誤!
在Spring MVC框架中,定義一系列的限制請求方式的注解,例如:
- `@GetMapping`:將請求方式限制為GET,除了不能添加在類上,其它用法與`@RequestMapping`相同
- `@PostMapping`:將請求方式限制為POST,除了不能添加在類上,其它用法與`@RequestMapping`相同
- 其它
在開發(fā)實踐中,控制器類上使用`@RequestMapping`,在方法上使用`@GetMapping`或`@PostMapping`,通常,以“獲取數(shù)據(jù)”為主要目的的請求應(yīng)該使用`@GetMapping`(例如查看訂單列表、查看商品詳情),否則,使用`@PostMapping`。
關(guān)于注解的源代碼
每個注解的源代碼中,其元注解`@Target`表示此注解可以添加在哪個位置,例如:
則表示此注解可以添加在`TYPE`(類)上,也可以添加在`METHOD`(方法)上。
在注解的內(nèi)部,源代碼例如:
以上`value`是注解中可配置的屬性,`String[]`表示此屬性的值類型,`default {}`表示此屬性的默認值是空數(shù)組。
每個注解的`value`屬性都是默認屬性,在配置時,如果只配置這1個屬性的值,并不需要顯式的添加此屬性名稱,例如:
@RequestMapping({"/delete"})
和
以上2種配置是完全等效的!
如果屬性的值類型是數(shù)組類型的,且如果需要配置的值只有1個時,可以不使用大括號框住值,例如:
和
以上2種配置是完全等效的!
所以,關(guān)于`@RequestMapping`的`value`屬性,以下4種配置是完全等效的:
另外,在Spring系列框架的注解中,經(jīng)常出現(xiàn)`@AliasFor`注解,例如:
以上代碼表示`value`和`path`是等效的!
接收請求參數(shù)
在Spring MVC中,可以將“請求參數(shù)”直接設(shè)計為“處理請求的方法的參數(shù)”,在方法體內(nèi)部可以直接使用。
以“增加品牌”為例,客戶端需要提交的數(shù)據(jù)有:
- 名稱
- 拼音
- LOGO圖片
- 類別
- 簡介
- 關(guān)鍵詞
- 排序值
則處理請求的方法可以設(shè)計為:
當(dāng)客戶端提交的請求參數(shù)是有效值時(例如`?name=XiaoMi`),處理請求的方法中的參數(shù)也是有效值(就是提交過來的值)。
當(dāng)客戶端只提交了請求參數(shù)對應(yīng)的名稱卻沒有值時(例如`?name=`),處理請求的方法收到的將是長度為0的字符串,如果參數(shù)是`String`類型,則參數(shù)值就是`""`,如果參數(shù)不是`String`類型(例如`Integer`類型),也無法正確的實現(xiàn)轉(zhuǎn)換,則參數(shù)值為`null`。
當(dāng)客戶端沒有提交對應(yīng)的參數(shù)時(無此參數(shù),或名稱不對應(yīng)),處理請求的方法中的參數(shù)值為`null`。更推薦將各請求參數(shù)封裝到自定義的類中,
關(guān)于POJO規(guī)范
所有的POJO類型都應(yīng)該符合以下設(shè)計標(biāo)準:
- 所有屬性都是私有的(`private`)
- 每個屬性都有對應(yīng)的、命名規(guī)范的Setter & Getter
? - 通過專業(yè)的開發(fā)工具生成即可
- 應(yīng)該生成`hashCode()`和`equals()`方法,且保證:2個對象中所有屬性值都相同時,返回相同的`hashCode()`,且這2個對象的`equals()`對比結(jié)果為`true`
? - 通過專業(yè)的開發(fā)工具生成即可
? - 通過IntelliJ IDEA生成時,還有多種代碼模版可選擇,選擇任何一個模版均可
- 實現(xiàn)序列化接口(`Serializable`)
? - 可以不生成序列化版本ID
以上規(guī)范是業(yè)內(nèi)共同認可的,且認為你都會按此規(guī)范來編碼,所以,許多框架都會自動調(diào)用其中的Setter & Getter方式,甚至?xí)褂胉Serializable`來聲明你的對象。
關(guān)于POJO類的命名
在項目中,可能存在多種定位不同的POJO類型,例如某些類型中的屬性是與數(shù)據(jù)庫中的表字段一一對應(yīng)的,這種類型通常稱之“實體”,但是,它并不能解決此種數(shù)據(jù)類型的所有業(yè)務(wù)!
以“用戶”數(shù)據(jù)為例,數(shù)據(jù)表中的字段可能有:
- ID
- 用戶名
- 密碼
- 昵稱
當(dāng)用戶注冊時,涉及的只有:用戶名、密碼、昵稱,并不涉及ID
當(dāng)用戶登錄時,涉及的只有:用戶名、密碼,并不涉及ID和昵稱
用戶登錄時,可能還需要提交“驗證碼”,原本的實體類并沒有
當(dāng)用戶需要修改密碼時,需要提交的是:原密碼、新密碼、確認新密碼,原本的實體類將不可用
所以,實體類型并不適用于每個業(yè)務(wù)!客戶端發(fā)起的不同請求,需要提交的數(shù)據(jù)都是不同的!另外,從數(shù)據(jù)庫中查詢的數(shù)據(jù),也不應(yīng)該使用實體類型,因為每次查詢所需要的數(shù)據(jù)是不同的!
綜合來看,客戶端提交的數(shù)據(jù)與實體可能是不同的,從數(shù)據(jù)庫中查詢的結(jié)果和實體可能也是不同的,所以,在項目中會存在多種定位不同的POJO類型,通常,不同定位的POJO類型,在命名時,應(yīng)該添加一些后綴:
阿里巴巴的建議:
【參考】各層命名規(guī)約:
?數(shù)據(jù)對象:xxxDO,xxx 即為數(shù)據(jù)表名。
?DO = Data Object
數(shù)據(jù)傳輸對象:xxxDTO,xxx 為業(yè)務(wù)領(lǐng)域相關(guān)的名稱。
DTO = Data Transfer Object
展示對象:xxxVO,xxx 一般為網(wǎng)頁名稱。
?VO = View Object
VO = Value Object
POJO 是 DO/DTO/BO/VO 的統(tǒng)稱,禁止命名成 xxxPOJO。
【強制】類名使用 UpperCamelCase 風(fēng)格,必須遵從駝峰形式,但以下情形例外:DO / BO /? DTO / VO / AO?
?正例:MarcoPolo / UserDO / XmlService / TcpUdpDeal / TaPromotion?
反例:macroPolo / UserDo / XMLService / TCPUDPDeal / TAPromotion
在項目中,每種定位的POJO到底使用什么后綴,并沒有標(biāo)準的約定,只要滿足:
不使用`POJO`作為后綴
同種定位的多個類,使用相同的后綴
RESTful
RESTful是一種設(shè)計風(fēng)格(并不是規(guī)范或標(biāo)準)。
RESTful的典型表現(xiàn)為:將id或類似具有“唯一性”的參數(shù)值作為URL的一部分,而不像傳統(tǒng)參數(shù)那樣體現(xiàn)。例如:
以上URL,如果使用傳統(tǒng)做法,通常會設(shè)計為:
注意:不具備“唯一性”的參數(shù)值通常不會設(shè)計為URL的一部分。
Spring MVC很好的支持了RESTful,在配置請求路徑時,可以在路徑中使用`{}`框住自定義的名稱表示占位符,則客戶端在提交請求時,占位符位置的內(nèi)容可以是任意內(nèi)容,在方法的參數(shù)列表中,接收參數(shù)時,需要在參數(shù)前添加`@PathVariable`注解,以表示此參數(shù)值是從URL中的占位符位置獲取的值,例如:
同一個請求路徑中,允許有多個`{}`占位符,則處理請求的方法也應(yīng)該有多個對應(yīng)的參數(shù),每個參數(shù)前都添加`@PathVariable`即可。
如果占位符的名稱與方法的參數(shù)名稱不匹配,還可以在`@PathVariable`注解中配置參數(shù)名稱,注解中配置的名稱應(yīng)該與占位符中的名稱一致,例如:
在使用占位符時,還可以在占位符的名稱之后添加冒號,再添加正則表達式,以匹配到符合格式的URL,例如:
后續(xù),當(dāng)客戶端提交請求時,如果占位符位置的值不符合正則表達式,將響應(yīng)404錯誤!
另外,不沖突的多個正則表達式配置的占位符是允許共存的,例如:
甚至,還可以存在精確匹配的路徑與以上占位符的配置共存,例如:
關(guān)于RESTful,其實,還有一些其它的建議,例如,RESTful推薦根據(jù)要操作數(shù)據(jù)的方式來決定請求方式,例如:
- 請求的目標(biāo)是增加數(shù)據(jù)時,推薦使用`POST`請求方式
- 請求的目標(biāo)是刪除數(shù)據(jù)時,推薦使用`DELETE`請求方式
- 請求的目標(biāo)是修改數(shù)據(jù)時,推薦使用`PUT`請求方式
- 請求的目標(biāo)是查詢數(shù)據(jù)時,推薦使用`GET`請求方式
事實上,在主流的業(yè)務(wù)系統(tǒng)的開發(fā)中,仍只使用`GET`和`POST`請求方式。
最后,關(guān)于RESTful風(fēng)格的URL,如果沒有更好的選擇,推薦設(shè)計為:
- `/數(shù)據(jù)類型的復(fù)數(shù)名稱`表示查詢列表,例如,查詢品牌列表,URL設(shè)計為`/brands`
/數(shù)據(jù)類型的復(fù)數(shù)名稱/{id}`表示根據(jù)id查詢數(shù)據(jù),例如,根據(jù)id查詢品牌詳情,URL設(shè)計為`/brands/{id}
/數(shù)據(jù)類型的復(fù)數(shù)名稱/{id}/命令`表示根據(jù)id操作數(shù)據(jù),例如,根據(jù)id刪除品牌,,URL設(shè)計為`/brands/{id}/delete
@RestController
? ? @RequestMapping("/brands")
? ? public class BrandController {
? ? ? ??
? ? ? ? // http://localhost:8080/brands/9527/delete
? ? ? ? @PostMapping("/{id:[0-9]+}/delete")
? ? ? ? public ... // 處理請求的方法
? ? }
統(tǒng)一處理異常
Spring MVC框架在接收到請求后,會自動調(diào)用處理請求的方法,如果調(diào)用的控制器中方法拋出異常,Spring MVC會捕獲到此異常對象,并嘗試調(diào)用統(tǒng)一處理異常的機制,如果沒有統(tǒng)一處理異常的機制,則會響應(yīng)500錯誤。
關(guān)于統(tǒng)一處理異常,需要自定義類,在類上添加`@ControllerAdvice`注解,添加此注解的類中的注解方法將可以作用于每一次處理請求的過程。
然后,在類中添加處理異常的方法,關(guān)于此方法:
- 訪問權(quán)限:應(yīng)該使用`public`
- 返回值類型:參考控制器中處理請求的方法
? - 假設(shè),需要響應(yīng)某字符串到客戶端,則使用`String`作為返回值類型,并結(jié)合`@ResponseBody`注解一起使用,或者,使用`@RestControllerAdvice`替代`@ControllerAdvice`,則此類中所有方法向客戶端響應(yīng)時,都是響應(yīng)正文的
- 方法名稱:自定義
- 參數(shù)列表:必須至少包含1個異常類型,此參數(shù)就是Spring MVC框架調(diào)用控制器的方法后捕獲的異常,并且,可以按需添加`HttpServletRequest`、`HttpServletResponse`等少量指定類型的參數(shù),不可以添加其它參數(shù)
- 注解:必須添加`@ExceptionHandler`注解
例如,在項目中,可以在`ex.handler`包下創(chuàng)建統(tǒng)一處理異常的類:
完成以上代碼后,無論項目中的任何控制器在處理任何請求時,只要控制器的方法拋出了`ServiceException`(不捕獲等同于拋出),都會執(zhí)行以上方法!
在以上統(tǒng)一處理異常的類中,統(tǒng)一處理異常的方法可以有多個(處理的異常必須不同,但允許存在繼承關(guān)系),通常,建議在此類中添加一個能夠處理任何異常的方法,避免某些異常未被處理導(dǎo)致響應(yīng)500錯誤(用戶不明確錯誤的原因,可能繼續(xù)嘗試錯誤的操作):
響應(yīng)JSON格式的字符串
在Spring MVC框架中,當(dāng)需要向客戶端響應(yīng)JSON格式的字符串時,需要:
- 在配置類上使用`@EnableWebMvc`開啟增強模式
? - 在Spring Boot中不需要,默認已開啟
- 添加`jackson-databind`依賴項
? - 在`spring-boot-starter-web`中已包含
- 處理請求的方法(或處理異常的方法)的返回值類型使用自定義的數(shù)據(jù)類型