1. 程式人生 > 其它 >Java中使用註解校驗引數

Java中使用註解校驗引數

技術標籤:Javajava引數校驗註解

一、引數校驗的註解

Java中引數校驗的註解來自三方面,分別是

  1. javax.validation:validation-api,對應包javax.validation.constraints
  2. org.springframework:spring-context,對應包org.springframework.validation
  3. org.hibernate:hibernate-validator,對應包org.hibernate.validator.constraints

1.validation-api中的註解

validation-api中的註解
註解說明適用型別
@AssertFalse限制必須是false

boolean

Boolean:not null時才校驗

@AssertTrue限制必須是true

boolean

Boolean:not null時才校驗

@Max(value)限制必須為一個小於等於value指定值的整數,value是long型byte/short/int/long/float/double及其對應的包裝類;包裝類物件not null時才校驗
@Min(value)限制必須為一個大於等於value指定值的整數,value是long型byte/short/int/long/float/double及其對應的包裝類;包裝類物件not null時才校驗
@DecimalMax(value)限制必須小於等於value指定的值,value是字串型別byte/short/int/long/float/double及其對應的包裝類;包裝類物件not null時才校驗
@DecimalMin(value)限制必須大於等於value指定的值,value是字串型別byte/short/int/long/float/double及其對應的包裝類;包裝類物件not null時才校驗
@Digits(integer, fraction)限制必須為一個小數(其實整數也可以),且整數部分的位數不能超過integer,小數部分的位數不能超過fraction。integer和fraction可以是0。byte/short/int/long/float/double及其對應的包裝類;包裝類物件必須not null時才校驗
@Null限制只能為null任意物件型別(比如基本資料型別對應的包裝類、String、列舉類、自定義類等);不能是8種基本資料型別
@NotNull限制必須不為null任意型別(包括8種基本資料型別及其包裝類、String、列舉類、自定義類等);但是對於基本資料型別,沒有意義
@Size(min, max)限制Collection型別或String的長度必須在min到max之間,包含min和max
  • Collection型別(List/Set)
  • String
@Pattern(regexp)限制必須符合regexp指定的正則表示式String
@Future限制必須是一個將來的日期

Date/Calendar

@Past限制必須是一個過去的日期

Date/Calendar

@Valid校驗任何非原子型別,標記一個物件,表示校驗物件中被註解標記的物件(不支援分組功能)需要校驗成員變數的物件,比如@ModelAttribute標記的介面入參

2. hibernate-validator中的註解

hibernate-validator中的註解
註解說明適用型別
@Length(min,max)限制String型別長度必須在min和max之間,包含min和maxString, not null時才校驗
@NotBlank驗證註解的元素值不是空白(即不是null,且包含非空白字元)String
@NotEmpty驗證註解的元素值不為null且不為空(即字串非null且長度不為0、集合型別大小不為0)
  • Collection型別(List/Set)
  • String
@Range(min,max)驗證註解的元素值在最小值和最大值之間
  • String(數字型別的字串),非null時才校驗
  • byte/short/int/long/float/double及其包裝類,包裝類非null時才校驗
@Email(regexp)驗證註解的字串符合郵箱的正則表示式,可以使用regexp自定義正則表示式String
@CreditCardNumber驗證銀行借記卡、信用卡的卡號String(不能包含空格等特殊字元)

3.spring-context中的註解

spring-context中的註解
註解說明適用型別
@Validated校驗非原子型別物件,或啟用類中原子型別引數的校驗(支援分組校驗;只校驗包含指定分組的註解引數)
  • Controller類
  • @ModelAttribue標記的查詢條件物件類
  • @RequestBody標記的請求體物件類

二、註解的啟用

  1. 方法中物件引數中成員變數校驗註解的生效條件
    1. @ModelAttribute標記的查詢條件類引數,需要同時用@Valid或@Validated標記,類中的註解校驗才會生效
    2. @RequestBody標記的請求體物件引數,需要同時用@Valid或@Validated標記,類中的註解校驗才會生效
    3. @Valid或@Validated標記在方法或方法所屬類上無效
  2. 方法中原子型別引數校驗註解的生效條件
    1. @Validated標記在方法所屬類上
  3. 按照分組啟用
    1. 在註解中使用groups新增啟用註解的分組
    2. 在@Validated中指定啟用的分組

三、註解使用的幾個注意事項

  1. 注意選傳變數的預設值
    1. 選傳變數沒有傳值時,校驗會使用變數的預設值
    2. 如果有指定的預設值,會使用指定的預設值進行校驗
    3. 如果沒有指定預設值,那麼使用型別預設值進行校驗
    4. 注意預設值能否通過校驗
  2. 選傳變數使用預設值時,注意例項化到db時是否為null及對null值的處理(StringUtils.defaultString)
  3. 注意基本資料型別與對應包裝型別之間,型別預設值的區別
  4. 注意非請求體入參中的列舉型別
    1. 列舉型別不能直接接受傳參繫結
    2. 使用String或int型別先接受繫結值,再轉成列舉變數
  5. 配合統一異常處理
/**
 * 與@Valid/@Validated @ModelAttribute註解配合使用,處理查詢條件物件類引數中成員變數校驗沒有通過的異常
 * @param e
 * @return
 */
@ExceptionHandler({BindException.class})
@ResponseStatus(HttpStatus.OK)
public Response handleBindException(BindException e) {
    LOG.warn("GlobalExceptionHandler BindException", e);
    List<ObjectError> allErrors = e.getAllErrors();
    return Response.fail(ErrorCodeEnum.INVALID_PARAMS.getCode(), allErrors.get(0).getDefaultMessage());
}
 
/**
 * 與@Valid/@Validated @RequestBody配合使用,處理請求體物件類引數中成員變數校驗沒有通過的異常
 * @param e
 * @param request
 * @return
 */
@ExceptionHandler({MethodArgumentNotValidException.class})
@ResponseStatus(HttpStatus.OK)
public Response handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request){
    LOG.warn("handleMethodArgumentNotValidException={}", request.getRequestURI(), e);
    BindingResult bindingResult = e.getBindingResult();
    if(bindingResult.hasErrors()){
        List<ObjectError> errors = bindingResult.getAllErrors();
        if (!errors.isEmpty()) {
            // 這裡列出了全部錯誤引數,按正常邏輯,只需要第一條錯誤即可
            FieldError fieldError = (FieldError) errors.get(0);
            return Response.fail(ErrorCodeEnum.INVALID_PARAMS.getCode(), fieldError.getDefaultMessage());
        }
    }
    return Response.fail(ErrorCodeEnum.INVALID_PARAMS.getCode(), ErrorCodeEnum.INVALID_PARAMS.toString());
}
 
/**
 * 當@Validated修飾類時,處理介面原子型別引數沒有通過校驗的異常
 * @param e
 * @return
 */
@ExceptionHandler({ConstraintViolationException.class})
@ResponseStatus(HttpStatus.OK)
public Response handleConstraintViolationException(ConstraintViolationException e) {
    LOG.warn("GlobalExceptionHandler ConstraintViolationException", e);
    Set<ConstraintViolation<?>> constraintViolations = e.getConstraintViolations();
    Iterator<ConstraintViolation<?>> iterator = constraintViolations.iterator();
    ConstraintViolation<?> next = iterator.next();
    return Response.fail(ErrorCodeEnum.INVALID_PARAMS.getCode(), next.getMessage());
}

四、具體場景示例

  1. 使用者的id
    1. 建立時不需要傳參
    2. 編輯時必須傳參
    3. 資料庫中,該欄位屬性是bigint primary not null auto_increment comment 'id';
  2. 使用者的name
    1. 建立時name必傳,不能是空或空白字元
    2. 編輯時name不可修改,不需要傳參
    3. 資料庫中,該欄位屬性是varchar(30) not null default '' comment '姓名';
  3. 使用者的電話phone
    1. 建立時phone必傳,不能是空或空白字元
    2. 編輯時phone可以修改,必傳
    3. 資料庫中,該欄位屬性是char(11) not null default '' comment '手機';
  4. 使用者的地址address
    1. 無論建立、編輯,都是選傳
    2. 資料庫中,該欄位屬性是varchar(30) not null default '' comment '地址';
  5. 使用者的出生年份year
    1. 無論建立、編輯,都是選傳
    2. 資料庫中,該欄位屬性是int not null default 0 comment '出生年份';
    3. 假設使用者出生年份在1970-2000年之間
  6. 使用者的性別gender
    1. 無論建立、編輯,都是選傳
    2. 資料庫中,該欄位屬性是tinyint not null default 0 comment '性別,1:男,2:女,0:未知';

Person程式碼示例

public class Person{
     
    @Min(value = 1, message = "缺少id", groups = {Update.class})
    private int id;
 
    @NotBlank(message = "缺少姓名", groups = {Insert.class})
    @Length(min = 2, max = 30, message = "姓名長度必須在2-30之間", groups = {Insert.class})
    private String name;
 
    @NotBlank(message = "缺少手機號", groups = {Insert.class, Update.class})
    @Pattern(regexp = "^1[0-9]{10}$", message = "手機號不合法", groups = {Insert.class, Update.class})
    private String phone;
 
    @Length(min = 3, max = 30, message = "地址長度必須在3-30之間", groups = {Insert.class, Update.class})
    private String address;
   
    @Range(min = 1970, max = 2000, message = "出生年份必須在1970-2000之間", groups = {Insert.class, Update.class})
    private Integer year;
 
    @ApiModelProperty(value = "性別")
    private GenderEnum genderEnum;
}

Person介面示例程式碼如下

@RestController
@RequestMapping("/person")
@Validated
public class PersonController extends BaseController {
 
    @Autowired
    private PersonLogic personLogic;
 
    @PostMapping("/createPerson")
    public Response<Void> createPerson(@Validated(Insert.class) @RequestBody Person person){
        personLogic.createPerson(person);
        return Response.ok();
    }
 
    @PostMapping("/editPerson")
    public Response<Void> editPerson(@Validated(Update.class) @RequestBody Person person){
        personLogic.editPerson(person);
        return Response.ok();
    }
 
    @GetMapping("/info")
    public Response<Person> getPersonById(@Min(value = 1, message = "缺少id") @RequestParam int id){
        return Response.ok().setData(personLogic.getPersonById(id));
    }
 
    @GetMapping("/listPersons")
    public Response<List<Person>> listPersons(@Valid @ModelAttribute PersonQueryParam queryParam){
        if(StringUtils.isNotBlank(queryParam.getGender())){
            queryParam.setGenderEnum(GenderEnum.findByString(queryParam.getGender()));
        }
        return Response.ok().setData(personLogic.listPersons(queryParam));
    }
}

查詢條件類程式碼示例如下

@Data
public class PersonQueryParam {
 
    @Length(min = 1, max = 1, message = "性別錯誤")
    @ApiModelProperty(value = "性別,值域:[男,女]")
    private String gender;
 
    @ApiModelProperty(value = "性別", hidden = true)
    private GenderEnum genderEnum;
 
    @Length(min = 1, max = 30, message = "模糊查詢姓名長度必須在1-30之間")
    private String namePattern;
}

以上