欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot如何使用validator框架優(yōu)雅地校驗(yàn)參數(shù)

 更新時間:2025年02月07日 09:39:17   作者:sco5282  
文章介紹了如何使用SpringValidation進(jìn)行參數(shù)校驗(yàn),包括引入依賴、@requestBody和@requestParam參數(shù)校驗(yàn)、統(tǒng)一異常處理、分組校驗(yàn)、嵌套校驗(yàn)、自定義校驗(yàn)、業(yè)務(wù)規(guī)則校驗(yàn)以及@Valid和@Validated的區(qū)別,同時,列舉了常用的BeanValidation和HibernateValidator注解

1、為什么要校驗(yàn)參數(shù)?

在日常的開發(fā)中,為了防止非法參數(shù)對業(yè)務(wù)造成影響,需要對接口的參數(shù)進(jìn)行校驗(yàn),以便正確性地入庫。

例如:登錄時,就需要判斷用戶名、密碼等信息是否為空。雖然前端也有校驗(yàn),但為了接口的安全性,后端接口還是有必要進(jìn)行參數(shù)校驗(yàn)的。

同時,為了校驗(yàn)參數(shù)更加優(yōu)雅,這里就介紹了 Spring Validation 方式。

  • Java API 規(guī)范(JSR303:JAVA EE 6 中的一項(xiàng)子規(guī)范,叫做 Bean Validation)定義了 Bean 校驗(yàn)的標(biāo)準(zhǔn) validation-api,但沒有提供實(shí)現(xiàn)。
  • hibernate validation 是對這個規(guī)范的實(shí)現(xiàn),并增加了校驗(yàn)注解。如:@Email、@Length。

JSR 官網(wǎng)

Hibernate Validator 官網(wǎng)

Spring Validation 是對 hibernate validation 的二次封裝,用于支持 spring mvc 參數(shù)自動校驗(yàn)。

2、引入依賴

如果 spring-boot 版本小于 2.3.x,spring-boot-starter-web 會自動傳入 hibernate-validator 依賴。如果 spring-boot 版本大于等于 2.3.x,則需要手動引入依賴。

<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>8.0.0.Final</version>
</dependency>

對于 web 服務(wù)來說,為防止非法參數(shù)對業(yè)務(wù)造成影響,在 Controller 層一定要做參數(shù)校驗(yàn)的!大部分情況下,請求參數(shù)分為如下兩種形式:

  • POST、PUT 請求,使用 @requestBody 接收參數(shù)
  • GET 請求,使用 @requestParam、@PathVariable 接收參數(shù)

3、@requestBody 參數(shù)校驗(yàn)

對于 POST、PUT 請求,后端一般會使用 @requestBody + 對象 接收參數(shù)。此時,只需要給對象添加 @Validated 或 @Valid 注解,即可輕松實(shí)現(xiàn)自動校驗(yàn)參數(shù)。如果校驗(yàn)失敗,會拋出 MethodArgumentNotValidException 異常。

UserVo :添加校驗(yàn)注解

@Data
public class UserVo {

	private Long id;

    @NotNull
    @Length(min = 2, max = 10)
    private String userName;

    @NotNull
    @Length(min = 6, max = 20)
    private String account;

    @NotNull
    @Length(min = 6, max = 20)
    private String password;
}

UserController :

@RestController
@RequestMapping("/user")
public class UserController {

    @PostMapping("/addUser")
    public String addUser(@RequestBody @Valid UserVo userVo) {
        return "addUser";
    }
}

或者使用 @Validated 注解:

@PostMapping("/addUser")
public String addUser(@RequestBody @Validated UserVo userVo) {
    return "addUser";
}

4、@requestParam、@PathVariable 參數(shù)校驗(yàn)

GET 請求一般會使用 @requestParam、@PathVariable 注解接收參數(shù)。如果參數(shù)比較多(比如超過 5 個),還是推薦使用對象接收。否則,推薦將一個個參數(shù)平鋪到方法入?yún)⒅小?/p>

在這種情況下,必須在 Controller 類上標(biāo)注 @Validated 注解,并在入?yún)⑸下暶骷s束注解(如:@Min )。如果校驗(yàn)失敗,會拋出 ConstraintViolationException 異常

@RestController
@RequestMapping("/user")
@Validated
public class UserController {

    @GetMapping("/getUser")
    public String getUser(@Min(1L) Long id) {
        return "getUser";
    }
}

5、統(tǒng)一異常處理

如果校驗(yàn)失敗,會拋出 MethodArgumentNotValidException 或者 ConstraintViolationException 異常。在實(shí)際項(xiàng)目開發(fā)中,通常會用統(tǒng)一異常處理來返回一個更友好的提示

@RestControllerAdvice
public class ExceptionControllerAdvice {

    @ExceptionHandler({MethodArgumentNotValidException.class})
    @ResponseStatus(HttpStatus.OK)
    public String handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
        BindingResult bindingResult = ex.getBindingResult();
        StringBuilder sb = new StringBuilder("校驗(yàn)失敗:");
        for (FieldError fieldError : bindingResult.getFieldErrors()) {
            sb.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage()).append(", ");
        }
        String msg = sb.toString();
        return "參數(shù)校驗(yàn)失敗" + msg;
    }

    @ExceptionHandler({ConstraintViolationException.class})
    public String handleConstraintViolationException(ConstraintViolationException ex) {
        return "參數(shù)校驗(yàn)失敗" + ex;
    }

}

6、分組校驗(yàn)

在實(shí)際項(xiàng)目中,可能多個方法需要使用同一個類對象來接收參數(shù),而不同方法的校驗(yàn)規(guī)則很可能是不一樣的。這個時候,簡單地在類的字段上加約束注解無法解決這個問題。因此,spring-validation 支持了分組校驗(yàn)的功能,專門用來解決這類問題。

如:保存 User 的時候,userId 是可空的,但是更新 User 的時候,userId 的值必須 >= 1L;其它字段的校驗(yàn)規(guī)則在兩種情況下一樣。這個時候使用分組校驗(yàn)的代碼示例如下:

約束注解上聲明適用的分組信息 groups

6.1、定義分組接口

public interface ValidGroup extends Default {
	// 添加操作
    interface Save extends ValidGroup {}
    // 更新操作
    interface Update extends ValidGroup {}
	// ...
}

為什么要繼承 Default ?下文有。

6.2、給需要校驗(yàn)的字段分配分組

@Data
public class UserVo {
	
    @Null(groups = ValidGroup.Save.class, message = "id要為空")
    @NotNull(groups = ValidGroup.Update.class, message = "id不能為空")
    private Long id;

    @NotBlank(groups = ValidGroup.Save.class, message = "用戶名不能為空")
    @Length(min = 2, max = 10)
    private String userName;

    @Email
    @NotNull
    private String email;
}

根據(jù)校驗(yàn)字段看:

  • id:分配分組:Save、Update。添加時,一定為 null;更新時,一定不為 null
  • userName:分配分組:Save。添加時,一定不能為空
  • email:分配分組:無。即:使用默認(rèn)的分組

6.3、給需要校驗(yàn)的參數(shù)指定分組

@RestController
@RequestMapping("/user")
public class UserController {

    @PostMapping("/addUser")
    public String addUser(@RequestBody @Validated(ValidGroup.Save.class) UserVo userVo) {
        return "addUser";
    }

    @PostMapping("/updateUser")
    public String updateUser(@RequestBody @Validated(ValidGroup.Update.class) UserVo userVo) {
        return "updateUser";
    }
}

測試校驗(yàn)。

6.4、默認(rèn)分組

如果 ValidGroup 接口 不繼承 Default 接口,那么,將無法校驗(yàn) email 字段(未分配分組);

繼承后,ValidGroup 就屬于 Default 類型,即:默認(rèn)分組/所以,可以對 email 校驗(yàn)

7、嵌套校驗(yàn)

必須要用 @Valid 注解

@Data
public class UserVo {

	@NotNull(groups = {ValidGroup.Save.class, ValidGroup.Update.class})
    @Valid
    private Address address;
}

8、自定義校驗(yàn)

8.1、案例一、自定義校驗(yàn) 加密id

假設(shè)我們自定義加密 id(由數(shù)字或者 a-f 的字母組成,32-256 長度)校驗(yàn),主要分為兩步:

8.1.1、自定義約束注解

@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {EncryptIdValidator.class}) // 自定義驗(yàn)證器
public @interface EncryptId {

    // 默認(rèn)錯誤消息
    String message() default "加密id格式錯誤";
    // 分組
    Class<?>[] groups() default {};
    // 負(fù)載
    Class<? extends Payload>[] payload() default {};
}

8.1.2、編寫約束校驗(yàn)器

public class EncryptIdValidator implements ConstraintValidator<EncryptId, String> {

    private static final Pattern PATTERN = Pattern.compile("^[a-f\\d]{32,256}$");

    @Override
    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
        if (value != null) {
            Matcher matcher = PATTERN.matcher(value);
            return matcher.find();
        }
        return true;
    }
}

8.1.3、使用

@Data
public class UserVo {

    @EncryptId
    private String id;
}

8.2、案例二、自定義校驗(yàn) 性別只允許兩個值

UserVo 類中的 sex 性別屬性,只允許前端傳遞傳 M,F(xiàn) 這2個枚舉值,如何實(shí)現(xiàn)呢?

8.2.1、自定義約束注解

@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {SexValidator.class})
public @interface SexValid {


    // 默認(rèn)錯誤消息
    String message() default "value not in enum values";

    // 分組
    Class<?>[] groups() default {};

    // 負(fù)載
    Class<? extends Payload>[] payload() default {};

    String[] value();
}

8.2.2、編寫約束校驗(yàn)器

public class SexValidator implements ConstraintValidator<SexValid, String> {

    private List<String> sexs;

    @Override
    public void initialize(SexValid constraintAnnotation) {
        sexs = Arrays.asList(constraintAnnotation.value());
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
        if (StringUtils.isEmpty(value)) {
            return true;
        }
        return sexs.contains(value);
    }

}

8.2.3、使用

@Data
public class UserVo {

    @SexValid(value = {"F", "M"}, message = "性別只允許為F或M")
    private String sex;

}
```### 8.2.4、測試
```java
@GetMapping("/get")
private String get(@RequestBody @Validated UserVo userVo) {
    return "get";
}

9、實(shí)現(xiàn)校驗(yàn)業(yè)務(wù)規(guī)則

業(yè)務(wù)規(guī)則校驗(yàn) 指 接口需要滿足某些特定的業(yè)務(wù)規(guī)則。舉個例子:業(yè)務(wù)系統(tǒng)的用戶需要保證其唯一性,用戶屬性不能與其他用戶產(chǎn)生沖突,不允許與數(shù)據(jù)庫中任何已有用戶的用戶名稱、手機(jī)號碼、郵箱產(chǎn)生重復(fù)。 這就要求在創(chuàng)建用戶時需要校驗(yàn)用戶名稱、手機(jī)號碼、郵箱是否被注冊;編輯用戶時不能將信息修改成已有用戶的屬性。

最優(yōu)雅的實(shí)現(xiàn)方法應(yīng)該是參考 Bean Validation 的標(biāo)準(zhǔn)方式,借助自定義校驗(yàn)注解完成業(yè)務(wù)規(guī)則校驗(yàn)。

9.1、自定義約束注解

首先我們需要創(chuàng)建兩個自定義注解,用于業(yè)務(wù)規(guī)則校驗(yàn):

  • UniqueUser:表示一個用戶是唯一的,唯一性包含:用戶名,手機(jī)號碼、郵箱
  • NotConflictUser:表示一個用戶的信息是無沖突的,無沖突是指該用戶的敏感信息與其他用戶不重合
@Documented
@Retention(RUNTIME)
@Target({FIELD, METHOD, PARAMETER, TYPE})
@Constraint(validatedBy = UserValidator.UniqueUserValidator.class)
public @interface UniqueUser {

    String message() default "用戶名、手機(jī)號碼、郵箱不允許與現(xiàn)存用戶重復(fù)";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};

}
@Documented
@Retention(RUNTIME)
@Target({FIELD, METHOD, PARAMETER, TYPE})
@Constraint(validatedBy = UserValidator.NotConflictUserValidator.class)
public @interface NotConflictUser {

    String message() default "用戶名稱、郵箱、手機(jī)號碼與現(xiàn)存用戶產(chǎn)生重復(fù)";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};

}

9.2、編寫約束校驗(yàn)器

想讓自定義驗(yàn)證注解生效,需要實(shí)現(xiàn) ConstraintValidator 接口。接口的第一個參數(shù)是 自定義注解類型,第二個參數(shù)是 被注解字段的類,因?yàn)樾枰r?yàn)多個參數(shù),我們直接傳入用戶對象。 需要提到的一點(diǎn)是 ConstraintValidator 接口的實(shí)現(xiàn)類無需添加 @Component 它在啟動的時候就已經(jīng)被加載到容器中了。

public class UserValidator<T extends Annotation> implements ConstraintValidator<T, UserVo> {

    protected Predicate<UserVo> predicate = c -> true;

    @Override
    public boolean isValid(UserVo userVo, ConstraintValidatorContext constraintValidatorContext) {
        return predicate.test(userVo);
    }

    public static class UniqueUserValidator extends UserValidator<UniqueUser>{
        @Override
        public void initialize(UniqueUser uniqueUser) {
            UserDao userDao = ApplicationContextHolder.getBean(UserDao.class);
            predicate = c -> !userDao.existsByUserNameOrEmailOrTelphone(c.getUserName(),c.getEmail(),c.getTelphone());
        }
    }

    public static class NotConflictUserValidator extends UserValidator<NotConflictUser>{
        @Override
        public void initialize(NotConflictUser notConflictUser) {
            predicate = c -> {
                UserDao userDao = ApplicationContextHolder.getBean(UserDao.class);
                Collection<UserVo> collection = userDao.findByUserNameOrEmailOrTelphone(c.getUserName(), c.getEmail(), c.getTelphone());
                // 將用戶名、郵件、電話改成與現(xiàn)有完全不重復(fù)的,或者只與自己重復(fù)的,就不算沖突
                return collection.isEmpty() || (collection.size() == 1 && collection.iterator().next().getId().equals(c.getId()));
            };
        }
    }

}
@Component
public class ApplicationContextHolder implements ApplicationContextAware {

    private static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;
    }

    public static ApplicationContext getContext() {
        return context;
    }

    public static Object getBean(String name) {
        return context != null ? context.getBean(name) : null;
    }

    public static <T> T getBean(Class<T> clz) {
        return context != null ? context.getBean(clz) : null;
    }

    public static <T> T getBean(String name, Class<T> clz) {
        return context != null ? context.getBean(name, clz) : null;
    }

    public static void addApplicationListenerBean(String listenerBeanName) {
        if (context != null) {
            ApplicationEventMulticaster applicationEventMulticaster = (ApplicationEventMulticaster)context.getBean(ApplicationEventMulticaster.class);
            applicationEventMulticaster.addApplicationListenerBean(listenerBeanName);
        }
    }

}

9.3、測試

@RestController
@RequestMapping("/user")
public class UserController {

    @PostMapping("/addUser")
    public String addUser(@RequestBody @UniqueUser UserVo userVo) {
        return "addUser";
    }

    @PostMapping("/updateUser")
    public String updateUser(@RequestBody @NotConflictUser UserVo userVo) {
        return "updateUser";
    }
}

10、@Valid 和 @Validated 的區(qū)別

區(qū)別如下:

11、常用注解

Bean Validation 內(nèi)嵌的注解很多,基本實(shí)際開發(fā)中已經(jīng)夠用了,注解如下:

注解詳細(xì)信息
@Null任意類型。被注釋的元素必須為 null
@NotNull任意類型。被注釋的元素不為 null
@Min(value)數(shù)值類型(double、float 會有精度丟失)。其值必須大于等于指定的最小值
@Max(value)數(shù)值類型(double、float 會有精度丟失)。其值必須小于等于指定的最大值
@DecimalMin(value)數(shù)值類型(double、float 會有精度丟失)。其值必須大于等于指定的最小值
@DecimalMax(value)數(shù)值類型(double、float 會有精度丟失)。其值必須小于等于指定的最大值
@Size(max, min)字符串、集合、Map、數(shù)組類型。被注釋的元素的大小(長度)必須在指定的范圍內(nèi)
@Digits (integer, fraction)數(shù)值類型、數(shù)值型字符串類型。其值必須在可接受的范圍內(nèi)。 integer:整數(shù)精度;fraction:小數(shù)精度
@Past日期類型。被注釋的元素必須是一個過去的日期
@Future日期類型。被注釋的元素必須是一個將來的日期
@Pattern(value)字符串類型。被注釋的元素必須符合指定的正則表達(dá)式

Hibernate Validator 在原有的基礎(chǔ)上也內(nèi)嵌了幾個注解,如下:

注解詳細(xì)信息
@Email字符串類型。被注釋的元素必須是電子郵箱地址
@Length字符串類型。被注釋的字符串的長度必須在指定的范圍內(nèi)
@NotEmpty字符串、集合、Map、數(shù)組類型。 被注釋的元素的長度必須非空
@Range數(shù)值類型、字符串類型。 被注釋的元素必須在合適的范圍內(nèi)

總結(jié)

以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • IDEA2020.1啟動SpringBoot項(xiàng)目出現(xiàn)java程序包:xxx不存在

    IDEA2020.1啟動SpringBoot項(xiàng)目出現(xiàn)java程序包:xxx不存在

    這篇文章主要介紹了IDEA2020.1啟動SpringBoot項(xiàng)目出現(xiàn)java程序包:xxx不存在,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-06-06
  • java實(shí)現(xiàn)延遲/超時/定時問題

    java實(shí)現(xiàn)延遲/超時/定時問題

    這篇文章主要介紹了java實(shí)現(xiàn)延遲/超時/定時問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-04-04
  • Java中的setting和getting使用方法

    Java中的setting和getting使用方法

    為了保障數(shù)據(jù)的安全性,通常將數(shù)據(jù)成員定義為private(封裝或私有化),這樣外部代碼就無法直接訪問這些數(shù)據(jù),只能通過類提供的公共方法來進(jìn)行訪問,這種方法主要包括setter和getter方法,以及構(gòu)造方法,setter方法用于給私有屬性賦值
    2024-09-09
  • spring學(xué)習(xí)教程之@ModelAttribute注解運(yùn)用詳解

    spring學(xué)習(xí)教程之@ModelAttribute注解運(yùn)用詳解

    這篇文章主要給大家介紹了關(guān)于spring學(xué)習(xí)教程之@ModelAttribute注釋運(yùn)用的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起看看吧。
    2017-06-06
  • JPA?通過Specification如何實(shí)現(xiàn)復(fù)雜查詢

    JPA?通過Specification如何實(shí)現(xiàn)復(fù)雜查詢

    這篇文章主要介紹了JPA?通過Specification如何實(shí)現(xiàn)復(fù)雜查詢,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • java使用正則表達(dá)校驗(yàn)手機(jī)號碼示例(手機(jī)號碼正則)

    java使用正則表達(dá)校驗(yàn)手機(jī)號碼示例(手機(jī)號碼正則)

    這篇文章主要介紹了java使用正則表達(dá)校驗(yàn)手機(jī)號碼示例,可校驗(yàn)三個號碼段:13*、15*、18*,大家根據(jù)自己的需要增加自己的號碼段就可以了
    2014-03-03
  • Spring依賴注入的三種方式小結(jié)

    Spring依賴注入的三種方式小結(jié)

    本篇文章主要介紹了Spring依賴注入的三種方式小結(jié),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-08-08
  • Spring AI集成DeepSeek的詳細(xì)步驟

    Spring AI集成DeepSeek的詳細(xì)步驟

    DeepSeek 作為一款卓越的國產(chǎn) AI 模型,越來越多的公司考慮在自己的應(yīng)用中集成,對于 Java 應(yīng)用來說,我們可以借助 Spring AI 集成 DeepSeek,非常簡單方便,本文給大家介紹了Spring AI集成DeepSeek的詳細(xì)步驟,需要的朋友可以參考下
    2025-02-02
  • Spring事件監(jiān)聽機(jī)制之@EventListener實(shí)現(xiàn)方式詳解

    Spring事件監(jiān)聽機(jī)制之@EventListener實(shí)現(xiàn)方式詳解

    這篇文章主要介紹了Spring事件監(jiān)聽機(jī)制之@EventListener實(shí)現(xiàn)方式詳解,ApplicationContext的refresh方法還是初始化了SimpleApplicationEventMulticaster,發(fā)送事件式還是先獲取ResolvableType類型,再獲取發(fā)送監(jiān)聽列表,需要的朋友可以參考下
    2023-12-12
  • 關(guān)于Nacos和Eureka的區(qū)別及說明

    關(guān)于Nacos和Eureka的區(qū)別及說明

    這篇文章主要介紹了關(guān)于Nacos和Eureka的區(qū)別及說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-06-06

最新評論