springMVC請求參數(shù)校驗

下下下周健蕊,爭取做只水煮魚~~~
算了吧缩功,買現(xiàn)成的調(diào)料吧~~~


fish.png

1 場景

JavaWeb后臺應(yīng)用程序,具體的執(zhí)行方法势木,收到請求跟压,需要對請求的數(shù)據(jù)進行基礎(chǔ)校驗,如字符串長度限制查剖、正則校驗笋庄、數(shù)字區(qū)間校驗等。

推薦在springMVC中對前臺的請求參數(shù)進行統(tǒng)一校驗静暂,校驗方式建議采用JSR30標準進行校驗洽蛀。

1.1 普通校驗方式

最簡單的校驗方式是郊供,對請求的參數(shù)手動一個個進行校驗鲫寄,如下代碼:

@GetMapping("saveWithOld")
public JSONObject saveWithOld(User user) {
    JSONObject result = new JSONObject();
    if (user.getUserCode() == null || user.getUserCode() == "") {
        result.put("success", true);
        result.put("message", "用戶代碼不可為空");
        return result;
    }
    if (user.getUserName() == null || user.getUserName() == "") {
        result.put("success", true);
        result.put("message", "用戶名稱不可為空");
        return result;
    }
    // do something ......

    result.put("success", true);
    return result;
}

這種方式塔拳,代碼量非常大量九,代碼非常不友好类浪。

1.2 springMVC校驗方式

springMVC费就,在執(zhí)行后臺方法之前,可以對請求的數(shù)據(jù)通過注解進行校驗眠蚂。此校驗方式基于JSR303規(guī)范

如下代碼所示:

@Data
public class User {
    @NotNull(message = "用戶代碼不可為空")
    private String userCode;
}
@GetMapping("saveWithNormal")
    public JSONObject saveWithNormal(@Valid User user) {
        JSONObject result = new JSONObject();
        result.put("success", true);
        result.put("message", user.toString());
        return result;
    }
@PostMapping("saveWithRequestParam")
public JSONObject saveWithRequestParam(@NotNull(message = "用戶代碼不可為空") String userCode) {
    JSONObject result = new JSONObject();
    result.put("success", true);
    result.put("message", userCode);
    return result;
}

此種方式笛臣,可以使用注解,已更簡單的方式對請求參數(shù)進行校驗踱蛀。

3 版本說明

本文中代碼涉及到的相關(guān)版本如下:

3.1 JDK

JDK1.8

3.2 maven依賴

spring-boot-starter-web中已包含了我們需要的依賴崩泡。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.2.9.RELEASE</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.7 </version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.18</version>
    <scope>provided</scope>
</dependency>

2 名詞關(guān)系說明

這里講下springMVC中使用JSR303進行參數(shù)校驗,相關(guān)的名詞含義及名詞之間的關(guān)系說明谒所。

2.1 基本說明

springMVC基于JSR303規(guī)范進行校驗劣领。

官網(wǎng)說明:https://jcp.org/en/jsr/detail?id=303

規(guī)范的相關(guān)說明如下:

JSR是Java Specification Requests的縮寫著觉,意思是Java 規(guī)范提案 趁桃。
JSR-303 是JAVA EE 6 中的一項子規(guī)范,叫做Bean Validation忽肛。
Hibernate Validator是 Bean Validation的參考實現(xiàn)
Hibernate Validator提供了JSR 303 規(guī)范中所有內(nèi)置 constrain(約束)的實現(xiàn)罕模,除此之外還有一些附加的constraint(約束)

2.2 詳細說明

關(guān)于springMVC請求參數(shù)校驗抛腕,涉及幾個對應(yīng)的名詞摔敛,如下是:

名詞 說明
constraint(約束) 對參數(shù)的校驗約束注解,如@NotNull表示參數(shù)不可以為Null
校驗注解 為元素加上約束后行楞,有時候需要在參數(shù)前加上校驗注解來開啟驗證。
相關(guān)注解有@Valid@Validated池颈,如沒有用到注解獨有的特性(分組、嵌套)等每币,用哪個注解都一樣携丁。

需注意不是所有的校驗都需要開啟校驗,如下不需要加上校驗注解:
saveWithRequestParam(@NotNull(message = "用戶代碼不可為空") String userCode)
但是需要在Controller類上加上注解@Valid或@Validated
JSR303規(guī)范 行業(yè)規(guī)范標準兰怠,包括校驗的constraint(約束梦鉴,如@NotNull)開啟校驗注解@Valid
體現(xiàn):代碼中體現(xiàn)為注解、接口揭保,無具體實現(xiàn)代碼
jar包:jakarta.validation-api-2.0.2.jar
約束注解:javax.validation.constraints包下注解+hibernate增強注解org.hibernate.validator.constraints
校驗注解:javax.validation.Valid
Hibernate Validator Hibernate對JSR303規(guī)范中的約束constraint具體代碼實現(xiàn)
jar包:hibernate-validator-6.0.20.Final.jar
增強:在原有JSR303的constraint(約束)增加了約束(如@Range)
spring JSR303 spring對JSR303的包裝肥橙,對原有的校驗進行了增強
增強:分組校驗椭坚、順序校驗
缺點:不支持嵌套校驗
約束注解:javax.validation.constraints包下注解+hibernate增強注解org.hibernate.validator.constraints
校驗注解:org.springframework.validation.annotation.Validated
增強說明:所謂的包裝和增強烁焙,只是將@Valid注解擴展為@Validated注解乞榨。約束注解和JSR303一樣。

2.3 關(guān)系圖

一圖勝千言媒楼。參數(shù)校驗的相關(guān)說明夺颤,關(guān)系圖如下:

spring參數(shù)校驗.jpg

3 校驗流程

3.1 對象參數(shù)

3.1.1 說明

對象參數(shù)中進行約束校驗抚恒。需滿足以下條件:

(1)在mapping方法中通過注解@Valid或@Validated指定要校驗的參數(shù)對象

如下:

@GetMapping("saveWithNormal")
public JSONObject saveWithNormal(@Valid User user) {......}

(2)在對象參數(shù)對應(yīng)的類中,對需要校驗的參數(shù)加上約束注解

如下:

@Data
public class User {
    /**
     * 用戶代碼
     */
    @NotNull(message = "用戶代碼不可為空")
    private String userCode;
}
3.1.2 校驗流程

校驗失敗后,需要對失敗的異常信息進行處理,處理方式有兩種:

1榆俺、在mapping方法上加上參數(shù)BindingResult bindingResult

此種方式,校驗失敗后,會將異常信息封裝到參數(shù)對象bindingResult中,可以自行對其中的異常信息進行處理装获,封裝錯位信息,返回請求結(jié)果习柠。

這種情況,需要每個請求露久,都對參數(shù)BindingResult進行處理臊泰,較為繁瑣,不建議此種方式莽鸭。

如下:

@GetMapping("saveWithBind")
public JSONObject saveWithBind(@Valid User user, BindingResult bindingResult) {
    // --------------------[手動檢測驗證是否通過]--------------------
    if (bindingResult.hasErrors()) {
        for (FieldError fieldError : bindingResult.getFieldErrors()) {
            JSONObject result = new JSONObject();
            result.put("success", false);
            result.put("message", fieldError.getDefaultMessage());
            return result;
        }
    }
    // --------------------[驗證檢測通過后執(zhí)行其他操作]--------------------
    // ......
    JSONObject result = new JSONObject();
    result.put("success", true);
    result.put("message", user.toString());
    return result;
}

2、定義spring全局異常處理,捕捉對應(yīng)的異常信息官地,進行統(tǒng)一處理

mapping方法上不加參數(shù)BindingResult bindingResult修陡,校驗失敗后魄鸦,會拋出異常,異常信息,通過spring全局異常管理图仓,統(tǒng)一對拋出的異常信息進行處理狸臣,處理后統(tǒng)一封裝錯位信息丹诀。這種方式钝的,代碼量較少翁垂,且處理錯誤信息集中,推薦此種方式硝桩。

如下代碼:

@Data
public class User {
    /**
     * 用戶代碼
     */
    @NotNull(message = "用戶代碼不可為空")
    private String userCode;
}
// 參數(shù)校驗失敗沿猜,拋出異常:org.springframework.validation.BindException
@GetMapping("saveWithNormal")
public JSONObject saveWithNormal(@Valid User user) {
    JSONObject result = new JSONObject();
    result.put("success", true);
    result.put("message", user.toString());
    return result;
}
/**
   * 捕捉全局異常:org.springframework.validation.BindException
   * <div>普通請求的參數(shù),校驗失敗碗脊,拋出此異常</div>
   * <div>如:(@Valid User user)</div>
   *
   * @param exception
   * @return
   */
@ExceptionHandler(BindException.class)
public JSONObject handlerBindException(BindException exception) {
    log.info("全局異常[BindException]:" + exception.getMessage());
    JSONObject result = new JSONObject();
    result.put("success", false);
    if (exception != null) {
        String message = exception.getBindingResult().getFieldErrors().stream().filter(e -> e != null).map(FieldError::getDefaultMessage).collect(Collectors.joining(","));
        result.put("message", message);
    }
    return result;
}

需注意:參數(shù)上@Valid和@Validated使用方式的不同啼肩,校驗失敗后,會拋出不同的異常

如下:

/**
  * 捕捉全局異常:org.springframework.validation.BindException
  * <div>普通請求的參數(shù)望薄,校驗失敗疟游,拋出此異常</div>
  * <div>如:(@Valid User user)</div>
  * @param exception
  * @return
  */
@ExceptionHandler(BindException.class)
public JSONObject handlerBindException(BindException exception) {......}
/**
  * 捕捉全局異常:org.springframework.web.bind.MethodArgumentNotValidException
  * <div>@RequestBody修飾的參數(shù),校驗失敗痕支,拋出此異常</div>
  * <div>如:(@RequestBody @Valid User user)</div>
  * @param exception
  * @return
  */
@ExceptionHandler({MethodArgumentNotValidException.class})
public JSONObject handleMethodArgumentNotValidException(MethodArgumentNotValidException exception) {......}

總結(jié)校驗流程圖如下:

對象參數(shù)校驗流程.jpg

3.2 普通類型參數(shù)

3.2.1 說明

普通類型參數(shù)中進行約束校驗颁虐。需滿足以下條件:

(1)在Controller類上通過注解@Validated開啟校驗

注意:需為@Validated注解,而不是@Valid注解

如下:

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

(2)在mapping方法普通類型參數(shù)前面加上約束注解

如下:

// 參數(shù)校驗失敗卧须,拋出異常:javax.validation.ConstraintViolationException
@PostMapping("saveWithRequestParam")
public JSONObject saveWithRequestParam(@NotNull(message="用戶代碼不可為空") String userCode){......}
// 參數(shù)校驗失敗另绩,拋出異常:org.springframework.web.bind.ConstraintViolationException
@GetMapping("saveWithRestful/{userCode}")
public JSONObject saveWithRest(@PathVariable("userCode") @Length(max = 10,message="用戶代碼不可超過10位") String userCode) {......}
3.2.1 校驗流程

校驗結(jié)果的處理流程同《3.1對象參數(shù)》

需注意:如使用全局異常捕捉,校驗失敗后拋出的異常如下:

 /**
   * 捕捉全局異常:javax.validation.ConstraintViolationException
   * <div>直接在參數(shù)上加的校驗花嘶,校驗失敗笋籽,拋出此異常</div>
   * <div>如:(@NotNull(message = "用戶代碼不可為空") String userCode)</div>
   *
   * @param exception
   * @return
   */
@ExceptionHandler(ConstraintViolationException.class)
public JSONObject handlerConstraintViolationException(ConstraintViolationException exception) {......}

總結(jié)校驗流程圖如下:

普通參數(shù)校驗流程.jpg

4 嵌套校驗

嵌套校驗,準確來說椭员,是對象內(nèi)約束的嵌套校驗车海。指的是校驗A對象,A對象內(nèi)有個屬性B是對象隘击,B對象內(nèi)部屬性仍然有約束侍芝。需要對A對象的約束+A對象內(nèi)B對象的約束進行校驗,這種就是嵌套約束埋同。

4.1 代碼示例

這里既校驗參數(shù)user中的userCode約束又需要校驗user中的屬性對象department中的departmentCode的約束州叠。

  • 實體定義
@Data
public class Department {
    @NotNull(message = "部門代碼不可為空")
    private String departmentCode;
}
@Data
public class User {
    @NotNull(message = "用戶代碼不可為空")
    private String userCode;
    
    @Valid
    @NotNull(message = "部門不可為空")
    private Department department;
    
    private String userName;
}
  • mapping方法
@GetMapping("saveWithLevel")
public JSONObject saveWithLevel(@Valid User user) {
    JSONObject result = new JSONObject();
    result.put("success", true);
    result.put("message", user.toString());
    return result;
}
  • 異常處理
@ExceptionHandler(BindException.class)
public JSONObject handlerBindException(BindException exception) {
    log.info("全局異常[BindException]:" + exception.getMessage());
    JSONObject result = new JSONObject();
    result.put("success", false);
    if (exception != null) {
        String message = exception.getBindingResult().getFieldErrors().stream().filter(e -> e != null).map(FieldError::getDefaultMessage).collect(Collectors.joining(","));
        result.put("message", message);
    }
    return result;
}

4.2 代碼測試

  • 請求信息

http://localhost:8080/user/saveWithLevel?department.departmentCode=001

  • 返回結(jié)果
{"success":false,"message":"部門代碼長度需在5~10之間,用戶代碼不可為空"}

可見嵌套校驗起作用了,對象user的內(nèi)部普通屬性userCode和內(nèi)部對象department的自己的約束都起作用了凶赁。

4.3 總結(jié)

  • 實現(xiàn)嵌套校驗咧栗,在被校驗對象的內(nèi)部屬性對象上,必須加上@Valid注解

  • mapping方法參數(shù)前虱肄,用@Valid注解和@Validated沒有區(qū)別

5 分組校驗

同一個javaBean致板,我們加上約束注解后,這個javaBean作為請求參數(shù)的對象類型咏窿,其中的約束注解斟或,會對參數(shù)對象的內(nèi)容進行校驗。

有時候翰灾,不同的請求我們會使用相同的javaBean作為對象的參數(shù)類型缕粹,如新增用戶更新用戶我們都會使用用戶這個JavaBean作為請求參數(shù)的封裝對象。

5.1 代碼示例

比如纸淮,我們新增用戶平斩,需要設(shè)置密碼;更新用戶咽块,不需要設(shè)置密碼绘面。

代碼如下:

  • 分組類型

分組接口不需要有實現(xiàn),僅僅作為一個分組類型

public interface Add {
}
public interface Edit {
}
  • 實體定義

通過約束中的group參數(shù)侈沪,來指定對應(yīng)的分組類型揭璃,可以指定多個

@Data
public class User {
    @NotNull(message = "用戶代碼不可為空", groups = {Add.class, Edit.class})
    private String userCode;
    
    @NotNull(message = "密碼不可為空", groups = {Add.class})
    private String password;
}
  • mapping方法

校驗方式,只能指定@Validated亭罪,其中的value為這個參數(shù)的分組類型瘦馍,和類中約束注解的groups屬性相對性可以指定多個应役。

@GetMapping("groupAdd")
public JSONObject groupAdd(@Validated(Add.class) User user) {
    JSONObject result = new JSONObject();
    result.put("success", true);
    result.put("message", user.toString());
    return result;
}

@GetMapping("groupEdit")
public JSONObject groupEdit(@Validated(Edit.class) User user) {
    JSONObject result = new JSONObject();
    result.put("success", true);
    result.put("message", user.toString());
    return result;
}
  • 異常處理

同4.1

5.2 代碼測試

5.3 總結(jié)

(1)分組校驗中情组,定義的分組類型接口,不需要有實現(xiàn)內(nèi)容箩祥,僅僅是作為分組的一個類型存在院崇,不同的業(yè)務(wù),可以共用相同的類型袍祖。

(2)約束中的分組類型底瓣,可以定義多個。

(3)@Validated中的分組類型蕉陋,也可以指定多個捐凭。

(4)校驗的時候,根據(jù)@Validated中指定分組類型寺滚,柑营,去找校驗對象中的對應(yīng)有此分組類型的約束,進行校驗村视。

(5)指定分組后官套,不滿足分組的約束(不加分組的約束為默認分組,也是一種分組)蚁孔,不會進行校驗

6 順序校驗

如不進行順序校驗配置奶赔,校驗對象內(nèi)的屬性,校驗順序是隨機的杠氢。

有時候想先校驗比較簡單的約束站刑,再校驗復(fù)雜的,因此需要指定約束的校驗順序鼻百〗事茫可以結(jié)合《7 驗證將檢測到第一個約束違例時停止》一起使用摆尝。

6.1 代碼示例

  • 分組類型
// 分組類型:第一個執(zhí)行
public interface FirstCheck {
}
// 分組類型:第二個執(zhí)行
public interface SecondCheck {
}
// 待順序的分組類型組
@GroupSequence({FirstCheck.class, SecondCheck.class})
public interface UserGroupCheck {
}
  • 實體定義
@Data
public class User {
    @NotNull(message = "用戶代碼不可為空", groups = {FirstCheck.class})
    private String userCode;
    
    @NotNull(message = "密碼不可為空", groups = {SecondCheck.class})
    private String password;
    
    @NotNull(message = "用戶名不可為空")
    private String userName;
}
  • mapping方法
@GetMapping("orderCheck")
public JSONObject orderCheck(@Validated(UserGroupCheck.class) User user) {
    JSONObject result = new JSONObject();
    result.put("success", true);
    result.put("message", user.toString());
    return result;
}
  • 異常處理

同4.1

6.2 代碼測試

  • 請求1

    • 請求

    http://localhost:8080/user/orderCheck

    • 結(jié)果
    {"success":false,"message":"用戶代碼不可為空"}
    
  • 請求2

    • 請求

    http://localhost:8080/user/orderCheck?userCode=001

    • 結(jié)果
    {"success":false,"message":"密碼不可為空"}
    

    請求3

    • 請求

    http://localhost:8080/user/orderCheck?userCode=001&password=123456

    • 結(jié)果
    {"success":true,"message":"User(userCode=001, password=123456, userName=null)"}
    
  • 特殊請求

    • 變更

      將實體類進行變更,userName上的約束也加上分組為FirstCheck因悲。此時userCode和userName的約束分組均為FirstCheck堕汞。

      如下:

      @Data
      public class User {
          @NotNull(message = "用戶代碼不可為空", groups = {FirstCheck.class})
          private String userCode;
          
          @NotNull(message = "密碼不可為空", groups = {SecondCheck.class})
          private String password;
          
          @NotNull(message = "用戶名不可為空", groups = {FirstCheck.class})
          private String userName;
      }
      
    • 請求

      http://localhost:8080/user/orderCheck

    • 結(jié)果

{"success":false,"message":"用戶代碼不可為空,用戶名不可為空"}


或

```json
{"success":false,"message":"用戶名不可為空,用戶代碼不可為空"}

可以看出,當一個分組內(nèi)有多個約束晃琳,約束的校驗順序仍然是隨機的

6.3 總結(jié)

(1)根據(jù)參數(shù)中的分組對應(yīng)的接口中@GroupSequence指定的分組類型的順序進行加校驗

(2)只有當一個分組內(nèi)的所有約束都校驗通過后讯检,才會進入下一個分組進行校驗。

(3)順序校驗卫旱,指的是@GroupSequence內(nèi)配置的分組的順序人灼,當一個分組內(nèi)有多個約束這個分組內(nèi)約束校驗順序仍然隨機

7 驗證將檢測到第一個約束違例時停止

默認顾翼,有多個約束的情況下投放,將會對所有參數(shù)進行校驗,如果存在校驗失敗的約束适贸,返回的校驗結(jié)果(BindingResult或?qū)?yīng)Exception)中會有所有的參數(shù)校驗錯誤信息跪呈。即如果多個不滿足約束,則返回結(jié)果中會有多個失敗信息取逾。

有時候耗绿,我們只需要返回第一個一個校驗失敗的約束信息就好,校驗到一個約束失敗后砾隅,沒有必要再花費代價進行其他約束校驗误阻。

springBoot中,參數(shù)校驗的實現(xiàn)晴埂,基于MethodValidationPostProcessor

@Bean
@ConditionalOnMissingBean
public static MethodValidationPostProcessor methodValidationPostProcessor(Environment environment,
                                                                          @Lazy Validator validator) {
    MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
    boolean proxyTargetClass = environment.getProperty("spring.aop.proxy-target-class", Boolean.class, true);
    processor.setProxyTargetClass(proxyTargetClass);
    processor.setValidator(validator);
    return processor;
}

這個postProcessor的校驗配置基于spring中的beanValidator究反,我們創(chuàng)建自己的Validator的bean,配置failFast儒洛,即可實現(xiàn)驗證將檢測到第一個約束違例時停止這個要求精耐。

實現(xiàn)代碼如下:

@Bean
public Validator validator() {
    HibernateValidatorConfiguration configuration = Validation.byProvider(HibernateValidator.class).configure();
    //驗證將檢測到第一個約束違例時停止
    configuration.failFast(true);
    ValidatorFactory validatorFactory = configuration.buildValidatorFactory();
    return validatorFactory.getValidator();
}

或使用更簡潔的寫法:

@Bean
public Validator validator() {
    ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
        .configure()
        //驗證將檢測到第一個約束違例時停止
        .failFast(true)
        .buildValidatorFactory();
    return validatorFactory.getValidator();
}

failFastHibernateValidatorConfiguration中的一個屬性配置,配置中還有其他配置屬性琅锻,可以定制我們的校驗器卦停。

8 自定義校驗器

自定義校驗器,注意點比較多恼蓬,不是本文的重點惊完,暫時不進行記錄,后續(xù)有時間會有專門的文章進行分析处硬。

9 生產(chǎn)環(huán)境配置

前面說的都是原理和使用細節(jié)小槐,這里記錄下生產(chǎn)環(huán)境,需要進行哪些全局配置荷辕。

9.1 全局異常處理

建議使用全局異常處理凿跳,對請求的異常信息進行統(tǒng)一處理件豌。

代碼如下:

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
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;

/**
 * 統(tǒng)一異常處理
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    
    /**
     * 捕捉全局異常:org.springframework.web.bind.MethodArgumentNotValidException
     * <div>@RequestBody修飾的參數(shù),校驗失敗控嗜,拋出此異常</div>
     * <div>如:xxxAction(@RequestBody @Valid User user)</div>
     * @param exception
     * @return
     */
    @ExceptionHandler({MethodArgumentNotValidException.class})
    public JSONObject handleMethodArgumentNotValidException(MethodArgumentNotValidException exception) {
        log.info("全局異常[MethodArgumentNotValidException]:" + exception.getMessage());
        JSONObject result = new JSONObject();
        result.put("success", false);
        if (exception != null) {
            String message = exception.getBindingResult().getFieldErrors().stream().filter(e -> e != null).map(FieldError::getDefaultMessage).collect(Collectors.joining(","));
            result.put("message", message);
        }
        return result;
    }
    
    /**
     * 捕捉全局異常:org.springframework.validation.BindException
     * <div>普通請求的參數(shù)苟径,校驗失敗,拋出此異常</div>
     * <div>如:xxxAction(@Valid User user)</div>
     * @param exception
     * @return
     */
    @ExceptionHandler(BindException.class)
    public JSONObject handlerBindException(BindException exception) {
        log.info("全局異常[BindException]:" + exception.getMessage());
        JSONObject result = new JSONObject();
        result.put("success", false);
        if (exception != null) {
            String message = exception.getBindingResult().getFieldErrors().stream().filter(e -> e != null).map(FieldError::getDefaultMessage).collect(Collectors.joining(","));
            result.put("message", message);
        }
        return result;
    }
    
    /**
     * 捕捉全局異常:javax.validation.ConstraintViolationException
     * <div>直接在參數(shù)上加的校驗躬审,校驗失敗,拋出此異常</div>
     * <div>如:xxxAction(@NotNull(message = "用戶代碼不可為空") String userCode)</div>
     * <div>如:xxxAction(@PathVariable("userCode") @Length(max = 10,message="用戶代碼不可超過10位") String userCode)</div>
     * @param exception
     * @return
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public JSONObject handlerConstraintViolationException(ConstraintViolationException exception) {
        log.info("全局異常[ConstraintViolationException]:" + exception.getMessage());
        JSONObject result = new JSONObject();
        result.put("success", false);
        if (exception != null) {
            result.put("message", exception.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(";")));
        }
        return result;
    }
    
}

參數(shù)校驗失敗蟆盐,返回的錯誤json信息如下承边,可以根據(jù)項目的實際情況進行定制:

{"success":false,"message":"用戶代碼不可超過10位"}

9.2 驗證將檢測到第一個約束違例時停止

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 ValidConfig {
    @Bean
    public Validator validator() {
        ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
                .configure()
                //驗證將檢測到第一個約束違例時停止
                .failFast(true)
                .buildValidatorFactory();
        return validatorFactory.getValidator();
    }
}

10 補充

10.1 校驗限制

@Valid支持嵌套驗證、@Validated支持分組驗證石挂、排序驗證(準確來說博助,排序驗證也是分組驗證的一種)。

無法實現(xiàn):“嵌套驗證+分組驗證”和“嵌套驗證+排序驗證”這種組合形式的驗證痹愚。

10.2 建議

雖然JSR303支持自定義校驗器富岳,筆者不建議將太復(fù)雜的校驗交給JSR303的標準進行校驗

如果是參數(shù)基本的屬性校驗(是否為空拯腮、長度窖式、大小、枚舉动壤、正則格式)萝喘,可以以這種形式進行校驗。

但是如果是太復(fù)雜的校驗琼懊,如需要連接數(shù)據(jù)庫進行業(yè)務(wù)判斷的校驗阁簸,筆者仍然建議在具體的業(yè)務(wù)代碼中進行校驗。

10.2 校驗順序的隨機性

如不使用@Validated指定約束的校驗順序哼丈,所有約束的校驗順序是隨機的启妹,即相同的情況,返回的校驗結(jié)果的順序可能不一樣醉旦。

10.3 一個字段多個約束

同一個字段可以加多個約束注解饶米,并不是只能有一個約束注解。如下:

@NotNull(message = "用戶代碼不可為空")
@Length(min = 5, max = 10, message = "用戶代碼長度需在5~10之間")
private String userCode;

如userCode為空车胡,則拋出異常:用戶代碼不可為空

如userCode不為空咙崎,則校驗約束:用戶代碼長度需在5~10之間

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者吨拍。
  • 序言:七十年代末褪猛,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子羹饰,更是在濱河造成了極大的恐慌伊滋,老刑警劉巖碳却,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異笑旺,居然都是意外死亡昼浦,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門筒主,熙熙樓的掌柜王于貴愁眉苦臉地迎上來关噪,“玉大人,你說我怎么就攤上這事乌妙∈雇茫” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵藤韵,是天一觀的道長虐沥。 經(jīng)常有香客問我,道長泽艘,這世上最難降的妖魔是什么欲险? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮匹涮,結(jié)果婚禮上天试,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布谍憔。 她就那樣靜靜地躺著泽台,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天,我揣著相機與錄音鞋真,去河邊找鬼。 笑死沃于,一個胖子當著我的面吹牛涩咖,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播繁莹,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼檩互,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了咨演?” 一聲冷哼從身側(cè)響起闸昨,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后饵较,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拍嵌,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年循诉,在試婚紗的時候發(fā)現(xiàn)自己被綠了横辆。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡茄猫,死狀恐怖狈蚤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情划纽,我是刑警寧澤脆侮,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站阿浓,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蹋绽。R本人自食惡果不足惜芭毙,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望卸耘。 院中可真熱鬧退敦,春花似錦、人聲如沸蚣抗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽翰铡。三九已至钝域,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間锭魔,已是汗流浹背例证。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留迷捧,地道東北人织咧。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像漠秋,于是被迫代替她去往敵國和親笙蒙。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,781評論 2 354