溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

spring自定義校驗注解ConstraintValidator的示例分析

發(fā)布時間:2021-06-30 14:08:25 來源:億速云 閱讀:445 作者:小新 欄目:開發(fā)技術(shù)

這篇文章主要介紹spring自定義校驗注解ConstraintValidator的示例分析,文中介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們一定要看完!

一、前言

系統(tǒng)執(zhí)行業(yè)務(wù)邏輯之前,會對輸入數(shù)據(jù)進(jìn)行校驗,檢測數(shù)據(jù)是否有效合法的。所以我們可能會寫大量的if else等判斷邏輯,特別是在不同方法出現(xiàn)相同的數(shù)據(jù)時,校驗的邏輯代碼會反復(fù)出現(xiàn),導(dǎo)致代碼冗余,閱讀性和可維護(hù)性極差。

JSR-303是Java為Bean數(shù)據(jù)合法性校驗提供的標(biāo)準(zhǔn)框架,它定義了一整套校驗注解,可以標(biāo)注在成員變量,屬性方法等之上。

hibernate-validator就提供了這套標(biāo)準(zhǔn)的實現(xiàn),我們在用Springboot開發(fā)web應(yīng)用時,會引入spring-boot-starter-web依賴,它默認(rèn)會引入spring-boot-starter-validation依賴,而spring-boot-starter-validation中就引用了hibernate-validator依賴。

spring自定義校驗注解ConstraintValidator的示例分析

但是,在比較高版本的spring-boot-starter-web中,默認(rèn)不再引用spring-boot-starter-validation,自然也就不會默認(rèn)引入到hibernate-validator依賴,需要我們手動添加依賴。

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

hibernate-validator中有很多非常簡單好用的校驗注解,例如NotNull,@NotEmpty,@Min,@Max,@Email,@PositiveOrZero等等。這些注解能解決我們大部分的數(shù)據(jù)校驗問題。如下所示:

package com.nobody.dto;

import lombok.Data;

import javax.validation.constraints.*;

@Data
public class UserDTO {

    @NotBlank(message = "姓名不能為空")
    private String name;

    @Min(value = 18, message = "年齡不能小于18")
    private int age;

	@NotEmpty(message = "郵箱不能為空")
    @Email(message = "郵箱格式不正確")
    private String email;
}

二、自定義參數(shù)校驗器

但是,hibernate-validator中的這些注解不一定能滿足我們?nèi)康男枨?,我們想校驗的邏輯比這復(fù)雜。所以,我們可以自定義自己的參數(shù)校驗器。

首先引入依賴是必不可少的。

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

最近不是基金很火嗎,一大批的韭菜瘋狂地涌入買基金的浪潮中。我就以用戶開戶為例,首先要校驗此用戶是不是成年人(即不能小于18歲),以及名字是不是以"新韭菜"開頭的,符合條件的才允許開戶。

定義一個注解,用于校驗用戶的姓名是不是以“新韭菜”開頭的。

package com.nobody.annotation;

import com.nobody.validator.IsLeekValidator;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
@Constraint(validatedBy = IsLeekValidator.class) // 指定我們自定義的校驗類
public @interface IsLeek {

    /**
     * 是否強制校驗
     * 
     * @return 是否強制校驗的boolean值
     */
    boolean required() default true;

    /**
     * 校驗不通過時的報錯信息
     * 
     * @return 校驗不通過時的報錯信息
     */
    String message() default "此用戶不是韭零后,無法開戶!";

    /**
     * 將validator進(jìn)行分類,不同的類group中會執(zhí)行不同的validator操作
     * 
     * @return validator的分類類型
     */
    Class<?>[] groups() default {};

    /**
     * 主要是針對bean,很少使用
     * 
     * @return 負(fù)載
     */
    Class<? extends Payload>[] payload() default {};

}

定義校驗類,實現(xiàn)ConstraintValidator接口,接口使用了泛型,需要指定兩個參數(shù),第一個是自定義注解,第二個是需要校驗的數(shù)據(jù)類型。重寫2個方法,initialize方法主要做一些初始化操作,它的參數(shù)是我們使用到的注解,可以獲取到運行時的注解信息。isValid方法就是要實現(xiàn)的校驗邏輯,被注解的對象會傳入此方法中。

package com.nobody.validator;

import com.nobody.annotation.IsLeek;
import org.springframework.util.StringUtils;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class IsLeekValidator implements ConstraintValidator<IsLeek, String> {

    // 是否強制校驗
    private boolean required;

    @Override
    public void initialize(IsLeek constraintAnnotation) {
        this.required = constraintAnnotation.required();
    }

    @Override
    public boolean isValid(String name, ConstraintValidatorContext constraintValidatorContext) {
        if (required) {
            // 名字以"新韭菜"開頭的則校驗通過
            return !StringUtils.isEmpty(name) && name.startsWith("新韭菜");
        }
        return false;
    }
}

三、使用自定義注解

通過以上幾個步驟,我們自定義的校驗注解就完成了,我們使用測試下效果。

package com.nobody.dto;

import com.nobody.annotation.IsLeek;
import lombok.Data;

import javax.validation.constraints.*;

@Data
public class UserDTO {

    @NotBlank(message = "姓名不能為空")
    @IsLeek // 我們自定義的注解
    private String name;

    @Min(value = 18, message = "年齡不能小于18")
    private int age;

    @NotEmpty(message = "郵箱不能為空")
    @Email(message = "郵箱格式不正確")
    private String email;
}

寫個接口,模擬用戶開戶業(yè)務(wù),調(diào)用測試。注意,記得加上@Valid注解開啟校驗,不然不生效。

package com.nobody.controller;

import com.nobody.dto.UserDTO;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;

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

    @PostMapping("add")
    public UserDTO add(@RequestBody @Valid UserDTO userDTO) {
        System.out.println(">>> 用戶開戶成功...");
        return userDTO;
    }

}

如果參數(shù)校驗不通過,會拋出MethodArgumentNotValidException異常,我們?nèi)痔幚硐氯缓蠓祷亟o接口。

package com.nobody.exception;

import javax.servlet.http.HttpServletRequest;

import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import lombok.extern.slf4j.Slf4j;

@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    // 處理接口參數(shù)數(shù)據(jù)格式錯誤異常
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    @ResponseBody
    public Object errorHandler(HttpServletRequest request, MethodArgumentNotValidException e) {
        return e.getBindingResult().getAllErrors();
    }
}

我們先測試用戶姓名不帶"新韭菜"前綴的進(jìn)行測試,發(fā)現(xiàn)校驗不通過,證明注解生效了。

POST http://localhost:8080/user/add

Content-Type: application/json


{"name": "小綠", "age": 19, "email": "845136542@qq.com"}

[
  {
    "codes": [
      "IsLeek.userDTO.name",
      "IsLeek.name",
      "IsLeek.java.lang.String",
      "IsLeek"
    ],
    "arguments": [
      {
        "codes": [
          "userDTO.name",
          "name"
        ],
        "arguments": null,
        "defaultMessage": "name",
        "code": "name"
      },
      true
    ],
    "defaultMessage": "此用戶不是韭零后,無法開戶!",
    "objectName": "userDTO",
    "field": "name",
    "rejectedValue": "小綠",
    "bindingFailure": false,
    "code": "IsLeek"
  }

如果多個參數(shù)校驗失敗,報錯信息也都能獲得。如下所示,姓名和郵箱都校驗失敗。

POST http://localhost:8080/user/add

Content-Type: application/json


{"name": "小綠", "age": 19, "email": "84513654"}

[
  {
    "codes": [
      "Email.userDTO.email",
      "Email.email",
      "Email.java.lang.String",
      "Email"
    ],
    "arguments": [
      {
        "codes": [
          "userDTO.email",
          "email"
        ],
        "arguments": null,
        "defaultMessage": "email",
        "code": "email"
      },
      [],
      {
        "defaultMessage": ".*",
        "codes": [
          ".*"
        ],
        "arguments": null
      }
    ],
    "defaultMessage": "郵箱格式不正確",
    "objectName": "userDTO",
    "field": "email",
    "rejectedValue": "84513654",
    "bindingFailure": false,
    "code": "Email"
  },
  {
    "codes": [
      "IsLeek.userDTO.name",
      "IsLeek.name",
      "IsLeek.java.lang.String",
      "IsLeek"
    ],
    "arguments": [
      {
        "codes": [
          "userDTO.name",
          "name"
        ],
        "arguments": null,
        "defaultMessage": "name",
        "code": "name"
      },
      true
    ],
    "defaultMessage": "此用戶不是韭零后,無法開戶!",
    "objectName": "userDTO",
    "field": "name",
    "rejectedValue": "小綠",
    "bindingFailure": false,
    "code": "IsLeek"
  }
]

以下是所有參數(shù)校驗通過的情況:

POST http://localhost:8080/user/add

Content-Type: application/json


{"name": "新韭菜小綠", "age": 19, "email": "84513654@qq.com"}

{

  "name": "新韭菜小綠",

  "age": 19,

  "email": "84513654@qq.com"

}

我們可能會將UserDTO對象用在不同的接口中接收參數(shù),比如在新增和修改接口中。在新增接口中,不需要校驗userId;在修改接口中需要校驗userId。那注解中的groups字段就派上用場了。groups和@Validated配合能控制哪些注解需不需要開啟校驗。

我們首先定義2個groups分組接口Update和Create,并且繼承Default接口。當(dāng)然也可以不繼承Default接口,因為使用注解時不顯示指定groups的值,則默認(rèn)為groups = {Default.class}。所以繼承了Default接口,在用@Validated(Create.class)時,也會校驗groups = {Default.class}的注解。

package com.nobody.annotation;

import javax.validation.groups.Default;

public interface Create extends Default {
}
package com.nobody.annotation;

import javax.validation.groups.Default;

public interface Update extends Default {
}

在用到注解的地方,填寫groups的值。

package com.nobody.dto;

import com.nobody.annotation.Create;
import com.nobody.annotation.IsLeek;
import com.nobody.annotation.Update;
import lombok.Data;

import javax.validation.constraints.*;

@Data
public class UserDTO {

    @NotBlank(message = "用戶ID不能為空", groups = Update.class)
    private String userId;

    @NotBlank(message = "姓名不能為空", groups = {Update.class, Create.class})
    @IsLeek
    private String name;

    @Min(value = 18, message = "年齡不能小于18")
    private int age;

    @NotEmpty(message = "郵箱不能為空")
    @Email(message = "郵箱格式不正確")
    private String email;
}

最后,在需要聲明校驗的地方,通過@Validated的指定即可。

package com.nobody.controller;

import com.nobody.annotation.Create;
import com.nobody.annotation.Update;
import com.nobody.dto.UserDTO;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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

    @PostMapping("add")
    public Object add(@RequestBody @Validated(Create.class) UserDTO userDTO) {
        System.out.println(">>> 用戶開戶成功...");
        return userDTO;
    }

    @PostMapping("update")
    public Object update(@RequestBody @Validated(Update.class) UserDTO userDTO) {
        System.out.println(">>> 用戶信息修改成功...");
        return userDTO;
    }

}

調(diào)用add接口時,即使不傳userId也能通過,即不對userId進(jìn)行校驗。

POST http://localhost:8080/user/add

Content-Type: application/json


{"name": "新韭菜小綠", "age": 18, "email": "84513654@qq.com"}

調(diào)用update接口時,不傳userId,會校驗不通過。

POST http://localhost:8080/user/update

Content-Type: application/json


{"name": "新韭菜小綠", "age": 18, "email": "84513654@qq.com"}

[
  {
    "codes": [
      "NotBlank.userDTO.userId",
      "NotBlank.userId",
      "NotBlank.java.lang.String",
      "NotBlank"
    ],
    "arguments": [
      {
        "codes": [
          "userDTO.userId",
          "userId"
        ],
        "arguments": null,
        "defaultMessage": "userId",
        "code": "userId"
      }
    ],
    "defaultMessage": "用戶ID不能為空",
    "objectName": "userDTO",
    "field": "userId",
    "rejectedValue": null,
    "bindingFailure": false,
    "code": "NotBlank"
  }
]

以上是“spring自定義校驗注解ConstraintValidator的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對大家有幫助,更多相關(guān)知識,歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI