springboot使用注解参数校验 javax.validation 和 hibernate-validator
1. 请求参数校验概述
1.1 业务需求概述
常见的业务开发中无可避免的会进行请求参数校验,一般对于复杂的业务参数校验,可以通过校验类单独的校验方法进行处理,通常对于一些与业务无关简单的参数校验可以采用javax.validation 和 hibernate-validator通过注解的方式实现校验。
1.2 常见注解说明
注解 | 说明 |
---|---|
@Length(min=,max=) | 检查所属的字段的长度是否在min和max之间,只能用于字符串 |
@Range(min=,max=,message=) | 被注释的元素必须在合适的范围内 |
@Max | 该字段的值只能小于或等于该值 |
@Min | 该字段的值只能大于或等于该值 |
@NotNull | 不能为null |
@NotBlank | 不能为空,检查时会将空格忽略 |
@NotEmpty | 不能为空,这里的空是指空字符串 |
@Pattern(regex=,flag=) | 被注释的元素必须符合指定的正则表达式 |
注意:
1.正则表达式@Pattern注解只用于String类型的字段上。数字类型的可用@Range注解。
2.需要在Controller中请求对象或者参数前加@Validated或@Valid注解一起使用,
@Validated和@Valid注解区别不是很大,一般情况下任选一个即可,区别如下:
注解 | @Validated | @Valid |
---|---|---|
所属的包 | 属于org.springframework.validation.annotation包下的,是spring提供的 | 属于javax.validation包下,是jdk给提供的 |
是否支持分组和排序 | 是 | 否 |
虽然@Validated比@Valid更加强大,在@Valid之上提供了分组功能和验证排序功能,不过在实际项目中一直没有用到过 Hibernate-validate框架中的注解是需要加在实体中一起使用的
2. 参数校验验证
2.1 引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>category</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>category</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<!--数据库连接诶-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--常用工具-->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1.1-jre</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.6.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<!--swagger2-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<!--字段校验-->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.13.Final</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.2 代码实现
1.实体类
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.*;
/**
* 员工实体类
*
* @author zrj
* @since 2021/7/31
**/
@Data
@ApiModel(value = "Employee", description = "员工信息")
public class Employee {
@NotNull(message = "userId is empty")
@ApiModelProperty(value = "员工ID")
private Integer id;
@NotBlank(message = "员工姓名不能为空")
@Length(min = 1, max = 100, message = "员工姓名长度不能超过50字")
@ApiModelProperty(value = "员工姓名")
private String name;
@Range(min = 1, max = 200, message = "年龄范围[1,200]")
@ApiModelProperty(value = "年龄")
private Integer age;
@Pattern(regexp = "^[0-1]$", message = "员工类型范围[0,1]")
@ApiModelProperty(value = "员工类型(0行政岗;1基础岗")
private String type;
@Pattern(regexp = "^[1][^0^1^2][0-9]{9}$", message = "员工手机号码不合法")
@ApiModelProperty(value = "员工手机号码")
private String phone;
@Email(message = "员工邮箱不合法")
@Length(min = 1, max = 50, message = "员工邮箱长度")
@ApiModelProperty(value = "员工邮箱")
private String email;
@Length(min = 0, max = 200, message = "备注长度不能超过100字")
@ApiModelProperty(value = "备注")
private String remarks;
}
2.控制类
import com.example.category.entity.Employee;
import com.example.category.entity.Response;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
/**
* 员工控制类
*
* @author zrj
* @since 2021/7/31
**/
@RestController
@RequestMapping("/employee")
@Api(value = "employee", description = "员工管理")
public class EmployeeController {
@GetMapping("/test")
@ApiOperation(value = "test测试", notes = "test测试", httpMethod = "GET")
public Response test() {
return Response.success("查询成功", null);
}
/**
* Valid测试
* 这里职位测试@Valid,所以不在写其他各层
*/
@PostMapping("/validEpmloyee")
@ApiOperation(value = "Valid测试", notes = "Valid测试", httpMethod = "POST")
public Response<Employee> validEpmloyee(@Valid @RequestBody Employee employee) {
System.out.println("测试请求对象:" + employee);
if (employee != null) {
return Response.success("查询成功", employee);
}
return Response.fail("查询失败");
}
}
3.结果集响应码
package com.example.category.entity;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 结果集封装
*
* @author zrj
* @date 2021/6/2
* @since V1.0
**/
@Data
@Component
public class Response<T> {
private static ResponseCode responseCode;
/**
* 提示消息
*/
private String message;
/**
* 具体返回的数据
*/
private T data;
/**
* 状态码
*/
private String code;
private Response(String code, String message, T data) {
this.message = message;
this.code = code;
this.data = data;
}
private Response(String code, String msg) {
this.message = msg;
this.code = code;
}
@Autowired
public Response(ResponseCode responseCode) {
Response.responseCode = responseCode;
}
/**
* 返回成功Response对象
*/
public static <T> Response<T> success(String successMessage, T data) {
return new Response<>(responseCode.getSuccessCode(), successMessage, data);
}
/**
* 返回错误Response对象
*/
public static <T> Response<T> fail(String errorMessage) {
return new Response<>(responseCode.getErrorCode(), errorMessage);
}
}
import lombok.Data;
import org.springframework.stereotype.Component;
/**
* 响应码
*
* @author zrj
* @date 2021/6/2
* @since V1.0
**/
@Data
@Component
public class ResponseCode {
public String successCode = "200";
public String errorCode = "500";
public String authErrorCode = "300";
}
4.异常切面封装类
import com.example.category.entity.Response;
import com.fasterxml.jackson.databind.JsonMappingException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
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.method.HandlerMethod;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.List;
import java.util.Set;
/**
* 异常切面封装类
*
* @author zrj
* @since 2021/7/31
**/
@ControllerAdvice
@ResponseBody
@Slf4j
public class ExceptionHandlerAdvice {
/**
* Exception
*/
@ExceptionHandler(Exception.class)
public Response<String> handleGlobalException(Exception exception, HandlerMethod handlerMethod) {
if (exception instanceof MethodArgumentNotValidException) {
List<ObjectError> errors = ((MethodArgumentNotValidException) exception).getBindingResult().getAllErrors();
StringBuilder sb = new StringBuilder();
if (!CollectionUtils.isEmpty(errors)) {
for (ObjectError error : errors) {
if (sb.length() != 0) {
sb.append(",");
}
sb.append(error.getDefaultMessage());
}
}
return Response.fail(sb.toString());
}
// 约束异常
if (exception instanceof ConstraintViolationException) {
Set<ConstraintViolation<?>> exceptionSet = ((ConstraintViolationException) exception).getConstraintViolations();
StringBuilder sb = new StringBuilder();
if (!CollectionUtils.isEmpty(exceptionSet)) {
for (ConstraintViolation<?> set : exceptionSet) {
if (sb.length() != 0) {
sb.append(",");
}
sb.append(set.getMessageTemplate());
}
}
return Response.fail(sb.toString());
}
// 参数类型转换异常处理
if (exception instanceof MethodArgumentTypeMismatchException) {
return Response.fail(((MethodArgumentTypeMismatchException) exception).getName() + " 类型不匹配");
}
if (exception instanceof JsonMappingException) {
return Response.fail("JSON格式错误, " + exception.getLocalizedMessage());
}
if (exception instanceof HttpMessageNotReadableException) {
return Response.fail("请求体格式错误, " + exception.getLocalizedMessage());
}
if (exception instanceof MissingServletRequestParameterException) {
String paramName = ((MissingServletRequestParameterException) exception).getParameterName();
return Response.fail(paramName + " 不能为空");
}
//if (exception instanceof MarketingException) {
// MarketingException marketingException = (MarketingException) exception;
// return RdfaResult.fail(marketingException.getErrorCodeEnum().getCode(), exception.getMessage());
//}
// 其他异常打印日志
log.error("{}.{} error, ", handlerMethod.getBeanType().getSimpleName(), handlerMethod.getMethod().getName(), exception);
//if (exception instanceof RpcException) {
// return RdfaResult.fail(ErrorCodeEnum.RPC_ERROR.getCode(), "RPC调用错误,请稍后重试");
//}
return Response.fail("服务器内部错误,请联系开发人员!");
}
}