引數校驗與LomBok的使用
1、引數校驗機制
在 SpringBoot 中如何接收前端傳送過來的引數,並對其進行驗證是否符合要求,是否合法是非常非常重要的;對於 Web 開發來講,引數合法的驗證之所以如此重要有兩個原因,一是對於服務端開發者,如果引數校驗寫的足夠規範,是可以大大提高前後端開發的效率;二是保護 Web 裡面的機密資料是非常重要的,因此對引數校驗一定要有深刻的認知。
在日常工作中,我們經常會為了省事兒直接在控制器中寫大量的校驗程式碼,這是不對的,因為控制器主要是承接是檢視層和Module或者服務層之間的一道橋樑,它不是用來編寫主要的業務邏輯也不是寫大量的校驗程式碼的;針對於複雜的業務系統來講,校驗程式碼是非常複雜,甚至可能寫到上百行,因此不建議在控制器中寫校驗程式碼,此外一定不要再控制器中寫業務邏輯。
我們要進行引數校驗,首先要能在控制器中獲取到前臺傳過來的引數,引數主要分為兩大類,一類是通過 URL 傳遞過來的引數,一類是通過 POST 請求的 body 傳遞過來的引數。通過 URL 傳遞的引數又分為兩類,一種是寫在 URL 路徑中,例如@GetMapping("/test/param"),另一種是查詢查詢,例如@GetMapping("/test?param=xxx");
2、獲取URL路徑中的引數和查詢引數
2.1 接收URL的路徑引數
路徑引數就是直接在URL中,使用 "/" 後面拼接起來的引數變數,使用 @PathVariable 註解來進行接收
@GetMapping("/test1/{id}")public String test1(@PathVariable Integer id) { System.out.println("路徑引數" + id); return "Gabriel"; }
接收結果:
注意:這裡的{id} 必須要和形參中的 Integer id 保持一致,如果 @GetMapping("/test1/id") 這裡是 id, 但是在形參中是 Integer id1,也是無法接收到的;如果這兩個地方的名稱不一樣,還需要接收的話可以使用下面的寫法:
@GetMapping("/test1/{id1}") publicString test1(@PathVariable(name = "id1") Integer id) { System.out.println("路徑引數" + id); return "Gabriel"; }
2.2 接收URL的查詢引數
查詢引數,也就是在 URL 中直接使用 ? 拼接後的變數,使用 @RequestParam 註解來接收查詢引數
@GetMapping("/test1/{id1}") public String test1(@PathVariable(name = "id1") Integer id, @RequestParam String name) { System.out.println("路徑引數" + id + ": name: " + name); return "Gabriel"; }
接收結果
2.3 接收資料傳輸物件DTO
我們傳送一些簡單的引數的時候,可以通過使用這些 key-value 形式的 GET 方式傳送,但是如果傳送大量的比較複雜的資料的時候,例如下訂單的情況,那麼通常這種使用 POST 請求;POST是不能直接使用瀏覽器傳送的,需要藉助 Postman 傳送(勾選 body -- raw -- JSON)
@PostMapping("/test1/{id1}") public String test1( @PathVariable(name = "id1") Integer id, @RequestParam String name, @RequestBody Map<String, Object> person) { System.out.println("路徑引數: " + id + ": name: " + name); return "Gabriel"; }
接收結果
這裡需要注意的是,在接收的時候引數的時候需要使用的是 @RequestBody 註解;接收的型別可以使用 Map型別,但是由於這裡建議使用的方式是定義一個類,專門用來接收這個物件。這就是我們說的資料傳輸物件 DTO
建立DTO
@Setter
@Getter
public class PersonDTO { private String name; private Integer age; }
接收引數
@PostMapping("/test1/{id1}") public String test1( @PathVariable(name = "id1") Integer id, @RequestParam String name, @RequestBody PersonDTO personDTO) { System.out.println("路徑引數: " + id + ": name: " + name + " person: " + personDTO); return "Gabriel"; }
3、Lombok 的使用
3.1 引入 Lombok 依賴
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>
3.2 通過 Lombok 可以快速生成 Getter 和 Setter 方法
@Getter @Setter @ToString public class PersonDTO { private String name; private Integer age; }
如果有成員變數是被 final 修飾的話,是不會生成 Setter 方法,只會生成 Getter
3.2 Lombok 中建構函式的註解
1.@AllArgsConstructor 通過該註解可以生成一個全部變數的建構函式
2.@NoArgsConstructor 通過該註解可以生成一個無參的建構函式
3. @RequiredArgsConstructor 通過該註解可以生成一個成員不能為空的所有的建構函式
@NonNull 用於宣告該變數不能為空
@Getter @Setter //@AllArgsConstructor 所有成員變數的構造方法 @RequiredArgsConstructor // 不為空的變數的構造方法 @NoArgsConstructor // 空構造方法 public class PersonDTO { @NonNull // name 不能為空 private String name; @NonNull // age 不能為空 private Integer age; }
3.2 @Builder 構造器模式
通過 @Builder 構造物件
@Builder public class PersonDTO { private String name; private Integer age; } @PostMapping("/test1/{id1}") public String test1( @PathVariable(name = "id1") Integer id, @RequestParam String name, @RequestBody PersonDTO personDTO) { PersonDTO gabriel = PersonDTO.builder().name("Gabriel").age(18).build(); System.out.println(gabriel); return "Gabriel"; }
注意:
1. 如果使用了 @Builder 註解,就不能使用無參構造函數了去構建物件,也不能使用 Setter 方法賦值,只能使用 Builder 方式去構建物件
原因:@Builder 會為Bean生成一個 private 的無參建構函式
2. 如果使用了 @Builder 註解,還想使用無參建構函式,則必須顯示宣告一個無參建構函式
@Builder @Setter @NoArgsConstructor public class PersonDTO { private String name; private Integer age; }
3. 如果使用 Builder 模式構建了物件,並且直接返回前端是會報錯的,如下示例:
@PostMapping("/test1/{id1}") public PersonDTO test1( @PathVariable(name = "id1") Integer id, @RequestParam String name, @RequestBody PersonDTO personDTO) { PersonDTO gabriel = PersonDTO.builder().name("gabriel").age(18).build(); return gabriel; }
呼叫後報錯:
常原因:org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class com.gx.missyou.dto.PersonDTO 2020-08-04 20:29:33.037 WARN 10576 --- [nio-8080-exec-3] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class com.gx.missyou.dto.PersonDTO]
原因:因為使用 @Builder 註解後,Bean是沒有 Getter 方法的,但是序列化一定是需要有 Getter 方法才可以,因此要解決該問題,需要新增 Getter 方法
@Builder @Getter public class PersonDTO { private String name; private Integer age; }
4. 使用 @Validated 註解進行引數校驗
使用 Hibernate-Validated 進行校驗,要使用則必須在類上新增 @Validated 註解,否則是不會生效的
@Max(10) 最大為10
@RestController @Validated @RequestMapping("/banner") public class BannerController { @Autowired private ISkill iSkill; @PostMapping("/test1/{id1}") public PersonDTO test1( // @PathVariable(name = "id1") @Max(10) Integer id, // @PathVariable(name = "id1") @Max(value = 10, message = "最大不能超過10哦") Integer id, @PathVariable(name = "id1") @Range(min = 1, max = 8, message = "範圍超過了") Integer id, @RequestParam @NotBlank String name, @RequestBody PersonDTO personDTO) { PersonDTO gabriel = PersonDTO.builder().name("gabriel").age(18).build(); return gabriel; }
4.2 如何在自定義的 DTO 進行驗證
在 DTO 類上新增校驗
@Builder @Getter public class PersonDTO { @Length(min = 3, max = 8, message = "姓名長度必須在3-8之間") private String name; private Integer age; }
在 Controller 中的形參中的 HTTP body 的引數中新增 @Validated, 如果不新增該註解,則是不會生效的
4.2 如何Http body 中的引數進行級聯校驗
如果在一個 DTO 還巢狀一個 DTO, 那麼要如何進行校驗,需要在巢狀的 DTO上新增一個 @Valid 的註解
@Builder @Getter public class PersonDTO { @Length(min = 3, max = 8, message = "姓名長度必須在3-8之間") private String name; @Max(120) private Integer age; @Valid private SchooleDTO schooleDTO; }
SchoolDTO
@Getter @Setter public class SchoolDTO { @Min(4) private String schoolName; }