您好,登錄后才能下訂單哦!
如何在SpringBoot中使用JSR303對后端數據進行校驗?很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。
首先創建一個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提供的注解進行校驗。
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=“正則”) | 字符串滿足正則表達式的值 |
看完上述內容是否對您有幫助呢?如果還想對相關知識有進一步的了解或閱讀更多相關文章,請關注億速云行業資訊頻道,感謝您對億速云的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。