原創不易,未經允許,請勿轉載,
博客主頁:https://blog.csdn.net/qq_43058685?spm=1001.2014.3001.5343
如果只想查看注解,請跳到文章末尾部分
文章目錄
- 簡介
- 專案創建
- 傳統的檢驗方式
- 使用JSR303
- 自定義錯誤資訊
- 錯誤資訊的獲取與回應
- 錯誤例外型別補充
- 分組校驗
- 自定義校驗
- 常用注解匯總
簡介
在前后端進行資料互動中,在前端把資料傳送到后端前,一般會先進行校驗一次,校驗成功之后,才把資料發送到后端,但是我們在服務端還得在對資料進行一次校驗,因為請求資料發送的鏈接很容易獲取,可以不經過前端界面,使用postman等工具直接向后臺發送資料,這就可能造成發送的資料是不合法的情況,
專案創建
首先創建一個springboot專案
使用的springboot版本為:(本文代碼以該版本為準,不同版本springboot,在下面內容會出現一些差異)
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
引入如下依賴
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
</dependencies>
這個作標在新一點的springboot版本中,需要單獨引入,在老版本是默認引入的,這個是用來引入對jsr303注解的支持,
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
接著創建一個Java Bean
package cn.jxj4869.demo.entity;
import lombok.Data;
import javax.validation.constraints.NotNull;
@Data
public class User {
@NotNull
private Integer id;
private String username;
private String password;
private String email;
}
回傳型別的JavaBean
package cn.jxj4869.demo.entity;
import java.util.HashMap;
public class R extends HashMap<String, Object> {
private static final long serialVersionUID = 1L;
public R() {
put("code", 0);
put("msg", "success");
}
public static R error(int code, String msg) {
R r = new R();
r.put("code", code);
r.put("msg", msg);
return r;
}
public static R ok(String msg) {
R r = new R();
r.put("msg", msg);
return r;
}
public R put(String key, Object value) {
super.put(key, value);
return this;
}
}
創建一個controller,
index方法用來跳轉到首頁,
package cn.jxj4869.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
@Controller
public class UserController {
@RequestMapping("/")
public String index(){
return "index";
}
}
首頁代碼放到resources/templates目錄下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
div{
margin-top: 50px;
}
</style>
</head>
<body>
<div>
新增表單
<br><br>
<form method="post">
<label>用戶名</label>
<input type="text" name="username"/>
<br>
<label>密碼</label>
<input type="text" name="password"/>
<br>
<label>郵箱</label>
<input type="email" name="email"/>
<br>
<input type="submit" value="提交"/>
</form>
</div>
<div>
更新表單
<br><br>
<form method="post">
<input type="hidden" name="id" value="1">
<label>用戶名</label>
<input type="text" name="username"/>
<br>
<label>密碼</label>
<input type="text" name="password"/>
<br>
<label>郵箱</label>
<input type="email" name="email"/>
<br>
<input type="submit" value="提交"/>
</form>
</div>
</body>
</html>
傳統的檢驗方式
要在后端進行資料校驗,傳統的校驗方式在controller層接受資料后,按照要求對資料進行校驗
比如要接收一個user bean物件,
現在要對user物件中的username屬性進行非空校驗,password屬性進行非空校驗和長度校驗,
@PostMapping("/user")
@ResponseBody
public R user1(User user) throws Exception {
if(StringUtils.isEmpty(user.getUsername())) {
return R.error(400,"username不能為空");
}
if(StringUtils.isEmpty(user.getPassword())||user.getPassword().length()>8||user.getPassword().length() <4) {
return R.error(400,"password無效");
}
return null;
}
如果有多個方法都需要接受user物件, 而且要校驗的屬性可能不止username和password這兩個屬性,如果每個方法里面都采用上面這種校驗方式的話,代碼就會很臃腫,而且不好維護,當改變了userbean的屬性,或者對校驗規則進行修改后,就得對所有的校驗代碼進行更新, 這是一件工程量很大的事,
使用JSR303
為了解決上述問題,我們可以使用JSR303提供的注解進行校驗,
JSR是Java Specification Requests的縮寫,意思是Java 規范提案,JSR303也就是第303號提案,
使用JSR303的方法很簡單,例如上面的需求,我們只需要在user的屬性上加上注解即可,
步驟如下:
1、給Bean添加校驗注解,一般是在 javax.validation.constraints這個包下,也還有一些是hibernate提供的,
2、開啟校驗功能@Valid,
3、當校驗失敗的時候,會拋出org.springframework.validation.BindException例外,
常用的校驗注解在文末
package cn.jxj4869.demo.entity;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotNull;
@Data
public class User {
private Integer id;
@NotBlank
private String username;
@NotBlank
@Length(min = 4,max = 8)
private String password;
private String email;
}
然后在controller里面的方法上,加上@Valid注解即可
@PostMapping("/user2")
@ResponseBody
public R user2(@Valid User user) throws Exception {
System.out.println(user);
return null;
}
當校驗失敗后,會出現如下錯誤,并且會給出默認的提示資訊,

自定義錯誤資訊
那這個錯誤資訊是怎么來的呢,
進入@NotNULL注解的代碼里面
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Repeatable(List.class)
@Documented
@Constraint(validatedBy = { })
public @interface NotBlank {
String message() default "{javax.validation.constraints.NotNull.message}";
............
............
............
}
會有一個message屬性,顯然就是指定錯誤的提示內容,而這些錯誤提示是在一個叫validationMessages.properties的檔案中,用idea的搜索工具可以找到,雙擊shift鍵打開搜索,
發現有這么多validationMessages.properties的檔案,而且支持國際化,

打開validationMessages_zh.properties,可以看到里面定義了這么多的提示,而錯誤提示就是從這檔案中獲取的,

如果我們不想用默認的校驗提示資訊的話,可以自己指定,
指定message的值即可,
@NotBlank(message = "用戶名不能為空")
private String username;

錯誤資訊的獲取與回應
當校驗出錯時,會默認回傳一個錯誤界面,或者回傳錯誤提示的json資料,但默認提供的顯然不是我們想要的,如果可以拿到錯誤資訊,那我們就能自定義相應資料了,
拿到錯誤資訊的方式也很簡單,只要在方法中加上BindingResult result這個引數,錯誤資訊就會封裝這里面,
@PostMapping("/user2")
@ResponseBody
public R user2(@Valid User user, BindingResult result) throws Exception {
System.out.println(user);
if(result.hasErrors()) { //判斷是否有錯誤
Map<String,String> map = new HashMap<>();
//1、獲取校驗的錯誤結果
result.getFieldErrors().forEach((item)->{
//FieldError 獲取到錯誤提示
String message = item.getDefaultMessage();
//獲取錯誤的屬性的名字
String field = item.getField();
map.put(field,message);
});
return R.error(400,"提交的資料不合法").put("data",map);
}
// 若沒有錯誤,則進行接下去的業務操作,
return null;
}
不過不推薦上面這種方式,理由同上,當校驗的地方多了,每個方法里面都加上這么個例外處理,會讓代碼很臃腫,
不知道你們是否還記得,springmvc里面有個全域的例外處理,我們可以自定義一個例外處理,在這里面統一處理例外,
統一處理BinException,這樣就可以不用在controller中去處理錯誤資訊了,
package cn.jxj4869.demo.execption;
import cn.jxj4869.demo.entity.R;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;
@RestControllerAdvice(basePackages = "cn.jxj4869.demo.controller")
public class MyExceptionControllerAdvice {
@ExceptionHandler(value = BindException.class)
public R handleVaildException(BindException e) {
Map<String,String> map = new HashMap<>();
//1、獲取校驗的錯誤結果
e.getFieldErrors().forEach((item)->{
//FieldError 獲取到錯誤提示
String message = item.getDefaultMessage();
//獲取錯誤的屬性的名字
String field = item.getField();
map.put(field,message);
});
return R.error(400,"提交的資料不合法").put("data",map);
}
}
錯誤例外型別補充
校驗出錯的時候,會拋出兩種例外
-
org.springframework.validation.BindException使用
@Valid注解進行校驗的時候拋出的 -
org.springframework.web.bind.MethodArgumentNotValidException使用
@validated校驗的時候拋出的
在例外捕獲中加入下面這個
@ExceptionHandler(value= MethodArgumentNotValidException.class)
public R handleVaildException(MethodArgumentNotValidException e){
BindingResult bindingResult = e.getBindingResult();
Map<String,String> map = new HashMap<>();
bindingResult.getFieldErrors().forEach((fieldError)->{
map.put(fieldError.getField(),fieldError.getDefaultMessage());
});
return R.error(400,"提交的資料不合法").put("data",map);
}
分組校驗
在不同業務場景下,校驗規則是不一樣的,比如user物件中id這個屬性,在新增的時候,這個屬性是不用填的,要為null,但是在修改的時候,id屬性是不能為null的,
可以用注解中的groups屬性來指定,在什么場合下使用改注解
@Documented
@Constraint(validatedBy = { })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Repeatable(List.class)
public @interface NotBlank {
Class<?>[] groups() default { };
........
}
首先定義兩個介面AddGroup和UpdateGroup,不需要做任何實作
package cn.jxj4869.demo.valid;
public interface UpdateGroup {
}
package cn.jxj4869.demo.valid;
public interface AddGroup {
}
在user中指定group,
- id屬性在AddGroup的時候,要為null,在UpdateGroup的時候不能為null
- username屬性在AddGroup和Update的時候,都要進行校驗,不能為空,
- password屬性,當校驗的時候指定分組的話,會不起作用,因為沒有給它指定校驗的分組
package cn.jxj4869.demo.entity;
import cn.jxj4869.demo.valid.AddGroup;
import cn.jxj4869.demo.valid.UpdateGroup;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Null;
@Data
public class User {
@Null(groups = {AddGroup.class})
@NotNull(groups = {UpdateGroup.class})
private Integer id;
@NotBlank(message = "用戶名不能為空",groups = {AddGroup.class,UpdateGroup.class})
private String username;
@NotEmpty
private String password;
private String email;
}
在controller中用@Validated注解,指定校驗的分組
@PostMapping("/user3")
@ResponseBody
public R user3(@Validated(UpdateGroup.class) User user) {
System.out.println(user);
return null;
}
結果如下圖所示,因為password屬性沒有指定校驗的分組,所以在校驗的時候,都不會對它進行合法性檢查,


自定義校驗
當提供的注解不能滿足我們需求的時候,可以自定義注解,
例如我們現在給user新加一個屬性status,并要求這個屬性的值只能是0或者1,
新建一個@StatusValue注解,
根據jsr303的規范,校驗注解得有三個屬性,
message:用來獲取錯誤提示的groups:指定校驗分組的,- payload:可以自定義一些負載資訊
使用@Constraint注解指定該注解的校驗器
package cn.jxj4869.demo.valid;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Documented
@Constraint(validatedBy = { StatusValueConstraintValidator.class })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@interface StatusValue {
String message() default "{cn.jxj4869.valid.StatusValue.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
int[] value() default { };
}
自定義校驗器
需要實作ConstraintValidator這個介面,第一個泛型是表示要校驗哪個注解,第二個泛型是要校驗的資料的型別,
initialize是初始化方法isValid校驗方法,判斷是否校驗成功
package cn.jxj4869.demo.valid;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;
public class StatusValueConstraintValidator implements ConstraintValidator<StatusValue,Integer> {
private Set<Integer> set = new HashSet<>();
//初始化方法
@Override
public void initialize(StatusValue constraintAnnotation) {
int[] value = constraintAnnotation.value();
for (int val : value) {
set.add(val);
}
}
/**
* 判斷是否校驗成功
* @param value
* @param context
* @return
*/
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return set.contains(value);
}
}
最后在resources目錄下添加一個ValidationMessages.properties檔案
用來指定錯誤資訊,
cn.jxj4869.valid.StatusValue.message=必須提交指定的值
UserBean
@Data
public class User {
@Null(groups = {AddGroup.class})
@NotNull(groups = {UpdateGroup.class})
private Integer id;
@NotBlank(message = "用戶名不能為空",groups = {AddGroup.class,UpdateGroup.class})
private String username;
@NotEmpty
private String password;
private String email;
@StatusValue(value = {0,1},groups = {AddGroup.class,UpdateGroup.class})
private Integer status;
}


常用注解匯總
| 注解 | 功能 |
|---|---|
| @Null | 物件必須為null |
| @NotNull | 物件必須不為null,無法檢查長度為0的字串 |
| @NotBlank | 字串必須不為Null,且去掉前后空格長度必須大于0 |
| @NotEmpty | 字串必須非空 |
| @Length(min = 1,max = 50) | 字串必須在指定長度內 |
| @Range(min = 0,max = 100) | 必須在指定范圍內 |
| @AssertTrue | 物件必須為true |
| @AssertFalse | 物件必須為false |
| @Max(Value) | 必須為數字,且小于或等于Value |
| @Min(Value) | 必須為數字,且大于或等于Value |
| @DecimalMax(Value) | 必須為數字( BigDecimal ),且小于或等于Value,小數存在精度 |
| @DecimalMin(Value) | 必須為數字( BigDecimal ),且大于或等于Value,小數存在精度 |
| @Digits(integer,fraction) | 必須為數字( BigDecimal ),integer整數精度,fraction小數精度 |
| @Size(min,max) | 物件(Array、Collection、Map、String)長度必須在給定范圍 |
| 字串必須是合法郵件地址 | |
| @Past | Date和Calendar物件必須在當前時間之前 |
| @Future | Date和Calendar物件必須在當前時間之后 |
| @Pattern(regexp=“正則”) | 字串滿足正則運算式的值 |
拒絕白嫖從一鍵三連開始!
原創不易,未經允許,請勿轉載,
博客主頁:https://blog.csdn.net/qq_43058685?spm=1001.2014.3001.5343
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/266662.html
標籤:java
下一篇:springboot郵件發送
