一、SpringBoot之参数校验
1、SpringBoot提供的参数校验注解
除了@NotEmpty
和@NotBlank
将 null 值认为是非法的之外,其它注解如@Size
, @Max
, @Min
等都将 null 认为是有效的,如果不允许 null 值,则需要额外添加@NotNull
注解。
注解 | 说明 |
| 字符串、集合、Map、数组等不能为 null 或空 |
| 字符串不能为 null,且至少包含一个非空字符 |
| 任意类型 不能为 null |
| 字符串、集合、Map、数组等元素的个数必须在指定的 min 和 max 范围内 |
| 字符串是有效的邮箱 |
| 字符串、整数、浮点数是一个数字,参数 integer 表示整数位的数字个数,fraction 表示小数位的数字个数 |
| 字符串是有效的信用卡数字,不校验信用卡本身的有效性 |
| 布尔类型必须是 true,null 值被认为是有效的 |
| 整数、浮点数必须小于等于指定的最大值 |
| 整数、浮点数必须大于等于指定的最小值 |
| 字符串、数字必须在指定的 min 和 max 范围内 |
| 字符串必须匹配指定的正则表达式 |
@Data
public class Employee implements Serializable {
private static final long serialVersionUID = -8224860450904540019L;
@NotEmpty(message = "名字不能为空")
@UTF8Size(max = 16, message = "name should be short than 128")
private String name;
@Email
private String email;
@NotBlank(message = "city is required")
@Size(max = 128, message = "city should be short than 128")
private String city;
@CreditCardNumber(message = "invalid credit card number")
private String ccNumber;
@Pattern(regexp = "^(0[1-9]|1[0-2])([\\\\/])([1-9][0-9])$", message = "required format MM/YY")
private String ccExpiration;
@Digits(integer = 3, fraction = 0, message = "invalid CVV")
private String ccCVV;
}
2、自定义校验注解
@Size
并不能支持中文字符,可以自定义如下然后在需要校验字符(中英文)的字段上使用@UTF8Size
即可。
@Documented
@Constraint(validatedBy = Utf8SizeValidator.class)
@Target({METHOD, FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface UTF8Size {
String message() default "{javax.validation.constraints.Size.message}";
int min() default 0;
int max() default Integer.MAX_VALUE;
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class Utf8SizeValidator implements ConstraintValidator<UTF8Size, String> {
private int maxCharSize;
@Override
public void initialize(UTF8Size constraintAnnotation) {
this.maxCharSize = constraintAnnotation.max();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (Objects.isNull(value)) {
return true;
}
return value.getBytes(Charset.forName("GB18030")).length <= maxCharSize;
}
}
二、字段校验的使用
使用 Bean Validation API 的@Valid
注解,或者 Spring Context 提供的@Validated
注解启用对 Bean 的校验。主要区别是,@Validated
是@Valid
的变体,支持分组校验(validation groups)。
1、这种写法会将异常信息抛给全局异常处理
@PostMapping("/emp")
public String addEmploy(@RequestBody @Valid Employee employee) {
log.info("employee to create: {}", employee);
String employeeId = UUID.randomUUID().toString();
return employeeId;
}
2、如果需要捕获参数校验的异常结果,写法如下
这种写法不会将异常处理的结果返回给全局异常处理
@PostMapping()
UserInfo addUser(@RequestBody @Valid UserInfo userInfo, BindingResult bindingResult) {
userService.addUser(userInfo);
return userInfo;
}
三、字段校验的全局异常处理
package com.example.springboot.exception;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
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 java.util.stream.Collectors;
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
public String handleMethodArgumentNotValidException(
MethodArgumentNotValidException exception) {
log.error("method argument not valid: {}", exception.getMessage());
String errorMessage = "field error";
BindingResult bindingResult = exception.getBindingResult();
if (bindingResult.hasErrors()) {
errorMessage = bindingResult.getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(" | "));
}
return errorMessage;
}
}
四、分组校验
相同字段在不同场景需要不同的验证策略
1、组接口
public class GroupVaildDTO {
public interface SaveGroup extends Default {}
public interface UpdateGroup extends Default {}
}
2、校验的JavaBean属性
public class GroupsValidForm {
@Null(message = "id必须为null", groups = {GroupVaildDTO.SaveGroup.class})
@NotNull(message = "id不能为null", groups = {GroupVaildDTO.UpdateGroup.class})
private Integer id;
@NotBlank(message = "用户名不能为空")
private String userName;
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("GroupsValidForm{");
sb.append("id=").append(id);
sb.append(", userName='").append(userName).append('\'');
sb.append('}');
return sb.toString();
}
}
3、Controller层分组校验的使用
相同字段在不同场景需要不同的验证策略
@PostMapping(value = "update")
public ServerResponse update(@RequestBody @Validated(value = GroupVaildDTO.UpdateGroup.class)
GroupsValidForm groupsValidForm, BindingResult results) {
if (results.hasErrors()) {
return ServerResponse.createByErrorMessage(results.getFieldError().getDefaultMessage());
}
return ServerResponse.createBySuccess();
}