数据的校验是交互式网站一个不可或缺的功能,前端的js校验可以涵盖大部分的校验职责,如用户名唯一性,生日格式,邮箱格式校验等等常用的校验。但是为了避免用户绕过浏览器,使用http工具直接向后端请求一些违法数据,服务端的数据校验也是必要的,可以防止脏数据落到数据库中,如果数据库中出现一个非法的邮箱格式,也会让运维人员头疼不已。可以使用本文将要介绍的validation来对数据进行校验。
spring-web模块使用了hibernate-validation,并且databind模块也提供了相应的数据绑定功能。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
我们只需要引入spring-boot-starter-web依赖即可,如果查看其子依赖,可以发现如下的依赖:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
System.out.println("Start app success.");
}
}
public class Person {
@NotEmpty(message = "name不能为空")
private String name;
@Range(min = 0, max = 100, message = "age不能大于100小于0")
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
springmvc为我们提供了自动封装表单参数的功能,一个添加了参数校验的典型controller如下所示。
@RequestMapping("/test")
public String valid(@Validated Person person, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
for (FieldError fieldError : bindingResult.getFieldErrors()) {
System.out.println(fieldError);
}
return "fail";
}
return "success";
}
值得注意的地方:
启动容器测试结果如下:
测试: http://localhost:8080/valid?age=105&name=steven
Field error in object 'person' on field 'age': rejected value [105]; codes [Range.person.age,Range.age,Range.int,Range]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [person.age,age]; arguments []; default message [age],100,0]; default message [age不能大于100小于0]
前面那种方式处理校验错误,略显复杂,而且一般网站都会对请求错误做统一的404页面封装,如果数据校验不通过,则Spring boot会抛出BindException异常,我们可以捕获这个异常并使用Result封装返回结果。通过@RestControllerAdvice定义异常捕获类。
Controller类:
@RequestMapping(value = "valid", method = RequestMethod.GET)
public String valid(@Validated Person person) {
System.out.println(person);
return "success";
}
统一异常处理类:
@RestControllerAdvice
public class BindExceptionHanlder {
@ExceptionHandler(BindException.class)
public String handleBindException(HttpServletRequest request, BindException exception) {
List<FieldError> allErrors = exception.getFieldErrors();
StringBuilder sb = new StringBuilder();
for (FieldError errorMessage : allErrors) {
sb.append(errorMessage.getField()).append(": ").append(errorMessage.getDefaultMessage()).append(", ");
}
System.out.println(sb.toString());
return sb.toString();
}
}
测试: http://localhost:8080/valid?age=105&name=steven
输出:age: age不能大于100小于0,
@Documented
@Constraint(validatedBy = NameValidationValidator.class)
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RUNTIME)
public @interface NameValidation {
String message() default "不是合法的名字";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
@Target({PARAMETER, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Documented
@interface List {
NameValidation[] value();
}
}
public class NameValidationValidator implements ConstraintValidator<NameValidation, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if ("steven".equalsIgnoreCase(value)) {
return true;
}
String defaultConstraintMessageTemplate = context.getDefaultConstraintMessageTemplate();
System.out.println("default message :" + defaultConstraintMessageTemplate);
//禁用默认提示信息
//context.disableDefaultConstraintViolation();
//设置提示语
//context.buildConstraintViolationWithTemplate("can not contains blank").addConstraintViolation();
return false;
}
}
@NotEmpty(message = "name不能为空")
@NameValidation
private String name;
测试: http://localhost:8080/valid?age=105&name=lxy
输出:age: age不能大于100小于0, name: 不是合法的名字,