无论是常规web开发还是服务端接口开发,出于安全性和系统健壮性的考虑,都免不了要对入参进行一系列校验。
没有用参数校验框架之前的画风是这样的(不上代码了,直接贴图)
对于这种原始校验给人的第一印象就是臃肿、无趣。当参数数量达到一个可观的级别我们写这种校验更能体会到其中的酸软,尤其是可能还会有手机号、邮箱等等稍复杂的校验。 专注于开发的我们怎么能被这种锁事所牵绊,束缚了手脚?!
用了参数校验框架后的画风是这样的
@Test
public void validate() {
Dog dto = Dog.builder()
.name("赵二狗")
.age(12)
.pnoneNum("123456789")
.weight(51D)
.build();
//一行代价搞定
ParamValidatorUtil.validate(dto);
}
}
@Builder
class Dog {
@NotNull(message = "姓名不能为空")
private String name;
@NotNull(message = "年龄不能为空")
@Max(value = 99,message = "年龄不合法")
private Integer age;
@NotNull(message = "手机号不能为空")
private String pnoneNum;
@DecimalMax(value = "50",message = "体重超标")
private Double weight;
}
结果立竿见影。原本啰里啰嗦又臭又长的代码现在只需要一行代码就可以搞定。前提是在需要校验的实体类上加上校验注解。
是Java EE 6 中的一项子规范,叫做BeanValidation,官方参考实现是hibernate Validator(与Hibernate ORM 没有关系),JSR 303 用于对Java Bean 中的字段的值进行验证。 JSR 303 – Bean Validation 规范
为 JavaBean 验证定义了相应的元数据模型和 API。缺省的元数据是 Java Annotations,通过使用 XML 可以对原有的元数据信息进行覆盖和扩展。在应用程序中,通过使用 Bean Validation 或是你自己定义的 constraint,例如
@NotNull
,@Max
,@ZipCode
, 就可以确保数据模型(JavaBean)的正确性。constraint 可以附加到字段,getter 方法,类或者接口上面。对于一些特定的需求,用户可以很容易的开发定制化的 constraint。Bean Validation 是一个运行时的数据验证框架,在验证之后验证的错误信息会被马上返回。
Hibernate Validator是 Bean Validation 的参考实现 . Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有一些附加的 constraint。如果想了解更多有关 Hibernate Validator 的信息,请查看 Hibernate Validator
注解 | 详细信息 |
---|---|
@Null |
|
@NotNull |
|
@AssertTrue |
|
@AssertFalse |
|
@Min(value) |
|
@Max(value) |
|
@DecimalMin(value) |
|
@DecimalMax(value) |
|
@Size(max, min) |
|
@Digits (integer, fraction) |
|
@Past |
|
@Future |
|
@Pattern(value) |
注解 | 详细信息 |
---|---|
@Email |
|
@Length |
|
@NotEmpty |
|
@Range |
一个 constraint 通常由 annotation 和相应的 constraint validator(校验器) 组成,它们是一对多的关系。也就是说可以有多个 constraint validator 对应一个 annotation。在运行时,Bean Validation 框架本身会根据被注释元素的类型来选择合适的 constraint validator 对数据进行验证。
有些时候,在用户的应用中需要一些更复杂的 constraint。Bean Validation 提供扩展 constraint 的机制。可以通过两种方法去实现,一种是组合现有的 constraint 来生成一个更复杂的 constraint,另外一种是开发一个全新的 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 "价格无效";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
Price
注解 由两个内置的 constraintDecimalMax
,DecimalMin
组合而成。
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {OrderStatusValidator.class})
public @interface OrderStatus {
String message() default "订单状态不合法";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
//用于校验OrderStatus注解 的校验器
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,"待确认"),
SUCCEEDED(2,"成功"),
ALLOCATED(3,"已排车"),
TOOK(4,"已取车"),
RETURNED(5,"已还车"),
SETTLED(6,"已结算"),
NONE(10,"无车"),
CANCELED(11,"已取消");
自定义校验注解必须指定 自定义校验器。
@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;
}
由此可见,用了这玩意儿,我们的开发变的简单高效更加放飞自我。当内置的注解无法满足我们的实际要求时,我们可以对之灵活的加以组合或拓展实现功能,从而做一个更加优秀的
api调用工程师
。
工具类封装
public class ParamValidatorUtil {
public static final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
/**
* 校验实体参数,返回第一条错误信息
*
* @param t
* @param <T>
* @return
*/
public static <T> String validateV2(T t) {
if (t == null){
return "参数不能为空!";
}
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理所当然会对此进行支持。SpringMVC模块中添加了自动校验,并将校验信息封装进了特定的类中。
以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";
}
}
参数Dog前需要加上@Validated注解,表明需要spring对其进行校验,而校验的信息会存放BindingResult对象中。在Controller中可以根据业务逻辑来决定具体的操作,通过全局异常类,跳转到错误页面。
//全局异常类
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResultVO handleMethodArgumentNotValidException(MethodArgumentNotValidException e){
BindingResult bindingResult = e.getBindingResult();
//返回一条错误信息
String msg = bindingResult.getFieldErrors().iterator().next().getDefaultMessage();
return ResultVOUtils.error(ResultEnum.PARAM_ERROR.getCode(), msg );
}
}
就写到这吧,搞了将近俩小时,该吃饭了。。。