無論是常規(guī)web開發(fā)還是服務(wù)端接口開發(fā)拳缠,出于安全性和系統(tǒng)健壯性的考慮墩新,都免不了要對(duì)入?yún)⑦M(jìn)行一系列校驗(yàn)。
沒有用參數(shù)校驗(yàn)框架之前的畫風(fēng)是這樣的(不上代碼了脊凰,直接貼圖)
對(duì)于這種原始校驗(yàn)給人的第一印象就是臃腫、無趣狸涌。當(dāng)參數(shù)數(shù)量達(dá)到一個(gè)可觀的級(jí)別我們寫這種校驗(yàn)更能體會(huì)到其中的酸軟切省,尤其是可能還會(huì)有手機(jī)號(hào)、郵箱等等稍復(fù)雜的校驗(yàn)帕胆。 專注于
開發(fā)的我們?cè)趺茨鼙贿@種鎖事所牽絆朝捆,束縛了手腳?懒豹!
用了參數(shù)校驗(yàn)框架后的畫風(fēng)是這樣的
@Test
public void validate() {
Dog dto = Dog.builder()
.name("趙二狗")
.age(12)
.pnoneNum("123456789")
.weight(51D)
.build();
//一行代價(jià)搞定
ParamValidatorUtil.validate(dto);
}
}
@Builder
class Dog {
@NotNull(message = "姓名不能為空")
private String name;
@NotNull(message = "年齡不能為空")
@Max(value = 99,message = "年齡不合法")
private Integer age;
@NotNull(message = "手機(jī)號(hào)不能為空")
private String pnoneNum;
@DecimalMax(value = "50",message = "體重超標(biāo)")
private Double weight;
}
結(jié)果立竿見影芙盘。原本啰里啰嗦又臭又長(zhǎng)的代碼現(xiàn)在只需要一行代碼就可以搞定。前提是在需要校驗(yàn)的實(shí)體類上加上校驗(yàn)注解脸秽。
是Java EE 6 中的一項(xiàng)子規(guī)范儒老,叫做BeanValidation,官方參考實(shí)現(xiàn)是hibernate Validator(與Hibernate ORM 沒有關(guān)系)记餐,JSR 303 用于對(duì)Java Bean 中的字段的值進(jìn)行驗(yàn)證驮樊。 JSR 303 – Bean Validation 規(guī)范
為 JavaBean 驗(yàn)證定義了相應(yīng)的元數(shù)據(jù)模型和 API。缺省的元數(shù)據(jù)是 Java Annotations片酝,通過使用 XML 可以對(duì)原有的元數(shù)據(jù)信息進(jìn)行覆蓋和擴(kuò)展囚衔。在應(yīng)用程序中,通過使用 Bean Validation 或是你自己定義的 constraint雕沿,例如
@NotNull
,@Max
,@ZipCode
练湿, 就可以確保數(shù)據(jù)模型(JavaBean)的正確性。constraint 可以附加到字段审轮,getter 方法肥哎,類或者接口上面。對(duì)于一些特定的需求断国,用戶可以很容易的開發(fā)定制化的 constraint贤姆。Bean Validation 是一個(gè)運(yùn)行時(shí)的數(shù)據(jù)驗(yàn)證框架,在驗(yàn)證之后驗(yàn)證的錯(cuò)誤信息會(huì)被馬上返回稳衬。
Hibernate Validator是 Bean Validation 的參考實(shí)現(xiàn) . Hibernate Validator 提供了 JSR 303 規(guī)范中所有內(nèi)置 constraint 的實(shí)現(xiàn)霞捡,除此之外還有一些附加的 constraint。如果想了解更多有關(guān) Hibernate Validator 的信息薄疚,請(qǐng)查看 Hibernate Validator
注解 | 詳細(xì)信息 |
---|---|
@Null |
|
@NotNull |
|
@AssertTrue |
|
@AssertFalse |
|
@Min(value) |
|
@Max(value) |
|
@DecimalMin(value) |
|
@DecimalMax(value) |
|
@Size(max, min) |
|
@Digits (integer, fraction) |
|
@Past |
|
@Future |
|
@Pattern(value) |
注解 | 詳細(xì)信息 |
---|---|
@Email |
|
@Length |
|
@NotEmpty |
|
@Range |
一個(gè) constraint 通常由 annotation 和相應(yīng)的 constraint validator(校驗(yàn)器) 組成,它們是一對(duì)多的關(guān)系呈枉。也就是說可以有多個(gè) constraint validator 對(duì)應(yīng)一個(gè) annotation趁尼。在運(yùn)行時(shí),Bean Validation 框架本身會(huì)根據(jù)被注釋元素的類型來選擇合適的 constraint validator 對(duì)數(shù)據(jù)進(jìn)行驗(yàn)證猖辫。
有些時(shí)候酥泞,在用戶的應(yīng)用中需要一些更復(fù)雜的 constraint。
Bean Validation 提供擴(kuò)展 constraint 的機(jī)制悯姊。可以通過兩種方法去實(shí)現(xiàn)贩毕,一種是組合現(xiàn)有的 constraint 來生成一個(gè)更復(fù)雜的 constraint悯许,另外一種是開發(fā)一個(gè)全新的 constraint。
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {})
@DecimalMax("9999")
@DecimalMin("0.0")
public @interface Price {
String message() default "價(jià)格無效";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
Price
注解 由兩個(gè)內(nèi)置的 constraintDecimalMax
辉阶,DecimalMin
組合而成岸晦。
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {OrderStatusValidator.class})
public @interface OrderStatus {
String message() default "訂單狀態(tài)不合法";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
//用于校驗(yàn)OrderStatus注解 的校驗(yàn)器
public class OrderStatusValidator implements ConstraintValidator<OrderStatus,Integer> {
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return EnumUtils.containsVal(OrderStatusEnum.values(),value);
}
@Override
public void initialize(OrderStatus constraintAnnotation) {
//可以在此做一些初始化工作 例如 從獲取注解中的某些值
}
}
public enum OrderStatusEnum implements IEnum{
CONFIRMING(1,"待確認(rèn)"),
SUCCEEDED(2,"成功"),
ALLOCATED(3,"已排車"),
TOOK(4,"已取車"),
RETURNED(5,"已還車"),
SETTLED(6,"已結(jié)算"),
NONE(10,"無車"),
CANCELED(11,"已取消");
自定義校驗(yàn)注解必須指定 自定義校驗(yàn)器。
@Test
public void validate() {
Dog dto = Dog.builder()
.amount(250D)
.orderStatus(22)
.build();
ParamValidatorUtil.validate(dto);
}
}
@Builder
class Dog {
@Price
private Double amount;
@OrderStatus
private Integer orderStatus;
}
由此可見睛藻,用了這玩意兒,我們的開發(fā)變的簡(jiǎn)單高效更加放飛自我邢隧。當(dāng)內(nèi)置的注解無法滿足我們的實(shí)際要求時(shí)店印,我們可以對(duì)之靈活的加以組合或拓展實(shí)現(xiàn)功能,從而做一個(gè)更加優(yōu)秀的
api調(diào)用工程師
倒慧。
工具類封裝
public class ParamValidatorUtil {
public static final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
/**
* 校驗(yàn)實(shí)體參數(shù),返回第一條錯(cuò)誤信息
*
* @param t
* @param <T>
* @return
*/
public static <T> String validateV2(T t) {
if (t == null){
return "參數(shù)不能為空按摘!";
}
Set<ConstraintViolation<T>> validationSet = validator.validate(t, Default.class);
String message = null;
if (validationSet != null && validationSet.size() > 0) {
ConstraintViolation<T> violation = validationSet.iterator().next();
message = violation.getMessage();
}
return message;
}
public static <T> void validate(T t) {
String msg = validateV2(t);
if (msg != null) {
throw new BizException(msg);
}
}
spring提供的支持
這么好用的功能,萬能膠spring理所當(dāng)然會(huì)對(duì)此進(jìn)行支持纫谅。SpringMVC模塊中添加了自動(dòng)校驗(yàn)炫贤,并將校驗(yàn)信息封裝進(jìn)了特定的類中。
以SpringBoot為例付秕,只需要引入spring-boot-starter-web starter即可兰珍。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
@RestController
public class TestController {
@RequestMapping("/test")
public String test(@Validated Dog dog) {
……
return "success";
}
}
參數(shù)Dog前需要加上@Validated注解,表明需要spring對(duì)其進(jìn)行校驗(yàn)询吴,而校驗(yàn)的信息會(huì)存放BindingResult對(duì)象中掠河。在Controller中可以根據(jù)業(yè)務(wù)邏輯來決定具體的操作亮元,通過全局異常類,跳轉(zhuǎn)到錯(cuò)誤頁面唠摹。
//全局異常類
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResultVO handleMethodArgumentNotValidException(MethodArgumentNotValidException e){
BindingResult bindingResult = e.getBindingResult();
//返回一條錯(cuò)誤信息
String msg = bindingResult.getFieldErrors().iterator().next().getDefaultMessage();
return ResultVOUtils.error(ResultEnum.PARAM_ERROR.getCode(), msg );
}
}
就寫到這吧爆捞,搞了將近倆小時(shí),該吃飯了勾拉。煮甥。。