Java中使用註解校驗引數
阿新 • • 發佈:2021-02-10
一、引數校驗的註解
Java中引數校驗的註解來自三方面,分別是
- javax.validation:validation-api,對應包javax.validation.constraints
- org.springframework:spring-context,對應包org.springframework.validation
- org.hibernate:hibernate-validator,對應包org.hibernate.validator.constraints
1.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 |
|
@Pattern(regexp) | 限制必須符合regexp指定的正則表示式 | String |
@Future | 限制必須是一個將來的日期 | Date/Calendar |
@Past | 限制必須是一個過去的日期 | Date/Calendar |
@Valid | 校驗任何非原子型別,標記一個物件,表示校驗物件中被註解標記的物件(不支援分組功能) | 需要校驗成員變數的物件,比如@ModelAttribute標記的介面入參 |
2. hibernate-validator中的註解
註解 | 說明 | 適用型別 |
---|---|---|
@Length(min,max) | 限制String型別長度必須在min和max之間,包含min和max | String, not null時才校驗 |
@NotBlank | 驗證註解的元素值不是空白(即不是null,且包含非空白字元) | String |
@NotEmpty | 驗證註解的元素值不為null且不為空(即字串非null且長度不為0、集合型別大小不為0) |
|
@Range(min,max) | 驗證註解的元素值在最小值和最大值之間 |
|
@Email(regexp) | 驗證註解的字串符合郵箱的正則表示式,可以使用regexp自定義正則表示式 | String |
@CreditCardNumber | 驗證銀行借記卡、信用卡的卡號 | String(不能包含空格等特殊字元) |
3.spring-context中的註解
註解 | 說明 | 適用型別 |
---|---|---|
@Validated | 校驗非原子型別物件,或啟用類中原子型別引數的校驗(支援分組校驗;只校驗包含指定分組的註解引數) |
|
二、註解的啟用
- 方法中物件引數中成員變數校驗註解的生效條件
- @ModelAttribute標記的查詢條件類引數,需要同時用@Valid或@Validated標記,類中的註解校驗才會生效
- @RequestBody標記的請求體物件引數,需要同時用@Valid或@Validated標記,類中的註解校驗才會生效
- @Valid或@Validated標記在方法或方法所屬類上無效
- 方法中原子型別引數校驗註解的生效條件
- @Validated標記在方法所屬類上
- 按照分組啟用
- 在註解中使用groups新增啟用註解的分組
- 在@Validated中指定啟用的分組
三、註解使用的幾個注意事項
- 注意選傳變數的預設值
- 選傳變數沒有傳值時,校驗會使用變數的預設值
- 如果有指定的預設值,會使用指定的預設值進行校驗
- 如果沒有指定預設值,那麼使用型別預設值進行校驗
- 注意預設值能否通過校驗
- 選傳變數使用預設值時,注意例項化到db時是否為null及對null值的處理(StringUtils.defaultString)
- 注意基本資料型別與對應包裝型別之間,型別預設值的區別
- 注意非請求體入參中的列舉型別
- 列舉型別不能直接接受傳參繫結
- 使用String或int型別先接受繫結值,再轉成列舉變數
- 配合統一異常處理
/**
* 與@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());
}
四、具體場景示例
- 使用者的id
- 建立時不需要傳參
- 編輯時必須傳參
- 資料庫中,該欄位屬性是bigint primary not null auto_increment comment 'id';
- 使用者的name
- 建立時name必傳,不能是空或空白字元
- 編輯時name不可修改,不需要傳參
- 資料庫中,該欄位屬性是varchar(30) not null default '' comment '姓名';
- 使用者的電話phone
- 建立時phone必傳,不能是空或空白字元
- 編輯時phone可以修改,必傳
- 資料庫中,該欄位屬性是char(11) not null default '' comment '手機';
- 使用者的地址address
- 無論建立、編輯,都是選傳
- 資料庫中,該欄位屬性是varchar(30) not null default '' comment '地址';
- 使用者的出生年份year
- 無論建立、編輯,都是選傳
- 資料庫中,該欄位屬性是int not null default 0 comment '出生年份';
- 假設使用者出生年份在1970-2000年之間
- 使用者的性別gender
- 無論建立、編輯,都是選傳
- 資料庫中,該欄位屬性是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;
}
以上