springboot版本是2.0.0.RELEASE已经内置hibernate validate好的,隶属于jsr303规范
官网:(以官网为准)
http://hibernate.org/validator/
api doc
https://docs.jboss.org/hibernate/stable/validator/api/
创建工程,添加依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId></dependency><dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId></dependency>
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>
测试在不进行验证时候的返回值
编写Controler代码
@RestController@RequestMapping("validate")@Slf4jpublic class ValidateController { @RequestMapping("validate") public String validateTest(String address) { log.info("address={}", address); return "success"; }}
访问http://localhost:8080/validate/validate返回如下
{ "timestamp": "2019-11-20T08:06:15.728+0000", "status": 400, "error": "Bad Request", "message": "Required String parameter 'address' is not present", "path": "/user/validate"}
访问http://localhost:8080/validate/validate?address返回success
测试在进行进行验证的时候的返回值
请求方法中的请求参数上直接添加验证规则 如:@NotNull ,需要在该类上面需要添加@Validated
import org.hibernate.validator.constraints.NotBlank;import org.springframework.validation.annotation.Validated;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController; import lombok.extern.slf4j.Slf4j; @RestController@RequestMapping("validate")@Slf4j@Validatedpublic class ValidateController { @RequestMapping("validate") public String validateTest(@NotBlank(message = "地址不能为空!") String address) { log.info("address={}", address); return "success"; }}
再次访问http://localhost:8080/validate/validate?address如下.成功的进行了验证
{ "timestamp": "2019-11-20T08:13:53.382+0000", "status": 500, "error": "Internal Server Error", "message": "validateTest.address: 地址不能为空!", "path": "/user/validate"}
编写一个实体类,@Past验证生日字段
import java.util.Date;import javax.validation.constraints.Past;import org.springframework.format.annotation.DateTimeFormat;import com.fasterxml.jackson.annotation.JsonFormat;import lombok.Data; @Datapublic class User { private String name; @Past(message = "生日必须是一个过去的日期") @JsonFormat(pattern = "yyyy/MM/dd", timezone = "GMT+8") @DateTimeFormat(pattern = "yyyy/MM/dd") private Date birthday;}
在Controller中进行验证
Contrller的类上添加@Validated,方法的形参的实体类上添加@Valid注解,后面紧跟着BindingResult result(必须强制性的),校验的结果放在了result中。
import javax.validation.Valid; import org.springframework.validation.BindingResult;import org.springframework.validation.annotation.Validated;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController; import com.biillrobot.study.spring.validte.dataobject.User; import lombok.extern.slf4j.Slf4j; @RestController@RequestMapping("user")@Slf4j@Validatedpublic class UserController { @RequestMapping("add") public String add(@Valid User user, BindingResult result) { log.info("user={}", user); return "success"; }}
测试访问
http://localhost:8080/user/add?name=litong&birthday=2019/11/21
出现下面的错误提示
{ "timestamp": "2019-11-20T08:28:32.600+0000", "status": 500, "error": "Internal Server Error", "message": "add.user.birthday: 生日必须是一个过去的日期", "path": "/user/add"}
但是如果格式不合法吗?
http://localhost:8080/user/add?name=litong&birthday=2019-11-21
因为在实体类中没有对生日格式进行验证,所以在这里生日格式可以不合法,上面的访问结果是success,日志中显示的是
2019-11-20 16:32:42.888 INFO UserController.add:21 - user=User(name=litong, birthday=null)
在resources目录下新建一个ValidationMessages_zh_CN.properties的文件,内容如下
user.birthday.past=\u751F\u65E5\u5FC5\u987B\u662F\u4E00\u4E2A\u8FC7\u53BB\u7684\u65E5\u671F
只需要在实体类上message指定用哪个消息key就行了
@Datapublic class User { private String name; @Past(message = "{user.birthday.past}") @JsonFormat(pattern = "yyyy/MM/dd", timezone = "GMT+8") @DateTimeFormat(pattern = "yyyy/MM/dd") private Date birthday;}
发送请求,进行验证,如果验证失败会返回错误的信息如下
同时日志中也会出现一个ConstraintViolationException异常
javax.validation.ConstraintViolationException: add.user.birthday xxx at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:109) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689) at com.biillrobot.study.spring.validte.controller.UserController$$EnhancerBySpringCGLIB$$2465c54c.add(<generated>)
验证国际化乱码问题**
[乱码问题面描述]
ValidationMessages_zh_CN.properties的默认编码是ISO-8891-1,输入的汉字会经过unicode转码
user.birthday.past=\u751F\u65E5\u5FC5\u987B\u662F\u4E00\u4E2A\u8FC7\u53BB\u7684\u65E5\u671F
验证失败时返回的内容不会乱码
但是如果将ValidationMessages_zh_CN.properties编码改成UTF-8
返回的信息中就会包含乱码
[乱码问题原因]
笔者没有找到原因,一直没有解决
笔者分组校验测试失败,测试过程如下
编写实体类
定义接口Update
在校验注解属性上使用groups指定class类
import java.util.Date; import javax.validation.constraints.NotNull;import javax.validation.constraints.Past;import org.springframework.format.annotation.DateTimeFormat;import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; @Datapublic class User { public interface Update{}; @NotNull(groups=Update.class ,message="更新时id不能为空") private Integer id; private String name; @Past(message = "{user.birthday.past}") @JsonFormat(pattern = "yyyy/MM/dd", timezone = "GMT+8") @DateTimeFormat(pattern = "yyyy/MM/dd") private Date birthday;}
Controller中
在方法的形参上使用@Validated指明class类
import javax.validation.Valid; import org.springframework.validation.BindingResult;import org.springframework.validation.annotation.Validated;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController; import com.biillrobot.study.spring.validte.dataobject.User; import lombok.extern.slf4j.Slf4j; @RestController@RequestMapping("user")@Slf4j@Validatedpublic class UserController { @RequestMapping("add") public String add(@Valid User user, BindingResult result) { log.info("user={}", user); return "success"; } @RequestMapping("update") public String update(@Validated({User.Update.class}) User user,BindingResult result){ log.info("user={}",user); return "success"; }}
奇怪的是测试失败,不发送id竟然也是成功
http://localhost:8080/user/update?name=litong&birthday=2019/11/22
使用分组校验,必须要手动获取错误,定义返回的消息格式,,修改后的Controller代码如下
@RequestMapping("update") public String update(@Validated({ User.Update.class }) User user, BindingResult bindingResult) { log.info("user={}", user); if (bindingResult.hasErrors()) { String errorMsg = bindingResult.getFieldError().getDefaultMessage(); log.info("errorMsg={}", errorMsg); return errorMsg; } return "success"; }
分组校验和其他校验不同的是,即使校验失败,在数据库中也不会出现异常
自定义校验器
自定类,实现ConstraintValidator,在泛型中填入注解类名和校验的数据类型
重新isValid方法对接进行校验,校验通过返回true,校验失败返回false
下面的校验规则定义,String类型必须为空
import javax.validation.ConstraintValidator;import javax.validation.ConstraintValidatorContext; import org.springframework.util.StringUtils; import lombok.extern.slf4j.Slf4j; @Slf4jpublic class MustEmptyValidator implements ConstraintValidator<MustEmpty, String> { @Override public boolean isValid(String input, ConstraintValidatorContext context) { log.info("input={}", input); // 验证通过返回true if (StringUtils.isEmpty(input)) { log.info("验证通过"); return true; } log.info("验证失败"); // 验证失败返回false return false; }}
自定义注解,指定校验器
@Constraint(validatedBy=MustEmptyValidator.class)@Documented@Target({ElementType.METHOD,ElementType.FIELD})@Retention(RetentionPolicy.RUNTIME)public @interface MustEmpty { String message() default "属性必须为空"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {};}
使用自定义校规则
在实体类中使用自定义的校验注解
import java.util.Date; import javax.validation.constraints.NotNull;import javax.validation.constraints.Past; import org.springframework.format.annotation.DateTimeFormat; import com.biillrobot.study.spring.validte.annotation.MustEmpty;import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; @Datapublic class User { public interface Update{}; public interface Insert{}; @NotNull(groups=Update.class ,message="更新时id不能为空") @MustEmpty(groups=Insert.class,message="添加时id必须为空") private String id; private String name; @Past(message = "{user.birthday.past}") @JsonFormat(pattern = "yyyy/MM/dd", timezone = "GMT+8") @DateTimeFormat(pattern = "yyyy/MM/dd") private Date birthday;}
Controller变化不大,如下
import org.springframework.validation.BindingResult;import org.springframework.validation.annotation.Validated;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController; import com.biillrobot.study.spring.validte.dataobject.User; import lombok.extern.slf4j.Slf4j; @RestController@RequestMapping("user")@Slf4j@Validatedpublic class UserController { @RequestMapping("add") // public String add(@Valid User user, BindingResult result) { public String add(@Validated(User.Insert.class) User user, BindingResult bindingResult) { log.info("user={}", user); if (bindingResult.hasErrors()) { String errorMsg = bindingResult.getFieldError().getDefaultMessage(); log.info("errorMsg={}", errorMsg); return errorMsg; } return "success"; } @RequestMapping("update") public String update(@Validated({ User.Update.class }) User user, BindingResult bindingResult) { log.info("user={}", user); return "success"; }}
发送请求测试
http://localhost:8080/user/add?id=1&name=litong&birthday=2019/11/22
返回如下
添加时id必须为空
编写实体类
import javax.validation.constraints.Email;import javax.validation.constraints.Max;import javax.validation.constraints.NotBlank;import javax.validation.constraints.NotNull;import javax.validation.constraints.Size; import lombok.Data; @Datapublic class Student { @NotBlank(message = "用户名不能为空") private String name; @Max(value = 120, message = "年龄不能超过120岁") private int age; @NotNull @Size(min = 8, max = 20, message = "密码必须大于8位并且小于20位") private String password; @Email(message = "请输入符合格式的邮箱") private String email;}
编写Controller
import javax.validation.Valid; import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController; import com.biillrobot.study.spring.validte.dataobject.Student; @RestController@RequestMapping("/stu")public class StudentController { @RequestMapping("add") public Student add(@Valid Student stu) { // 仅测试验证过程,省略其他的逻辑 return stu; }}
执行请求,发送一个错误的邮箱
http://localhost:8080/stu/add?name=litong&age=18&password=00000000&email=litongjava@
返回如下
{ "timestamp": "2019-11-22T06:22:02.768+0000", "status": 400, "error": "Bad Request", "errors": [ { "codes": [ "Email.student.email", "Email.email", "Email.java.lang.String", "Email" ], "arguments": [ { "codes": [ "student.email", "email" ], "arguments": null, "defaultMessage": "email", "code": "email" }, [], { "defaultMessage": ".*", "arguments": null, "codes": [ ".*" ] } ], "defaultMessage": "请输入符合格式的邮箱", "objectName": "student", "field": "email", "rejectedValue": "litongjava@", "bindingFailure": false, "code": "Email" } ], "message": "Validation failed for object='student'. Error count: 1", "path": "/stu/add"}
日志中并没有异常堆栈,显示如下
2019-11-22 14:22:02.766 WARN DefaultHandlerExceptionResolver.logException:193 - Resolved exception caused by Handler execution: org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'student' on field 'email': rejected value [litongjava@]; codes [Email.student.email,Email.email,Email.java.lang.String,Email]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [student.email,email]; arguments []; default message [email],[Ljavax.validation.constraints.Pattern$Flag;@538ddcc4,org.springframework.validation.beanvalidation.SpringValidatorAdapter$ResolvableAttribute@66098dbe]; default message [请输入符合格式的邮箱]
这是因为,SpringBoot配置了默认异常处理器DefaultHandlerExceptionResolver,而该处理器仅仅是将异常信息打印出来,显然,我们并不需要返回如此多的信息,只需要将对应属性中的message信息给调用者即可,解决的方法有两种。
1.在需要验证的方法中加入BindingResult参数,SpringBoot会自动将异常错误信息绑定到该参数上,然后处理对应的逻辑,如下
@RequestMapping("add") public Student add(@Valid Student stu, BindingResult bindingResult) { if (bindingResult.hasErrors()) { // 具体的处理逻辑,如封装错误信息等 } return stu; }
但是这种方式不是很优雅,因为对于每一个需要验证的方法,都需要进行这样的逻辑(虽然封装处理可以解决,但依旧每次需要手动调用以及加入BindingResult参数)
2.由于在验证失败的时候,会抛出异常,所以可以使用全局异常处理器来捕获该异常,然后进行统一处理即可,具体的异常类型是org.springframework.validation.BindException具体实现如下所示
import java.util.List;import java.util.stream.Collectors;import java.util.stream.Stream; import org.springframework.validation.BindException;import org.springframework.validation.BindingResult;import org.springframework.validation.ObjectError;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.RestControllerAdvice; import lombok.extern.slf4j.Slf4j; @RestControllerAdvice@Slf4jpublic class GlobalExceptionHandler { @ExceptionHandler({ BindException.class }) public ResultInfo<?> validationErrorHandler(BindException exception) { // 获取BindingResult对象 BindingResult bindingResult = exception.getBindingResult(); // 获取bindingResul中的所有错误 List<ObjectError> allErrors = bindingResult.getAllErrors(); //将List<ObjectError>转为Stream<ObjectError> Stream<ObjectError> stream = allErrors.stream(); //获取Stream<ObjectError>中ObjectError.getDefaultMessage的返回值,组成新加的Stream<String> Stream<String> map = stream.map(ObjectError::getDefaultMessage); //Stream<String>转为List<String> List<String> errorInformation = map.collect(Collectors.toList()); log.info("异常已将发现,获取到的错误信息是={}", errorInformation); return new ResultInfo<>(400, errorInformation.toString(), null); }}
发送和上面相同的请求,返回的消息语句如下
{ "code": 400, "message": "[请输入符合格式的邮箱]", "body": null}
日志中内容如下
2019-11-22 14:50:18.240 INFO GlobalExceptionHandler.validationErrorHandler:28 - 异常已将发现,获取到的错误信息是=[请输入符合格式的邮箱]
2019-11-22 14:50:18.243 WARN ExceptionHandlerExceptionResolver.logException:193 - Resolved exception caused by Handler execution: org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'student' on field 'email': rejected value [litongjava@]; codes [Email.student.email,Email.email,Email.java.lang.String,Email]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [student.email,email]; arguments []; default message [email],[Ljavax.validation.constraints.Pattern$Flag;@4ec9c0f5,org.springframework.validation.beanvalidation.SpringValidatorAdapter$ResolvableAttribute@1e0fa557]; default message [请输入符合格式的邮箱]
ObjectError中有很多字段,笔者可以按需获取
import java.util.List;import java.util.Map;import java.util.stream.Collectors;import java.util.stream.Stream; import org.springframework.validation.BindException;import org.springframework.validation.BindingResult;import org.springframework.validation.FieldError;import org.springframework.validation.ObjectError;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.RestControllerAdvice; import lombok.extern.slf4j.Slf4j; @RestControllerAdvice@Slf4jpublic class GlobalExceptionHandler { @ExceptionHandler({ BindException.class }) public ResultInfo<?> validationErrorHandler(BindException exception) { // 获取BindingResult对象 BindingResult bindingResult = exception.getBindingResult(); // 获取bindingResul中的所有错误 List<ObjectError> allErrors = bindingResult.getAllErrors(); //获取field和getMessage组合成map返回 Map<String, String> collect = allErrors.stream().collect(Collectors.toMap(item -> ((FieldError) item).getField(), item -> item.getDefaultMessage(), (oldVal, currVal) -> oldVal)); return new ResultInfo<>(-400, exception.getMessage(), collect); }}
返回的信息内容如下
{ "code": -400, "message": "org.springframework.validation.BeanPropertyBindingResult: 1 errors\nField error in object 'student' on field 'email': rejected value [litongjava@]; codes [Email.student.email,Email.email,Email.java.lang.String,Email]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [student.email,email]; arguments []; default message [email],[Ljavax.validation.constraints.Pattern$Flag;@5e24fd07,org.springframework.validation.beanvalidation.SpringValidatorAdapter$ResolvableAttribute@4b426803]; default message [请输入符合格式的邮箱]", "body": { "email": "请输入符合格式的邮箱" }}
常用的注解主要有以下几个,作用及内容如下所示
@Null,标注的属性值必须为空
@NotNull,标注的属性值不能为空
@AssertTrue,标注的属性值必须为true
@AssertFalse,标注的属性值必须为false
@Min,标注的属性值不能小于min中指定的值
@Max,标注的属性值不能大于max中指定的值
@DecimalMin,小数值,同上
@DecimalMax,小数值,同上
@Negative,负数
@NegativeOrZero,0或者负数
@Positive,整数
@PositiveOrZero,0或者整数
@Size,指定字符串长度,注意是长度,有两个值,min以及max,用于指定最小以及最大长度
@Digits,内容必须是数字
@Past,时间必须是过去的时间
@PastOrPresent,过去或者现在的时间
@Future,将来的时间
@FutureOrPresent,将来或者现在的时间
@Pattern,用于指定一个正则表达式
@NotEmpty,字符串内容非空
@NotBlank,字符串内容非空且长度大于0
@Email,邮箱
@Range,用于指定数字,注意是数字的范围,有两个值,min以及max
原网址: 访问
创建于: 2020-12-23 17:47:34
目录: default
标签: 无
未标明原创文章均为采集,版权归作者所有,转载无需和我联系,请注明原出处,南摩阿彌陀佛,知识,不只知道,要得到
最新评论