Java参数校验validation和validator区别
- 1. 参数校验概述
- 2. validation与validator区别
- 3. validation注解说明
- 4. validator注解说明
- 5. 日期格式化说明
- 6. 实现验证
- 6.1 引入依赖
- 6.2 代码实现
- 6.3 实现验证
1. 参数校验概述
常见的业务开发中无可避免的会进行请求参数校验,一般对于复杂的业务参数校验,可以通过校验类单独的校验方法进行处理,通常对于一些与业务无关简单的参数校验可以采用javax.validation 和 hibernate-validator通过注解的方式实现校验。
2. validation与validator区别
javax的validation是Java定义的一套基于注解的数据校验规范,目前已经从JSR 303的1.0版本升级到JSR 349的1.1版本,再到JSR 380的2.0版本(2.0完成于2017.08),已经经历了三个版本。
需要注意的是,JSR只是一项标准,它规定了一些校验注解的规范,但没有实现,比如@Null、@NotNull、@Pattern等,它们位于 javax.validation.constraints这个包下。而hibernate validator是对这个规范的实现,并增加了一些其他校验注解,如 @NotBlank、@NotEmpty、@Length等,它们位于org.hibernate.validator.constraints这个包下。
如果我们的项目使用了Spring Boot,hibernate validator框架已经集成在 spring-boot-starter-web中,所以无需再添加其他依赖。如果不是Spring Boot项目,需要添加如下依赖。
<dependency><groupId>org.hibernate.validator</groupId><artifactId>hibernate-validator</artifactId><version>6.0.13.Final</version>
</dependency>
3. validation注解说明
注解 | 说明 |
---|---|
@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(value) | 被注释的元素必须符合指定的正则表达式 |
4. validator注解说明
注解 | 说明 |
---|---|
@NotBlank | 被注释的元素不能为null,去除空格( trim() 之后 size > 0) 且长度必须大于0,只能用于注解字符串 |
@NotEmpty | 被注释的元素值不为null且不为空,支持字符串、集合、Map和数组类型 |
@Length(min=,max=) | 检查所属的字段的长度是否在min和max之间,只能用于字符串 |
被注释的元素必须是电子邮箱地址 | |
@Range(min=,max=,message=) | 被注释的元素必须在合适的范围内 |
@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框架中的注解是需要加在实体中一起使用的
5. 日期格式化说明
java日期问题,针对Date类型字段。
1.接收前端校验注解,@DateTimeFormat 接受前台的时间格式 传到后台的格式。
@DateTimeFormat(pattern="yyyy-MM-dd")
private Date born;
2.后端返回给前端日期格式化,使用场景数据库存储的是yyyy-MM-dd HH:mm:ss,但是前端需要yyyy-MM-dd的时候可以使用此注解。
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
private Date born;
6. 实现验证
6.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>
6.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;}@Autowiredpublic 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("服务器内部错误,请联系开发人员!");}
}