1、传统的参数校验方式
搭建项目结构
@Data
@ToString
@EqualsAndHashCode
@AllArgsConstructor
@NoArgsConstructor
public class Department {
private Integer id;
private Integer parentId;
private String name;
private LocalDateTime createTime;
}
String errorPrefix = "添加失败,";
if (department.getId() != null) {
return errorPrefix + "实体参数异常";
}
if (department.getParentId() == null) {
return errorPrefix + "父机构异常";
}
if (department.getParentId() < 0) {
return errorPrefix + "父机构不存在";
}
if (department.getName() == null || department.getName().equals("")) {
return errorPrefix + "名称不能为空";
}
if (department.getCreateTime() == null) {
department.setCreateTime(LocalDateTime.now());
}else {
if (department.getCreateTime().isAfter(LocalDateTime.now())) {
return errorPrefix + "创建时间不能大于当前时间";
}
}
id 必须是 null (因为id是主键)
parentId 不能为 null,必须大于 0
name 不能为空,长度必须大于 0
createTime 肯定不是未来的时间
2、开始使用validator
- 引入
validator 的依赖,非web 场景(这里开发场景使用web开发场景,所以我们不单独引入该依赖)。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>2.1.9.RELEASE</version>
</dependency>
- 引入springboot的web开发场景,如下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
@JsonInclude(JsonInclude.Include.NON_NULL)
@Data
@ToString
@EqualsAndHashCode
@AllArgsConstructor
@NoArgsConstructor
public class ResultVO {
private boolean success;
private String code;
private String msg;
private Object data;
public static ResultVO success() {
ResultVO resultVO = new ResultVO();
resultVO.setSuccess(true);
return resultVO;
}
public static ResultVO success(Object data) {
ResultVO resultVO = new ResultVO();
resultVO.setSuccess(true);
resultVO.setData(data);
return resultVO;
}
public static ResultVO fail(ErrorCode errorCode) {
ResultVO resultVO = new ResultVO();
resultVO.setSuccess(false);
resultVO.setCode(errorCode.getCode());
resultVO.setMsg(errorCode.getMsg());
return resultVO;
}
public static ResultVO fail(ErrorCode errorCode, Object data) {
ResultVO resultVO = new ResultVO();
resultVO.setSuccess(false);
resultVO.setCode(errorCode.getCode());
resultVO.setMsg(errorCode.getMsg());
resultVO.setData(data);
return resultVO;
}
}
- 创建一个
enum 包,该包下创建ErrorCode 的枚举类,用来声明一些错误状态码,和错误信息。 - 新建
controller包 ,在该包中创建DepartmentController 类,该类声明如下测试方法。 - 目前的项目结构:
- 请求
DeparementController 中的add 方法。注意下面标红信息。
- 在
add 方法参数加了@Valid
- postman测试,可以看到校验结果。查看到校验不通过的结果太难看了。下面进行异常处理。
- 新建
exception 包,在下面建一个CtrlAdvice 的类,声明如下: - 在上面的debug断点处,我们可以查看到异常类型为``
- 修改上面的异常,只获取重要的信息
- 这样看就清爽多了。
- 下面圈住的使用
@Validated 也是可以的。
3、级联验证(一对一、一对多)
一对一
通俗来讲,就是一个对象里面有另一个对象。‘ 在上面的entity 包中新建实体类Employee ,声明如下:
- 级联验证就是,如果在一个实体类中有另一个实体类属性,而另一个实体类上也加了注解验证(
Department 实体类中,加了注解验证),那么,另一个实体类的属性上面的注解能否生效。 - 新建一个
EmployeeController ,声明addList 方法如下: - 使用postman访问
- 解决上面
Department 的属性没有进行校验的问题。在Employee 中的Department 类型的属性上加@Valid 注解。 - 使用postman再次访问,发现
department 上面的也可以被校验了。
一对多
通俗来讲,一对多就是一个实体类中,有一个另一个实体类的集合。如下面:
- 使用postman进行访问,发现校验成功。
总结 对于级联验证,只要在要验证的实体类属性上面添加@Valid 注解就行。(对于一对多的有两种校验,一种是加在属性上,另一种加在List<@Valid Department> )
4. 在service中做参数验证
情况一:不实现service接口
项目中,有可能参数校验不在Controller 层中,而是在Service 层进行参数校验。
- 创建
service 层,并创建一个DepartmentService 的实体类(这个实体类不实现接口,按照规范是:DepartmentServiceImpl 实DepartmentService 接口,这里不这样做,直接DepartmentService 声明为class ,而不是interface ),声明如下: - 更改
DepartmentController 如下: - 使用postman工具,访问上面的接口:
- 发现保错了,在
idea 控制台查看,报错误信息javax.validation.ConstraintViolationException : - 在上面的
exception 包中的CtrlAdvice 类中,声明一个异常处理,来捕获该异常(controller调用service,service层的异常抛给controller,添加的异常处理类,可以处理controller中的异常信息): - 所以,下面写一个捕获该异常的方法,并且将上面的信息提炼出来,作为响应信息。
@ExceptionHandler
public ResultVO exceptionHandler(ConstraintViolationException ce){
Map<String,String> collect =new HashMap<>();
ce.getConstraintViolations().forEach(constraintViolation -> {
PathImpl propertyPath = (PathImpl) constraintViolation.getPropertyPath();
NodeImpl leafNode = propertyPath.getLeafNode();
String name = leafNode.getName();
String value = constraintViolation.getMessageTemplate();
collect.put(name,value);
});
return ResultVO.fail(ErrorCode.PARAM_ERROR,collect);
}
- postman测试,查看响应结果
情况二:实现service接口
- 创建在service包下,创建
IEmployee 接口,在serviceimpl 中创建EmployService 实现类,该类实现IEmployee 接口。此时项目结构如下: IEmployeeService 方法声明如下: EmployeeService 声明如下: EmployeeController 声明如下,访问下面的方法 - postman请求结果
总结: 对于在service 层的校验,如果不实现接口的话,在类上加 @Validated 注解,并且在验证的对象参数前加@Valid 注解。对于有接口,有实现类的情况,@valid 一定加在接口中抽象方法的形参前面,@Validated 可以加在实现类上,也可以加在接口上,不能不加,我习惯加在实现类上。
5、方法上参数的验证(使用参数接收表单数据)
使用多个参数一一接收表单数据
EmployeeController 实体类做出如下的更改,声明如下: - 将
name=刘 ,·age=250·,使用postman测试,查看异常信息。使用debug启动。 - postman发送的参数如下:
- 发现上述异常信息,很工整,肯定是被哪个异常捕获了,下面以
debug 启动,在下面位置打上断点: - debug进入了上面的异常方法中,也就是校验service层使用对象作为参数时,不满足条件所抛出的异常。
- 上面的代码的可复制版:
@ExceptionHandler
public ResultVO exceptionHandler(ConstraintViolationException ce){
Map<String,String> collect =new HashMap<>();
ce.getConstraintViolations().forEach(constraintViolation -> {
PathImpl propertyPath = (PathImpl) constraintViolation.getPropertyPath();
NodeImpl leafNode = propertyPath.getLeafNode();
String name = leafNode.getName();
String value = constraintViolation.getMessageTemplate();
collect.put(name,value);
});
return ResultVO.fail(ErrorCode.PARAM_ERROR,collect);
}
使用对象接收多个表单数据
-
DepartmentController 更改如下,下面的Department实体类还是上面的,上面加了注解校验:(可以看到,当参数是一个对象时,@valid或者@validated可以在该对象参数前面) -
不加任何异常处理方法,直接使用postman测试,查看控制台信息 -
发现是BindException 异常,下面我们还在CtrlAdvice 类中定义处理该异常的方法: -
根据上面的信息,写出我们想要的结果的处理方法。 -
完成,在该方法上debug调试,看看能否得到想要的结果:postman测试结果如下: -
异常处理方法,可copy版本:
@ExceptionHandler
public ResultVO exceptionHandler(BindException b){
List<ObjectError> allErrors = b.getBindingResult().getAllErrors();
Map<String,String> collect = new HashMap<>();
allErrors.forEach(error->{
FieldError fieldError =(FieldError)error;
collect.put(fieldError.getField(),fieldError.getDefaultMessage());
});
return ResultVO.fail(ErrorCode.PARAM_ERROR,collect);
}
6、分组校验
就是校验你想校验的那个或者那些属性。
-
修改上面的Employee 实体类,声明如下: 在这里插入图片描述 -
EmployeeController 声明如下: -
使用postman访问controller方法,响应信息如下: -
更改EmployeeController 中的方法,将@Validated 中的group做出更改如下: -
postman结果: -
查看该校验被哪个异常方法所捕获。 在这里插入图片描述
- 通过以上测试可以发现,如果进行了分组的话,只会对已经分组的属性进行校验(上面的
name 属性,id 属性,那么如何校验没有分组的属性呢,如上面的age 属性。结论,如果类中的属性,没有被分到组的话,则这些属性都属于是默认组) - 更改
Employee 实体类,如下:(未完。。。抽时间接着测试)
|