SpringBoot 中使用聲明式註解簡化參數校驗

1.先看下最終效果react

當咱們發起一個POST /users的請求指望新增一個用戶git

@PostMapping("/users")
public void addUser(@Valid @RequestBody User user) {
    log.info("用戶添加成功:{}", user);
}

假設攜帶如下JSON數據做爲請求參數,可是一般咱們會指望username和password不能爲空web

{
        "username":"",
        "password":""
}

所以咱們指望能獲得一個具體的響應,告訴咱們參數校驗失敗的個數及緣由正則表達式

{
    "code": 400,
    "message": "BAD_REQUEST",
    "data": "參數校驗錯誤(2):用戶名不能爲空;密碼不能爲空"
}

data中的(2) 表示有兩處參數校驗失敗並在其後代表校驗失敗的緣由spring

 

2.接下來講明下實現過程數組

總共分爲三步app

 

第一步,在參數上添加聲明式註解定義參數須要的校驗類型,例如上文的User對象ide

@Data
public class User {
    private String id;
    @NotBlank(message = "用戶名不能爲空")
    private String username;
    @NotBlank(message = "密碼不能爲空")
    private String password;
    @Email(message = "Email格式錯誤")
    private String email;
    @PastOrPresent(message = "日期小於等於當前時間")
    private Date birthday;
    @Pattern(regexp = "^((13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(17[013678])|(18[0,5-9]))\\d{8}$"
            , message = "手機號格式錯誤")
    private String phone;
    @Min(value = 0, message = "年齡超出範圍,最小值爲0")
    @Max(value = 120, message = "年齡超出範圍,最大值爲120")
    private Integer age;
}
註解 描述
@AssertFalse 所註解的元素必須是Boolean類型,且值爲false
@AssertTrue 所註解的元素必須是Boolean類型,且值爲true
@DecimalMax 所註解的元素必須是數字,且值小於等於給定的值
@DecimalMin 所註解的元素必須是數字,且值大於等於給定的值
@Digits 所註解的元素必須是數字,且值必須是指定的位數
@Future 所註解的元素必須是未來某個日期
@Max 所註解的元素必須是數字,且值小於等於給定的值
@Min 所註解的元素必須是數字,且值小於等於給定的值
@Range 所註解的元素需在指定範圍區間內
@NotNull 所註解的元素值不能爲null
@NotBlank 所註解的元素值有內容
@Null 所註解的元素值爲null
@Past 所註解的元素必須是某個過去的日期
@PastOrPresent 所註解的元素必須是過去某個或如今日期
@Pattern 所註解的元素必須知足給定的正則表達式
@Size 所註解的元素必須是String、集合或數組,且長度大小需保證在給定範圍以內
@Email 所註解的元素需知足Email格式

注意:spring-boot

a.其中 ,username與password屬性必傳而其餘屬性沒有限制測試

b.註解中的message屬性會在校驗失敗拋出異常時賦給defaultMessage屬性

 

第二步,在須要校驗的參數前添加@Valid註解

public void addUser(@Valid @RequestBody User user)

注意

a.若是沒有添加@Valid註解是不會對參數進行校驗的

 

第三步,添加WebExchangeBindException異常處理器

@Slf4j
@RestControllerAdvice
public class MyExceptionHandler {
    @ExceptionHandler(WebExchangeBindException.class)
    public Map<String, Object> handle(WebExchangeBindException exception) {
        //獲取參數校驗錯誤集合
        List<FieldError> fieldErrors = exception.getFieldErrors();
        //格式化以提供友好的錯誤提示
        String data = String.format("參數校驗錯誤(%s):%s", fieldErrors.size(),
                fieldErrors.stream()
                .map(FieldError::getDefaultMessage)
                .collect(Collectors.joining(";")));
        //參數校驗失敗響應失敗個數及緣由
        return ImmutableMap.of("code", exception.getStatus().value(),
                "message", exception.getStatus(),
                "data", data);
    }
}

@RestControllerAdvice @ResponseBody與@ControllerAdvice兩個註解結合,表示當前類是一個控制器加強類,一般與@ExceptionHandler註解搭配來捕獲處理異常

@ResponseBody 表示響應客戶端時使用消息轉換器(Message conversion)而不是內容協商(Content negotiation),默認使用Jackson解析,註解在類上表名類下的全部方法都須要響應爲JSON(沒有使用其餘消息轉換器的狀況下)

@ExceptionHandler 註解的方法將會捕獲並處理指定的異常,文中處理的是WebExchangeBindException異常

ImmutableMap.of() Guava提供的API,須要引入如下依賴

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>23.0</version>
</dependency>

 

以上,便可達到本文開頭的效果啦。可是仍然存在能夠改進的地方,例如,User對象的age屬性的校驗註解能夠簡化爲

@Range(min = 0, max = 120, message = "年齡大於等於0小於等於120")
private Integer age;

同時,咱們可使用自定義參數校驗器來實現一個通用的手機號碼校驗註解。

 

3.實現自定義的參數校驗註解與校驗器

上文中咱們能夠發現想要校驗手機號碼是否符合格式,須要在註解上添加一長串的正則表達式,下面讓咱們使用自定義的參數校驗註解加上自定義的參數校驗器來實現一個通用的手機號碼校驗規則。一樣,分爲三步

 

第一步,定義一個參數校驗註解,複製@NotNull註解並加以修改

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE,
        ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
//定義當前註解使用哪一個參數校驗器進行校驗
@Constraint(validatedBy = PhoneValidator.class)
@Repeatable(Phone.List.class)
public @interface Phone {
    String message() default "手機號碼格式錯誤";

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

    Class<? extends Payload>[] payload() default {};

    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE,
            ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @interface List {
        Phone[] value();
    }
}

注意:

a.message、groups、payload屬性都須要定義在參數校驗註解中不能缺省\

b.@Repeatable是JDK1.8中的元註解,表示在同一個位置重複相同的註解

若是使用的JDK版本低於1.8在可使用如下方式建立@Phone註解

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE,
        ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneValidator.class)
public @interface Phone {
    String message() default "手機號碼格式錯誤";

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

    Class<? extends Payload>[] payload() default {};
}

 

接着,須要定義上文的PhoneValidator參數校驗器

public class PhoneValidator implements ConstraintValidator<Phone, Object> {
    private static final String PHONE_REGEX = "^((13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(17[013678])|(18[0,5-9]))\\d{8}$";

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {
        //值不爲空或者知足正則表達式時返回true
        return Objects.isNull(value) || Pattern.compile(PHONE_REGEX).matcher(value.toString()).find();
    }
}

 

最後,使用參數校驗註解,替換上文中繁瑣的@Pattern註解

@Phone
private String phone;

 

測試

 

使用自定義參數校驗的優點

1.消除耦合,若是哪天你須要更改正則表達式你須要在每一個引用的地方進行更改

2.通俗易懂,能夠一目瞭然表示這是一個手機校驗的註解,即使代碼的閱讀者對正則表達式不太熟悉,也能夠猜出這個註解用來幹嗎的

3.更強大,自定義校驗邏輯,能夠幹更多的事情,甚至能夠在校驗器中引入其餘的組件如使用@Autowired引入服務類進行處理校驗判斷

 

最後貼出本文的pom.xml文件

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>23.0</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>io.projectreactor</groupId>
        <artifactId>reactor-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
相關文章
相關標籤/搜索