超詳細(xì)講解SpringBoot參數(shù)校驗(yàn)實(shí)例
使用傳統(tǒng)方式的弊端
public String addUser(User user) { if (user == null || user.getId() == null || user.getAccount() == null || user.getPassword() == null || user.getEmail() == null) { return "對(duì)象或者對(duì)象字段不能為空"; } if (StringUtils.isEmpty(user.getAccount()) || StringUtils.isEmpty(user.getPassword()) || StringUtils.isEmpty(user.getEmail())) { return "不能輸入空字符串"; } if (user.getAccount().length() < 6 || user.getAccount().length() > 11) { return "賬號(hào)長(zhǎng)度必須是6-11個(gè)字符"; } if (user.getPassword().length() < 6 || user.getPassword().length() > 16) { return "密碼長(zhǎng)度必須是6-16個(gè)字符"; } if (!Pattern.matches("^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$", user.getEmail())) { return "郵箱格式不正確"; } // 參數(shù)校驗(yàn)完畢后這里就寫上業(yè)務(wù)邏輯 return "success"; }
這樣做確實(shí)沒(méi)有什么問(wèn)題,而且排版也工整,但代碼太繁瑣了,如果有幾十個(gè)字段要校驗(yàn),那這個(gè)方法里面將會(huì)變得非常臃腫,實(shí)在不夠優(yōu)雅。下面我們就來(lái)講講如何使用最優(yōu)雅的方式來(lái)解決。
引入依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
注解說(shuō)明
注解 | 說(shuō)明 |
---|---|
@AssertFalse | 被注解的元素必須為 false |
@AssertTrue | 被注解的元素必須為 true |
@DecimalMax(value) | 被注解的元素必須是一個(gè)數(shù)字,其值必須小于等于指定的最大值 |
@DecimalMin(value) | 被注解的元素必須是一個(gè)數(shù)字,其值必須大于等于指定的最小值 |
@Digits (integer, fraction) | 被注解的元素必須是一個(gè)數(shù)字,其值必須在可接受的范圍內(nèi) |
@Null | 被注解的元素必須為空 |
@NotNull | 被注解的元素必須不為空 |
@Min(value) | 被注解的元素必須是一個(gè)數(shù)字,其值必須大于等于指定的最大值 |
@Max(value) | 被注解的元素必須是一個(gè)數(shù)字,其值必須小于等于指定的最大值 |
@Size(max, min) | 被注解的元素的長(zhǎng)度必須在指定的范圍內(nèi) |
@Past | 被注解的元素必須是一個(gè)過(guò)去的日期 |
@Future | 被注解的元素必須是一個(gè)未來(lái)的日期 |
@Pattern(value) | 被注解的元素必須符合指定的正則表達(dá)式 |
下面我們以此來(lái)在業(yè)務(wù)中實(shí)現(xiàn)
一、對(duì)實(shí)體類進(jìn)行校驗(yàn)
1、entity
@Data public class User { @NotNull(message = "用戶id不能為空") private Long id; @NotNull(message = "用戶賬號(hào)不能為空") @Size(min = 6, max = 11, message = "賬號(hào)長(zhǎng)度必須是6-11個(gè)字符") private String account; @NotNull(message = "用戶密碼不能為空") @Size(min = 6, max = 11, message = "密碼長(zhǎng)度必須是6-16個(gè)字符") private String password; @NotNull(message = "用戶郵箱不能為空") @Email(message = "郵箱格式不正確") private String email; }
2、controller
@RestController public class UserController { @PostMapping("/addUser") public void addUser(@RequestBody @Valid User user) { //業(yè)務(wù) } }
3、編寫全局統(tǒng)一異常處理
import org.springframework.validation.ObjectError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolationException; import java.util.stream.Collectors; /** * 全局異常處理 * * @author master */ @RestControllerAdvice public class ExceptionConfig { /** * 參數(shù)為實(shí)體類 * @param e * @return */ @ExceptionHandler(value = MethodArgumentNotValidException.class) public String handleValidException(MethodArgumentNotValidException e) { // 從異常對(duì)象中拿到ObjectError對(duì)象 ObjectError objectError = e.getBindingResult().getAllErrors().get(0); // 然后提取錯(cuò)誤提示信息進(jìn)行返回 return objectError.getDefaultMessage(); } /** * 參數(shù)為單個(gè)參數(shù)或多個(gè)參數(shù) * @param e * @return */ @ExceptionHandler(value = ConstraintViolationException.class) public String handleConstraintViolationException(ConstraintViolationException e) { // 從異常對(duì)象中拿到ObjectError對(duì)象 return e.getConstraintViolations() .stream() .map(ConstraintViolation::getMessage) .collect(Collectors.toList()).get(0); } }
然后我們使用apipost測(cè)試
二、針對(duì)單個(gè)參數(shù)進(jìn)行校驗(yàn)
import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import javax.validation.constraints.NotNull; @RestController @Validated public class TestController { @GetMapping("/test") public void test(@NotNull(message = "id不能為空") Integer id) { } }
然后我們使用apipost測(cè)試
三、分組校驗(yàn)
場(chǎng)景:在新增時(shí)我們需要id為空,但修改時(shí)我們又需要id不為空,總不可能搞兩個(gè)類吧,這時(shí)候分組校驗(yàn)的用處就來(lái)了
1、entity
import lombok.Data; import javax.validation.constraints.Email; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; @Data public class User { public interface Insert{ } public interface Update{ } @NotNull(message = "用戶id不能為空",groups = Update.class) @Null(message = "用戶id必須為空",groups = Integer.class) private Long id; private String account; private String password; private String email; }
2、controller
@PostMapping("/add") public void add(@RequestBody @Validated(User.Insert.class) User user) { }
添加時(shí)就用User.Insert.class,修改時(shí)就用User.Update.class
四、自定義分組校驗(yàn)
場(chǎng)景:當(dāng)type為1時(shí),需要參數(shù)a不為空,當(dāng)type為2時(shí),需要參數(shù)b不為空。
1、entity
import com.example.demo.provider.CustomSequenceProvider; import lombok.Data; import org.hibernate.validator.group.GroupSequenceProvider; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.Pattern; @Data @GroupSequenceProvider(value = CustomSequenceProvider.class) public class CustomGroup { /** * 類型 */ @Pattern(regexp = "[A|B]" , message = "類型不必須為 A|B") private String type; /** * 參數(shù)A */ @NotEmpty(message = "參數(shù)A不能為空" , groups = {WhenTypeIsA.class}) private String paramA; /** * 參數(shù)B */ @NotEmpty(message = "參數(shù)B不能為空", groups = {WhenTypeIsB.class}) private String paramB; /** * 分組A */ public interface WhenTypeIsA { } /** * 分組B */ public interface WhenTypeIsB { } }
2、CustomSequenceProvider
import com.example.demo.controller.CustomGroup; import org.hibernate.validator.spi.group.DefaultGroupSequenceProvider; import java.util.ArrayList; import java.util.List; public class CustomSequenceProvider implements DefaultGroupSequenceProvider<CustomGroup> { @Override public List<Class<?>> getValidationGroups(CustomGroup form) { List<Class<?>> defaultGroupSequence = new ArrayList<>(); defaultGroupSequence.add(CustomGroup.class); if (form != null && "A".equals(form.getType())) { defaultGroupSequence.add(CustomGroup.WhenTypeIsA.class); } if (form != null && "B".equals(form.getType())) { defaultGroupSequence.add(CustomGroup.WhenTypeIsB.class); } return defaultGroupSequence; } }
3、controller
@PostMapping("/add") public void add(@RequestBody @Validated CustomGroup user) { }
五、自定義校驗(yàn)
雖然官方提供的校驗(yàn)注解已經(jīng)滿足很多情況了,但還是無(wú)法滿足我們業(yè)務(wù)的所有需求,比如校驗(yàn)手機(jī)號(hào)碼,下面我就以校驗(yàn)手機(jī)號(hào)碼來(lái)做一個(gè)示例。
1、定義校驗(yàn)注解
@Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = PhoneValidator.class) public @interface Phone { String message() default "手機(jī)號(hào)碼格式有誤"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
注:groups和payload是必須要寫的,Constraint是使用哪個(gè)類來(lái)進(jìn)行校驗(yàn)。
2、實(shí)現(xiàn)注解
import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import java.util.regex.Pattern; /** * @author master */ public class PhoneValidator implements ConstraintValidator<Phone, Object> { @Override public boolean isValid(Object telephone, ConstraintValidatorContext constraintValidatorContext) { String pattern = "^1[3|4|5|6|7|8|9]\\d{9}$"; return Pattern.matches(pattern, telephone.toString()); } }
最后直接用到參數(shù)前面或者實(shí)體類變量上面即可。
六、嵌套校驗(yàn)
當(dāng)某個(gè)對(duì)象中還包含了對(duì)象需要進(jìn)行校驗(yàn),這個(gè)時(shí)候我們需要用嵌套校驗(yàn)。
@Data public class TestAA { @NotEmpty(message = "id不能為空") private String id; @NotNull @Valid private Job job; @Data public class Job { @NotEmpty(message = "content不能為空") private String content; } }
七、快速失敗
Spring Validation默認(rèn)會(huì)校驗(yàn)完所有字段,然后才拋出異常??梢酝ㄟ^(guò)配置,開(kāi)啟Fali Fast模式,一旦校驗(yàn)失敗就立即返回。
import org.hibernate.validator.HibernateValidator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.validation.Validation; import javax.validation.Validator; import javax.validation.ValidatorFactory; @Configuration public class FailFastConfig { @Bean public Validator validator() { ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class) .configure() // 快速失敗模式 .failFast(true) .buildValidatorFactory(); return validatorFactory.getValidator(); } }
注意事項(xiàng)
SpringBoot 2.3.x 移除了validation依賴需要手動(dòng)引入依賴。
總結(jié)
非空校驗(yàn)是校驗(yàn)的第一步, 除了非空校驗(yàn),我們還需要做到以下幾點(diǎn):
- 普通參數(shù) - 需要限定字段的長(zhǎng)度。如果會(huì)將數(shù)據(jù)存入數(shù)據(jù)庫(kù),長(zhǎng)度以數(shù)據(jù)庫(kù)為準(zhǔn),反之根據(jù)業(yè)務(wù)確定。
- 類型參數(shù) - 最好使用正則對(duì)可能出現(xiàn)的類型做到嚴(yán)格校驗(yàn)。比如type的值是【0|1|2】這樣的。
- 列表(list)參數(shù) - 不僅需要對(duì)list內(nèi)的參數(shù)是否合格進(jìn)行校驗(yàn),還需要對(duì)list的size進(jìn)行限制。比如說(shuō) 100。
- 日期,郵件,金額,URL這類參數(shù)都需要使用對(duì)于的正則進(jìn)行校驗(yàn)。
- 參數(shù)真實(shí)性 - 這個(gè)主要針對(duì)于 各種Id 比如說(shuō) userId、merchantId,對(duì)于這樣的參數(shù),都需要進(jìn)行真實(shí)性校驗(yàn)
參數(shù)校驗(yàn)越嚴(yán)格越好,嚴(yán)格的校驗(yàn)規(guī)則不僅能減少接口出錯(cuò)的概率,同時(shí)還能避免出現(xiàn)臟數(shù)據(jù),從而來(lái)保證系統(tǒng)的安全性和穩(wěn)定性。
到此這篇關(guān)于超詳細(xì)講解SpringBoot參數(shù)校驗(yàn)實(shí)例的文章就介紹到這了,更多相關(guān)SpringBoot參數(shù)校驗(yàn)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring?Boot中調(diào)用外部接口的3種方式步驟
這篇文章主要給大家介紹了關(guān)于Spring?Boot中調(diào)用外部接口的3種方式步驟,在Spring-Boot項(xiàng)目開(kāi)發(fā)中,存在著本模塊的代碼需要訪問(wèn)外面模塊接口,或外部url鏈接的需求,需要的朋友可以參考下2023-08-08如何使用intellij IDEA搭建Spring Boot項(xiàng)目
這篇文章主要介紹了如何使用intellij IDEA搭建Spring Boot項(xiàng)目,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07java 判斷兩個(gè)對(duì)象是否為同一個(gè)對(duì)象實(shí)例代碼
這篇文章主要介紹了java 判斷兩個(gè)對(duì)象是否為同一個(gè)對(duì)象實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2016-12-12Springboot整合MongoDB的Docker開(kāi)發(fā)教程全解
這篇文章主要介紹了Springboot整合MongoDB的Docker開(kāi)發(fā),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值 ,需要的朋友可以參考下2020-07-07菜鳥(niǎo)學(xué)習(xí)java設(shè)計(jì)模式之單例模式
這篇文章主要為大家詳細(xì)介紹了java設(shè)計(jì)模式之單例模式的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-11-11