1. Spring Validation

JSR-303 简介

JSR-303 是 JavaEE 6 中的一项子规范,叫做 Bean Validation,官方参考实现是 Hibernate Validator

此实现与 Hibernate ORM 没有任何关系。JSR-303 用于对 Java Bean 中的字段的值进行验证。 Spring MVC 3.x 之中也大力支持 JSR-303,可以在控制器中使用注解的方式对表单提交的数据方便地验证。Spring 4.0 开始支持 Bean Validation 功能。在日常开发中,Hibernate Validator经常用来验证bean的字段,基于注解,方便快捷高效。

2. JSR-303 基本的校验规则

2.1空检查

  • @Null 验证对象是否为 null
  • @NotNull 验证对象是否不为 null, 无法查检长度为 0 的字符串
  • @NotBlank 检查约束字符串是不是 Null 还有被 Trim 的长度是否大于 0,只对字符串,且会去掉前后空格
  • @NotEmpty 检查约束元素是否为 NULL 或者是EMPTY

    使用hibernate validator出现上面的错误, 需要注意@NotNull 和 @NotEmpty 和@NotBlank 区别

    @NotEmpty 用在集合类上面
    @NotBlank 用在String上面

    @NotNull 用在基本类型上

    在枚举类上不要加非空注解

2.2 布尔检查

  • @AssertTrue 验证Boolean对象是否为 true
  • @AssertFalse 验证 Boolean对象是否为false

    2.3 长度检查

  • @Size(min=, max=) 验证对象(Array, Collection , Map, String)长度是否在给定的范围之内
  • @Length(min=, max=)验证字符串长度介于 min 和 max 之间

    2.4 日期检查

  • @Past 验证 DateCalendar 对象是否在当前时间之前,验证成立的话被注释的元素一定是一个过去的日期
  • @Future 验证DateCalendar 对象是否在当前时间之后 ,验证成立的话被注释的元素一定是一个将来的日期

    2.5 正则检查

  • @Pattern 验证 String对象是否符合正则表达式的规则,被注释的元素符合制定的正则表达式
    • regexp:正则表达式
    • flags:指定Pattern.Flag的数组,表示正则表达式的相关选项

      2.6 数值检查

注意: 建议使用在String ,Integer 类型,不建议使用在int类型上,因为表单值为 “”时无法转换为int,但可以转换为String“”Integernull

  • @Min 验证 Number 和 String 对象是否大等于指定的值

  • @Max 验证 Number 和 String 对象是否小等于指定的值

  • @DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过 BigDecimal 定义的最大值的字符串表示.小数 存在精度

  • @DecimalMin被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过 BigDecimal 定义的最小值的字符串表示 .小数存在精度

  • @Digits 验证NumberString的构成是否合法

  • @Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,integer 指定整数精度,fraction 指定小数精度

  • @Range(min=, max=) 被指定的元素必须在合适的范围内

  • @Range(min=10000,max=50000,message=”range.bean.wage”)

  • @CreditCardNumber 信用卡验证

  • @Email 验证是否是邮件地址,如果为 null,不进行验证,算通过验证

  • @ScriptAssert(lang= ,script=, alias=)

  • @URL(protocol=,host=, port=,regexp=, flags=)

    2.7 对象校验

  • @Valid 被注释的元素是一个对象,需要检查此对象的所有字段值

  • @Validated 被注解的元素是一个对象或者一个类,需要检查此对象的所有字段值

    3 使用 Spring Validation 验证

    POM

这里我们使用 Hibernate Validator 5.x 来实现Spring Validation接口,pom.xml 文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.3.4.Final</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

主要是增加了 org.hibernate:hibernate-validator 依赖

实体类参数校验

User

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import lombok.Data;
import org.hibernate.validator.constraints.Email;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

//使用Spring Validator和Hibernate Validator这两套Validator来进行方便的参数校验!
//这两套Validator依赖包已经包含在前面所说的web依赖包里了,所以可以直接使用。所以可以直接使用
@Data
public class User {

@NotNull(message = "用户名id不能为空")
private Long id;

@NotNull(message = "用户账号不能为空")
@Size(min = 6, max = 11, message = "账号长度必须是6-11个字符")
private String account;

@NotNull(message = "用户密码不能为空")
@Size(min = 6, max = 11, message = "密码长度必须是6-16个字符")
private String password;


//@Email使用的是org.hibernate包下的注解
// javax下的包会报错,坑
@NotNull(message = "用户邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;

}

controller

实体类上加上@Valid注解,使用json传参,加@RequestBody解析json参数映射为实体类,如果校验错误不会执行业务逻辑,抛出异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@RestController
public class UserController {

@Autowired
private UserService userService;

@PostMapping("/addUser")
//@Valid 被注释的元素是一个对象,需要检查此对象的所有字段值
//@Validated 被注解的元素是一个对象或者一个类,需要检查此对象的所有字段值
//public String addUser(@RequestBody @Valid User user,BindingResult bindingResult) {
public String addUser(@RequestBody @Valid User user) { //BindingResult bindingResult

// 如果出现异常,后面的代码不会执行
// 如果有参数校验失败,会将错误信息封装成对象组装在BindingResult里
// for (ObjectError error : bindingResult.getAllErrors()) {
// return error.getDefaultMessage();
// }
return userService.save(user);
}
}

service

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface UserService {
String save(User user);
}


@Service
public class UserServiceImpl implements UserService {
@Override
public String save(User user) {
System.out.println("保存成功---->"+user);
return "success";
}
}

PostMan测试

发送post请求

1
2
3
4
5
6
{
"id": 1,
"account": "12345678",
"password": "12312123",
"email": "15096021" # 错误格式
}

响应数据

数据不友好,使用全局异常捕获返回友好提示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
{
"timestamp": "2020-09-19T06:00:38.546+00:00",
"status": 400,
"error": "Bad Request",
"trace": "org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public java.util.List cn.zysheep.springboot_interface.controller.UserController.addUser(cn.zysheep.springboot_interface.entity.User): [Field error in object 'user' on field 'email': rejected value [15096021m]; codes [Email.user.email,Email.email,Email.java.lang.String,Email]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.email,email]; arguments []; default message [email],[Ljavax.validation.constraints.Pattern$Flag;@3796b0b3,.*]; default message [邮箱格式不正确]] \r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:139)\r\n\tat org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:167)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:134)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:878)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:792)\r\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)\r\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)\r\n\tat org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:652)\r\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:733)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)\r\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)\r\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)\r\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143)\r\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\r\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)\r\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)\r\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374)\r\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)\r\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)\r\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1590)\r\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\r\n\tat java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)\r\n\tat java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)\r\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\r\n\tat java.lang.Thread.run(Thread.java:748)\r\n",
"message": "Validation failed for object='user'. Error count: 1",
"errors": [
{
"codes": [
"Email.user.email",
"Email.email",
"Email.java.lang.String",
"Email"
],
"arguments": [
{
"codes": [
"user.email",
"email"
],
"arguments": null,
"defaultMessage": "email",
"code": "email"
},
[],
{
"arguments": null,
"defaultMessage": ".*",
"codes": [
".*"
]
}
],
"defaultMessage": "邮箱格式不正确",
"objectName": "user",
"field": "email",
"rejectedValue": "15096021m",
"bindingFailure": false,
"code": "Email"
}
],
"path": "/addUser"
}

全局处理异常

全局处理异常,处理@RequestBody参数校验异常,统一返回格式自定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RestControllerAdvice
public class ExceptionControllerAdvice {
/**
* 处理 @RequestBody参数校验异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Map<String, Object> MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {

Map<String, Object> body = new LinkedHashMap<>();
body.put("timestamp", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

// 获取所有异常
List<String> errors = e.getBindingResult()
.getFieldErrors()
.stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.toList());
body.put("errors", errors);
return body;
}
}

PostMan测试,响应数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 请求数据
{
"id": 1,
"account": "12345678",
"password": "12312123",
"email": "15096021m"
}

# 全局异常处理后的响应数据
{
"timestamp": "2020-09-19 14:07:05",
"errors": [
"邮箱格式不正确"
]
}

表单传参校验

使用表单传参,即不使用@RequestBody,跟上面的第一类异常捕获的异常类型不同而已。

1
2
3
4
5
6
7
8
9
10
11
@Controller
public class LoginController {

@PostMapping("/test1")
@ResponseBody
public AjaxResult test1(@Validated User user){
System.out.println(user);
return user;
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@RestControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResultVO<Map<String, Object>> MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
// 从异常对象中拿到ObjectError对象
//ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
// 然后提取错误提示信息进行返回

Map<String, Object> body = new LinkedHashMap<>();
body.put("timestamp", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

// 获取所有异常
List<String> errors = e.getBindingResult()
.getFieldErrors()
.stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.toList());
body.put("errors", errors);

return new ResultVO<>(ResultCode.FAILED,body);
}
}

单个参数校验

直接在参数前加上校验注解

1
2
3
4
5
6
7
8
9
10
@RestController
@Validated
public class UserController {
@GetMapping("/test3")
public AjaxResult test3(@NotNull(message = "name不能为空") String name ,@Email(message ="邮箱格式不正确") String email){
System.out.println(name);
System.out.println(email);
return AjaxResult.success(name+" "+email);
}
}

注意:需要在类上添加@Validated注解,否则不会校验。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@RestControllerAdvice
public class GlobalExceptionHandler {

/**
* 处理所有参数校验时抛出的异常
* @param ex
* @return
*/
@ExceptionHandler(value = ValidationException.class)
public AjaxResult handleBindException(ValidationException ex) {
AjaxResult body = new AjaxResult();

body.put("timestamp", new Date());

// 获取所有异常
List<String> errors = new LinkedList<>();
if (ex instanceof ConstraintViolationException) {
ConstraintViolationException exs = (ConstraintViolationException) ex;
Set<ConstraintViolation<?>> violations = exs.getConstraintViolations();
for (ConstraintViolation<?> item : violations) {
errors.add(item.getMessage());
}
}
body.put("errors", errors);
body.put("code",900);
body.put("msg","提交的数据校验失败");

return body;
}

}