文章目录
前言
今天给大家介绍一下SpringBoot(Spring也可以)项目中如何优雅的整合JSR-303进行参数校验 以及对全局的异常进行处理封装
什么是JSR-303
JSR-303 是JAVA EE 6 中的一项子规范,叫做Bean Validation,Bean Validation 是一个运行时的数据验证框架,在验证之后验证的错误信息会被马上返回.Hibernate Validator 是 Bean Validation 的参考实现 . Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有一些附加的 constraint.
添加依赖
SpringBoot项目可以用starter组件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
老点的spring项目可以用下面这个(同样适用于springBoot项目)
<!-- 参数校验 -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.1.Final</version>
</dependency>
SpringBoot项目设置全局异常处理
参数在校验失败的时候会抛出的MethodArgumentNotValidException异常,可以在全局的异常处理器中捕捉这个异常,将报错信息同一封装之后返回给客户端。
全局异常处理器代码如下
注意: 此控制器为接口形式 处理异常的方法均为接口的default方法 此举不同于其他博客的注解形式和继承形式 注解形式是直接控制全局灵活性欠佳(比如你有一个三方回调的控制器类不想被监控) 而继承的方式又绑定的太死 所以采用了接口和default方法
/**
* <p>
* 全局异常处理的基础类 必须实现此类
* </p>
* @author liang
* @date 2021/5/28 17:46
*/
public interface HandleExceptionController {
/**
* 处理实现此类的所有程序抛出的业务代码异常
* @param e 业务代码自定义异常
* @return 封装好的统一返回结果
*/
@ExceptionHandler(ServiceException.class)
@ResponseBody
default ResponseResult handleException(ServiceException e) {
ResponseResult result = new ResponseResult();
result.setCode(e.getCode());
result.setMessage(e.getMessage());
result.setData(e.getData());
return result;
}
/**
* 处理实现此类的所有程序抛出的参数绑定异常
* @param e 参数绑定异常
* @return 封装好的统一返回结果
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
default ResponseResult methodArgumentNotValidException(MethodArgumentNotValidException e) {
String message = e.getBindingResult().getFieldError().getDefaultMessage();
return new ResponseResult(ResultEnum.REQUIRED_MISSING.getCode(), message,null);
}
/**
* 处理实现此类的所有程序抛出的未知异常
* @param e 程序抛出来的异常
* @return 封装好的统一返回结果
*/
@ExceptionHandler(Exception.class)
@ResponseBody
default ResponseResult handleException(Exception e) {
return new ResponseResult(ResultEnum.ERROR, e.getMessage());
}
}
示例Controller代码
@Validated注解是用来标识这个参数类需要进行参数校验的
@RestController
@RequestMapping("/api/test")
@Api(value = "TestController", tags = { "测试类" })
public class TestController implements HandleExceptionController {
@ApiOperation(value = "测试查询", notes = "测试查询")
@PostMapping(value = "/queryTest")
public ResponseResult queryTest(@RequestBody @Validated QueryTestParam param){
JSONObject result = new JSONObject();
return new ResponseResult(ResultEnum.SUCCESS,result);
}
}
参数类
省略get和set方法
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* @author liang
* @date 2021年12月29日 14:23
*/
public class QueryTestParam {
@NotNull(message = "integer不能为空")
private Integer integer;
@NotBlank(message = "string不能为空")
private String string;
}
如果参数类中的integer为null或者string为空字符串则会抛出MethodArgumentNotValidException异常,紧接着此异常会被HandleExceptionController接口的methodArgumentNotValidException方法捕获到 注意这行代码: @ExceptionHandler(MethodArgumentNotValidException.class)这个注解表示你的方法要捕获处理的异常是什么 如果你要捕获的异常有继承关系 他会优先捕获子类的详细类型的异常 所以接口中有一个handleException方法来捕获最终的异常exception
javax参数校验
内嵌的注解列表
注释 | 解释 |
---|---|
@Null | 被注释的元素必须为 null |
@NotNull | 被注释的元素必须不为 null |
@NotBlank | 被注释的元素必须为字符串而且不能为空 |
@AssertTrue | 被注释的元素必须为 true |
@AssertFalse | 被注释的元素必须为 false |
@Min(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@Max(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@DecimalMin(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@DecimalMax(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@Size(max, min) | 被注释的元素的大小必须在指定的范围内 |
@Digits (integer, fraction) | 被注释的元素必须是一个数字,其值必须在可接受的范围内 |
@Past | 被注释的元素必须是一个过去的日期 |
@Future | 被注释的元素必须是一个将来的日期 |
@Pattern(value) | 被注释的元素必须符合指定的正则表达式 |
被注释的元素必须是电子邮箱地址 | |
@Length | 被注释的字符串的大小必须在指定的范围内 |
@NotEmpty | 被注释的字符串的必须非空 |
@Range | 被注释的元素必须在合适的范围内 |
如何使用
get和set方法省略
import javax.validation.constraints.Future;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.Date;
/**
* @author liang
* @date 2021年12月29日 14:23
*/
public class QueryTestParam {
@NotNull(message = "文章id不能为空")
@Min(value = 1,message = "文章ID不能为负数")
private Integer id;
@NotBlank(message = "文章内容不能为空")
private String content;
@NotBlank(message = "作者Id不能为空")
private String authorId;
@Future(message = "提交时间不能为过去时间")
private Date submitTime;
}
分组校验
举例说明 新增和修改一篇文章 修改只比新增接口多传一个主键id,这时候很明显不想要建立两个非常相似的实体类去分别接收,这时候该怎么办呢?
答案显而易见,就是进行分组校验 新增是一个分组 修改是一个分组
所有的校验注解都有一个groups属性用来指定分组,Class<?>[]类型,没有实际意义,因此只需要定义一个或者多个接口用来区分即可。
/**
* @author liang
* @date 2021年12月29日 14:23
*/
public class QueryTestParam {
/**
*添加的校验分组接口
*/
public interface add{}
/**
* 修改的校验分组接口
*/
public interface update{}
@NotNull(message = "文章id不能为空",groups = update.class)
@Min(value = 1,message = "文章ID不能为负数",groups = update.class)
private Integer id;
@NotBlank(message = "文章内容不能为空",groups = {add.class,update.class})
private String content;
@NotBlank(message = "作者Id不能为空",groups = {add.class,update.class})
private String authorId;
@Future(message = "提交时间不能为过去时间",groups = {add.class,update.class})
private Date submitTime;
}
JSR303本身的@Valid并不支持分组校验,但是Spring在其基础提供了一个注解@Validated支持分组校验。@Validated这个注解value属性指定需要校验的分组。
@RestController
@RequestMapping("/api/test")
@Api(value = "TestController", tags = { "测试类" })
public class TestController implements HandleExceptionController {
@ApiOperation(value = "测试查询", notes = "测试查询")
@PostMapping(value = "/queryTest")
public ResponseResult queryTest(@RequestBody @Validated(value =QueryTestParam.add.class ) QueryTestParam param){
JSONObject result = new JSONObject();
return new ResponseResult(ResultEnum.SUCCESS,result);
}
}
嵌套校验
嵌套校验 就是一个实体中包含了另一个实体 而这两个实体都需要校验
嵌套校验很简单,只需要在嵌套的实体属性标注@Valid注解,则其中的属性也将会得到校验,否则不会校验。
/**
* @author liang
* @date 2021年12月29日 14:23
*/
public class QueryTestParam {
@Valid
private AddParam addParam;
}
最后附赠一个校验工具类 可以在代码里随时对对象进行javax的校验
import org.hibernate.validator.HibernateValidator;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.util.Set;
/**
* @author liang
*/
public class ValidatorUtils {
private static final Validator VALIDATOR_ALL = Validation.byProvider(HibernateValidator.class).configure().failFast(false).buildValidatorFactory().getValidator();
/**
* 校验不通过则抛出去自定义异常 {@link ServiceException}
* @param obj 需要校验的对象
* @param groups 需要校验的分组(可变长参数列表)
* @param <T> 对象类型
*/
public static <T> void validatorByGroups(T obj, Class<?>... groups) {
Set<ConstraintViolation<T>> constraintViolations = VALIDATOR_ALL.validate(obj, groups);
// 抛出检验异常
if (constraintViolations.size() > 0) {
throw new ServiceException(ResultEnum.REQUIRED_MISSING.getCode(),constraintViolations.iterator().next().getMessage());
}
}
/**
* 校验不通过则抛出去自定义异常 {@link ServiceException}
* @param obj 需要校验的对象
* @param <T> 对象类型
*/
public static <T> void validatorObject(T obj) {
Set<ConstraintViolation<T>> constraintViolations = VALIDATOR_ALL.validate(obj);
// 抛出检验异常
if (constraintViolations.size() > 0) {
throw new ServiceException(ResultEnum.REQUIRED_MISSING.getCode(),constraintViolations.iterator().next().getMessage());
}
}
}