对请求参数进行检验,这在web中开始经常能碰到,如果用一个个if/else去做判断,相信这样的代码可读性会比较差
JSR-303 是java为bean数据合法性校验提供的标准框架,是Java EE6中的一项子规范,叫做BeanValidation。JSR303通过在Bean属性上标注@NotNull、@Max等标准的注解指定校验规则,并通过这些标准的验证接口对Bean进行验证。
规定一些检验规范即校验注解,如@Null, @NotNull, @Pattern,位于javax.validation.constraints包下,只提供规范 不提供实现。
在Spring中,有两种方式可以验证输入,一是利用Spring自带的验证框架,二是利用JSR-303的实现,一般建议使用JSR-303的实现,比如Hibernate-Validator。
Hibernate-Validator是JSR-303的实现。Hibernate Validator提供了JSR-303规范中所有内置constraint的实现,除此之外还有一些附加的constraint,如@Email, @Length, @Range等,位于org.hibernate,validator.constraints包下。
spring-boot-starter-web包里面已经有了hibernate-vlidator包,不需要额外引用hibernate validator依赖。
同时Spring为了给开发者提供便捷,对Hibernate-Validator进行了二次封装,封装了LocalValidatorFactorBean作为validator的实现,这个类兼容了Spring的Validation体系和Hibernate的Validation体系,LocalValidatorFactorBean已经成为了Validator的默认实现。
说明:JSR-349是JSR-303的升级版,添加了一些新特性
如下图,是spring boot 2.1.1中hibernate依赖情况:
属性 | 描述 | 举例 |
@AssertTrue | 应用于boolean属性,该属性值必须为true | @AssertTrue boolean isOkay; |
@AssertFalse | 应用于boolean属性,该属性值必须为false | @AssertFalse boolean isOkay; |
@DecimalMax | 只能小于或等于指定值 | @DecimalMax("1.1") BigDecimal price; |
@DecimalMin | 只能大于或等于指定值 | @DecimalMin("1.1") BigDecimal price; |
@Digits | 该属性值必须在指定范围内,interger属性定义该数值的最大整数部分,fraction属性定义该数值的最大 小数部分 | @Digits(integer=5, fraction=2) BigDecimal price; |
@Future | 检查该字段是否是属于未来的日期 | @Future Date shippingDate; |
@Max | 该字段的值只能小于或等于该值 | @Max(20) int age; |
@Min | 该字段的值只能大于或等于该值 | @Min(20) int age; |
@NotNull | 该字段不能为Null | @NotNull String name; |
@Null | 该字段必须是Null | @Null String dest; |
@Past | 该字段必须是过去的一个日期 | @Past Date birthDate; |
@Size | 检查该字段的size是否在min和max之间,可以是字符串、数组、集合、Map等 | @Size(min=2, max=10) String description; |
@Pattern | 该属性值必须与指定的常规表达式相匹配 | @Pattern(regexp="\\d{3}") String areaCode; |
@NotBlank | 只用于String, 不能为Null且trim()之后size>0 | @NotBlank String src; |
@NotEmpty | 不能为Null,且size>0 | @NotEmpty String src; |
被注释的元素必须是电子邮箱地址 | ||
@Length | 被注释的字符串String 大小必须在指定范围内 | @Length(min=6, max=12, message="密码长度必须在6~12") String src; |
@Range | BigDecimal,BigInteger,CharSequence, byte, short, int, long等原子类型和包装类型,验证注解的元素值在最小值和最大值之间 | |
@Valid | 指定递归验证(下篇讲)关联的对象; 如用户对象中有个地址对象属性,如果想在验证用户对象时一起验证地址对象的话,在地址对象上加@Valid注解即可级联验证 |
需要检验的Bean定义:
public class StudentBean implements Serializable{
@NotBlank(message = "用户名不能为空")
private String name;
@Min(value = 18, message = "年龄不能小于18岁")
private Integer age;
@Pattern(regexp = "^((13[0-9])|(14[5,7,9])|(15([0-3]|[5-9]))|(166)|(17[0,1,3,5,6,7,8])|(18[0-9])|(19[8|9]))\\d{8}$", message = "手机号格式错误")
private String phoneNum;
@Email(message = "邮箱格式错误")
private String email;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getPhoneNum() {
return phoneNum;
}
public void setPhoneNum(String phoneNum) {
this.phoneNum = phoneNum;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
返回错误字段定义:
public class ArgumentsInvalidResponseBean {
private String argumentName;
private String exceptionMsg;
public ArgumentsInvalidResponseBean() {
}
public ArgumentsInvalidResponseBean(String argumentName, String exceptionMsg) {
this.argumentName = argumentName;
this.exceptionMsg = exceptionMsg;
}
public String getArgumentName() {
return argumentName;
}
public void setArgumentName(String argumentName) {
this.argumentName = argumentName;
}
public String getExceptionMsg() {
return exceptionMsg;
}
public void setExceptionMsg(String exceptionMsg) {
this.exceptionMsg = exceptionMsg;
}
}
全局异常处理:
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
public List methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException ex){
System.out.println("===================methodArgumentNotValidExceptionHandler Occur============");
List argumentsInvalidResponseBeanList = new ArrayList<>();
for (FieldError error : ex.getBindingResult().getFieldErrors()){
ArgumentsInvalidResponseBean bean = new ArgumentsInvalidResponseBean();
bean.setArgumentName(error.getField());
bean.setExceptionMsg(error.getDefaultMessage());
argumentsInvalidResponseBeanList.add(bean);
}
return argumentsInvalidResponseBeanList;
}
测试代码:
@RestController
public class CheckController {
@PostMapping("stu")
public String addStu(@Valid @RequestBody StudentBean studentBean){
return "add student success";
}
}
在PostMan中测试:
注意这里,年龄和邮箱是有错误的,运行后查看返回值,具体如下:
新建注解类 MyConstraint:
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = MyConstraintValidator.class)
public @interface MyConstraint {
String message() default "这是一个自定义注解,检测输入是否大写";
Class>[] groups() default {};
Class extends Payload>[] payload() default {};
}
实现MyConstraintValidator:
public class MyConstraintValidator implements ConstraintValidator {
@Override
public void initialize(MyConstraint constraintAnnotation) {
System.out.println("+++++++++++++myConstraint init");
}
@Override
public boolean isValid(String o, ConstraintValidatorContext constraintValidatorContext) {
if (!o.equals(o.toUpperCase())){
System.out.println("输入信息必须是大写");
return false;
}
return true;
}
}
当输入信息不是全大写字符时,则检验不通过
使用:
在上面StudentBean中加入检验
@MyConstraint
private String className;
测试:
结果如下:
这时说明自定义检验可以工作了
当请求中 Content-Type为“application/x-www-form-urlencoded”时,Spring会把数据解析成 web form data而非json,会使用 FormHttpMessageConverter来转换post的body 并且异常转为BindException。
如果我们想要Spring把POST数据认为是json并且使用MappingJackson2HttpMessageConverter来解析数据,可以把Content-Type设置成 application/json