本文有参考这篇:https://blog.csdn.net/weixin_41146599/article/details/81908532
在开发中经常需要写一些字段校验的代码,比如字段非空,字段长度限制,邮箱格式验证等等。 会导致如下情况:
- 验证代码繁琐
- 方法内代码显得冗长
- 修改相同逻辑验证代码时需要修改涉及到同样逻辑的所有地方
spring-boot-starter-web 包里面有hibernate-validator包,不需要再引入依赖。
使用@Valid对参数进行校验在使用对象进行参数接收时,我们可以对参数进行校验录入我们可以对属性username和password加上如下注解
public class User{
@NotBlank(message = "{user.name.notBlank}",groups = {GroupA.class})
private String username; // 用户名
@Max(value = 999999,message = "超过最大数值")
@Min(value = 000000,message = "密码设定不正确")
@NotNull(message = "密码不能为空")
private String password; // 密码
}
然后我们给Controller层的方法中接收的对象前加入@Valid注解,并在参数中加入BindingResult来获取错误信息。 在逻辑处理中我们判断BindingResult知否含有错误信息,如果有错误信息,则直接返回错误信息。
// 添加用户
@PostMapping("/createuser") // 多个 @Valid 则对应多个 BindingReslt
public String createUser(@Valid User user, BindingResult bindingResult){
if (bindingResult.hasErrors()){
return bindingResult.getFieldError().getDefaultMessage();
}else{
userService.createUser(userInfo.getTel(),userInfo.getPassWord(); return "OK";
}
}
Hibernate validator的校验只能对Object的属性进行校验,不能对单个的参数进行校验,spring 在此基础上进行了扩展,添加了++MethodValidationPostProcessor拦截器++后可以实现对方法参数的校验。
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
@Validated // 加入注释
@RestController
@RestMapping("/validate")
public class ValidateController {
}12345
@GetMapping("/paramcheck")
public String paramCheck(@Length(min = 10) @RequestParam String name) {
System.out.println(name);
return null;
}
{
"timestamp": 1476108200558,
"status": 500,
"error": "Internal Server Error",
"exception": "javax.validation.ConstraintViolationException",
"message": "No message available",
"path": "/paramcheck"
}
通常的校验会验证所有属性并返回一个错误消息集合,而快速失败模式通常按顺序验证到第一个字段不符合验证要求时,就可以直接拒绝请求了。
Hibernate Validator有以下两种验证模式:
两种验证模式配置方式:
就是在SpringBoot中配置一个Bean
@Configuration
public class ValidatorConfiguration {
@Bean
public Validator validator(){
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
.configure() // true-快速失败返回模式 false-普通模式
.addProperty( "hibernate.validator.fail_fast", "true" )
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
return validator;
}
}
有这样一种场景,新增用户信息的时候,不需要验证userId(因为系统生成);
修改的时候则需要验证userId,这时候可用分组验证功能。
public interface GroupA {
}
public interface GroupB {
}
public class Person {
@NotBlank
@Range(min = 1,max = Integer.MAX_VALUE,message = "必须大于0",groups = {GroupA.class})
private Integer userId;// 用户id
@NotBlank
@Length(min = 4,max = 20,message = "必须在[4,20]",groups = {GroupB.class})
private String userName;// 用户名
@NotBlank
@Range(min = 0,max = 100,message = "年龄必须在[0,100]",groups={Default.class})
private Integer age;// 年龄
@NotNull(message = "地址不能为空",groups = {GroupB.class})
private String address;// 地址
}
如上Person所示,3个分组分别验证字段如下:
GroupA验证字段userId;
GroupB验证字段userName,address;
Default验证字段age(默认分组)
A情景
只验证GroupA或GroupB标记的分组:
(可叠加 GroupA,GroupB)
// 添加用户
@PostMapping("/createuser2") // 多个@Valid对应则多个BindingReslt
public String createuser2(@Validated(GroupA.class) User user, BindingResult bindingResult){
if (bindingResult.hasErrors()){
return bindingResult.getFieldError().getDefaultMessage();
}else{
userService.createUser(userInfo.getTel(),userInfo.getPassWord());
return "OK";
}
}12345678910
B情景
指定验证组的顺序:
除了按组指定是否验证之外,还可以指定组的验证顺序,前面组验证不通过的,后面组不进行验证:
(GroupA>GroupB>Default)
@GroupSequence({GroupA.class, GroupB.class, Default.class})
public interface GroupOrder {
}
在resource 目录下新建提示信息配置文件“ValidationMessages.properties“
注意:名字必须为“ValidationMessages.properties“ 因为SpringBoot自动读取classpath中的ValidationMessages.properties里的错误信息
@NotBlank(message = "{user.name.notBlank}")
private String name;
Controller 层的代码,就不需要进行异常处理了:
主要是MethodArgumentNotValidException这个异常不用每个Controller都来一遍处理的代码了
/**
* @Author: wei1
* @Date: Create in 2018/9/14 19:55
* @Description: @ControllerAdvice + @ExceptionHandler 使用 GlobalExceptionHandler 全局处理 Controller 层异常的示例
* 参考:https://blog.csdn.net/kinginblue/article/details/70186586
*/
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 处理所有不可知的异常
*
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
@ResponseBody
public Map<String, Object> handleException(Exception e) throws Exception {
LOGGER.error(e.getMessage(), e);
// 由于Exception是异常的父类,如果你的项目中出现过在自定义异常中使用@ResponseStatus的情况,
// 你的初衷是碰到那个自定义异常响应对应的状态码,而这个控制器增强处理类,会首先进入,并直接
// 返回,不会再有@ResponseStatus的事情了,这里为了解决这种纰漏,我提供了一种解决方式。
if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) {
throw e;
}
Map<String, Object> result = new HashMap<>();
result.put("Error Message: ", e.getMessage());
return result;
}
/**
* 处理所有业务异常
*
* @param e
* @return
*/
@ExceptionHandler(UserNotExistException.class)
@ResponseBody
public Map<String, Object> handleBusinessException(UserNotExistException e) {
LOGGER.error(e.getMessage(), e);
Map<String, Object> result = new HashMap<>();
result.put("Error Message: ", e.getMessage());
return result;
}
/**
* 处理所有接口数据验证异常
*
* @param e
* @return
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
public Map<String, Object> handleMethodArgumentNotValidException(
MethodArgumentNotValidException e) {
LOGGER.error(e.getMessage(), e);
Map<String, Object> result = new HashMap<>();
String messages = "";
for (ObjectError objectError : e.getBindingResult().getAllErrors()) {
messages += objectError.getDefaultMessage() + ",";
}
result.put("Error Message: ", messages);
return result;
}
}
JSR303中内置的注解
注解名 | 作用 |
---|---|
@Null | 元素必须为 null |
@NotNull | 元素必须不为 null |
@AssertTrue | 元素必须为 true |
@AssertFalse | 的元素必须为 false |
@Min(value) | 元素必须是一个数字,其值必须大于等于指定的最小值 |
@Max(value) | 元素必须是一个数字,其值必须小于等于指定的最大值 |
@DecimalMin(value) | 元素必须是一个数字,其值必须大于等于指定的最小值 |
@DecimalMax(value) | 元素必须是一个数字,其值必须小于等于指定的最大值 |
@Size(max=, min=) | 元素的大小必须在指定的范围内 |
@Digits (integer, fraction) | 元素必须是一个数字,其值必须在可接受的范围内 |
@Past | 元素必须是一个过去的日期 |
@Future | 元素必须是一个将来的日期 |
@Pattern(regex=,flag=) | 元素必须符合指定的正则表达式 |
Hibernate Validator对JSR303的扩展注解
注解名 | 作用 |
---|---|
@NotBlank(message =) | 字符串非null,且长度必须大于0 |
元素必须是电子邮箱地址 | |
@Length(min=,max=) | 字符串的大小必须在指定的范围内 |
@NotEmpty | 字符串非空 |
@Range(min=,max=,message=) | 元素必须在合适的范围内 |