有参数传递的地方都少不了参数校验。在web开发中,前端的参数校验是为了用户体验,后端的参数校验是为了安全。试想一下,如果在controller层中没有经过任何校验的参数通过service层、dao层一路来到了数据库就可能导致严重的后果,最好的结果是查不出数据,严重一点就是报错,如果这些没有被校验的参数中包含了恶意代码,那就可能导致更严重的后果。
org.springframework.boot
spring-boot-starter-validation
在controller层的参数校验可以分为两种场景:
- 单个参数校验
- 实体类参数校验
Validator 可以非常方便的制定校验规则,并自动帮你完成校验。首先在入参里需要校验的字段加上注解,每个注解对应不同的校验规则,并可制定校验失败后的信息
/**
* 参数校验测试 控制类
* @author oyc
*/
@RestController
@RequestMapping("user")
@Validated
public class RequestParamsValidatedController {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@GetMapping
public User test(@NotNull(message = "姓名不能为空") String name,
@NotNull(message = "年龄不能为空") @Max(value = 99, message = "不能大于200岁") Integer age) {
logger.info("name:" + name + " -age:" + age);
return new User(name, age);
}
}
/**
* 参数校验测试 控制类
* @author oyc
*/
@RestController
@RequestMapping("user")
@Validated
public class RequestParamsValidatedController {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@PostMapping
public User save(@Validated User user) {
logger.info(user.toString());
return user;
}
}
User实体类 :
package com.oycbest.springbootvalidated.vo;
import javax.validation.constraints.*;
import java.io.Serializable;
/**
* 用户实体类
* @author oyc
*/
public class User implements Serializable {
private String userId;
@NotNull(message = "用户名不能为空")
private String userName;
@NotNull(message = "年龄不能为空")
@Max(value = 100, message = "年龄不能大于100岁")
private int age;
@NotNull(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;
@NotNull(message = "电话号码不能为空")
private String phoneNumber;
public User(@NotNull(message = "用户名不能为空") String userName, int age) {
this.userName = userName;
this.age = age;
}
public User() {
}
public User(String userId, @NotNull(message = "用户名不能为空") String userName, int age, String email, String phoneNumber) {
this.userId = userId;
this.userName = userName;
this.age = age;
this.email = email;
this.phoneNumber = phoneNumber;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
@Override
public String toString() {
return "User{" +
"userId='" + userId + '\'' +
", userName='" + userName + '\'' +
", age=" + age +
", email='" + email + '\'' +
", phoneNumber='" + phoneNumber + '\'' +
'}';
}
}
校验规则和错误提示信息配置完毕后,接下来只需要在接口需要校验的参数上加上 @Valid 注解,并添加 BindResult 参数即可方便完成验证。这样当请求数据传递到接口的时候 Validator 就自动完成校验了,校验的结果就会封装到 BindingResult 中去,如果有错误信息我们就直接返回给前端,业务逻辑代码也根本没有执行下去。
/**
* 使用BindingResult返回异常
*
* @param user
* @param bindingResult
* @return
*/
@GetMapping("validated")
public Object getBindingResult(@Validated User user, BindingResult bindingResult) {
// 如果有参数校验失败,会将错误信息封装成对象组装在BindingResult里
for (ObjectError error : bindingResult.getAllErrors()) {
return error.getDefaultMessage();
}
logger.info(user.toString());
return user;
}
使用 Validator + BindingResult 已经是非常方便实用的参数校验方式了,在实际开发中也有很多项目就是这么做的,不过这样还是不太方便,因为你每写一个接口都要添加一个 BindingResult 参数,然后再提取错误信息返回给前端。这样有点麻烦,并且重复代码很多(尽管可以将这个重复代码封装成方法),使用Validator + 自动抛出异常 模式,可以去掉BindingResult。
package com.oycbest.springbootvalidated.exception;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.util.List;
import java.util.Set;
/**
* 全局异常处理
*
* @author oyc
*/
@ControllerAdvice
@Component
public class GlobalExceptionHandler {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
@ExceptionHandler
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
public String handle(ValidationException exception) {
logger.error("请求异常:" + exception.getMessage());
if (exception instanceof ConstraintViolationException) {
ConstraintViolationException exs = (ConstraintViolationException) exception;
Set> violations = exs.getConstraintViolations();
for (ConstraintViolation item : violations) {
//打印验证不通过的信息
logger.error("请求异常:" + item.getMessage());
}
}
return "请求异常: " + exception.getMessage();
}
@ResponseBody
@ExceptionHandler(value = BindException.class)
public String bindException(Exception e) {
if (e instanceof BindException) {
BindException exs = (BindException) e;
List fieldErrors = exs.getFieldErrors();
for (FieldError item : fieldErrors) {
logger.error("请求异常:" + item.getDefaultMessage());
}
}
logger.error("数据绑定异常:" + e.getMessage());
return "数据绑定异常";
}
@ResponseBody
@ExceptionHandler(value = Exception.class)
public String defaultException(Exception e) {
logger.error("请求异常:" + e.getMessage());
return "请求异常 " + e.getMessage();
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public String MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
// 注意哦,这里返回类型是自定义响应体
return objectError.getDefaultMessage();
}
}
注解 |
功能 |
@AssertFalse |
可以为null,如果不为null的话必须为false |
@AssertTrue |
可以为null,如果不为null的话必须为true |
@DecimalMax |
设置不能超过最大值 |
@DecimalMin |
设置不能超过最小值 |
@Digits |
设置必须是数字且数字整数的位数和小数的位数必须在指定范围内 |
@Future |
日期必须在当前日期的未来 |
@Past |
日期必须在当前日期的过去 |
@Max |
最大不得超过此最大值 |
@Min |
最大不得小于此最小值 |
@NotNull |
不能为null,可以是空 |
@Null |
必须为null |
@Pattern |
必须满足指定的正则表达式 |
@Size |
集合、数组、map等的size()值必须在指定范围内 |
|
必须是email格式 |
@Length |
长度必须在指定范围内 |
@NotBlank |
字符串不能为null,字符串trim()后也不能等于“” |
@NotEmpty |
不能为null,集合、数组、map等size()不能为0;字符串trim()后可以等于“” |
@Range |
值必须在指定范围内 |
@URL |
必须是一个URL |
通过 Validator + 自动抛出异常来完成了方便的参数校验;
通过全局异常处理 + 自定义异常完成了异常操作的规范;
通过数据统一响应完成了响应数据的规范。