目录
一、前言
二、JSR303 简介
三、使用方法
常用注解
@Validated、@Valid区别
四、编写测试代码:
1. 实体类添加校验
2. 统一返回类型
3. 测试类
4.我们把异常返回给页面
5.抽离全局异常处理
2. 书写ExceptionControllerAdvice
一、前言
我们在日常开发中,避不开的就是参数验证,有人说前端不是回在表单证进行校验吗,在后端中,我们可以直接不管前端怎么做判断过滤,在后端中为了安全,还是需要进行判断的。在前端做校验是很容易绕过的,举个例子,当测试使用PpostMan
时,如果后端没有校验,肯定回出现很多异常。今天就和大家来一起学习 JSR303
专门用于参数校验。
二、JSR303 简介
JSR-303
是 JAVA EE 6 中的一项子规范,叫做 Bean Validation
,官方参考实现是Hibernate Validator
。Hibernate Validator
提供了 JSR 303
规范中所有内置 constraint
的实现,除此之外还有一些附加的 cons
traint
。
三、使用方法
在 SpringBoot
项目的 pom.xml
文件中导入 JSR303
数据校验的启动依赖
创建 SpringB
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>3.0.1</version>
</dependency>
oot 的方式,有兴趣的朋友可以参考SpringBoot 快速入门(保姆级详细教程)
pom.xml
文件:
常用注解
@Validated、@Valid区别
@Validated:
-
Spring提供的
-
支持分组校验
-
可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上
-
由于无法加在成员属性(字段)上,所以无法单独完成级联校验,需要配合@Valid
@Valid:
-
JDK提供的(标准JSR-303规范)
-
不支持分组校验
-
可以用在方法、构造函数、方法参数和成员属性(字段)上
-
可以加在成员属性(字段)上,能够独自完成级联校验
总结:@Validated
用到分组时使用,一个学校对象里还有很多个学生对象需要使用
@Validated在Controller
方法参数前加上
,@Valid
加在学校中的学生属性上,不加则无法对学生对象里的属性进行校验!
四、编写测试代码:
1. 实体类添加校验
package com.javaClass.entity;
import lombok.Data;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import java.io.Serializable;
@Data
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 品牌id
*/
@NotNull(message = "修改必须有品牌id")
private Long brandId;
/**
* 品牌名F
*/
@NotBlank(message = "品牌名必须提交")
private String name;
/**
* 品牌logo地址
*/
@NotBlank(message = "地址必须不为空")
private String logo;
/**
* 介绍
*/
private String descript;
/**
* 检索首字母
*/
//正则表达式
@Pattern(regexp = "^[a-zA-Z]$",message = "检索的首字母必须是字母")
private String firstLetter;
/**
* 排序
*/
@Min(value = 0,message = "排序必须大于等于0")
private Integer sort;
}
2. 统一返回类型
import com.alibaba.druid.util.StringUtils;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
//统一返回结果
@Data
@NoArgsConstructor
@AllArgsConstructor
@ApiModel
public class Result<T> {
@ApiModelProperty("响应码")
private Integer code;
@ApiModelProperty("相应信息")
private String msg;
@ApiModelProperty("返回对象或者集合")
private T data;
//成功码
public static final Integer SUCCESS_CODE = 200;
//成功消息
public static final String SUCCESS_MSG = "SUCCESS";
//失败
public static final Integer ERROR_CODE = 201;
public static final String ERROR_MSG = "系统异常,请联系管理员";
//没有权限的响应码
public static final Integer NO_AUTH_COOD = 999;
//执行成功
public static <T> Result<T> success(T data){
return new Result<>(SUCCESS_CODE,SUCCESS_MSG,data);
}
//执行失败
public static <T> Result failed(String msg){
msg = StringUtils.isEmpty(msg)? ERROR_MSG : msg;
return new Result(ERROR_CODE,msg,"");
}
//传入错误码的方法
public static <T> Result failed(int code,String msg){
msg = StringUtils.isEmpty(msg)? ERROR_MSG : msg;
return new Result(code,msg,"");
}
//传入错误码的数据
public static <T> Result failed(int code,String msg,T data){
msg = StringUtils.isEmpty(msg)? ERROR_MSG : msg;
return new Result(code,msg,data);
}
}
3. 测试类
package com.javaClass.zcm.controller;
import com.javaClass.zcm.entity.BrandEntity;
import com.javaClass.zcm.utils.Result;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
@RestController
@RequestMapping("/hello")
public class testController {
@PostMapping("/add")
public Result add(@Valid @RequestBody BrandEntity brandEntity) {
return Result.success("成功");
}
}
通过 postMan 进行测试
首次测试结果如下:
4.我们把异常返回给页面
@PostMapping("/add")
public Result add(@Valid @RequestBody BrandEntity brandEntity, BindingResult bindingResult){
if (bindingResult.hasErrors()){
Map<String,String> map = new HashMap<>();
bindingResult.getFieldErrors().forEach(item ->{
map.put(item.getField(),item.getDefaultMessage());
});
return Result.failed(400,"提交的数据不合规范",map);
}
return Result.success("成功");
}
这次测试结果如下:
5.抽离全局异常处理
1. 心得体会
上面我们要在每个校验的接口上面写,所以我们要抽离出来做个全局异常。并且要改进一下,原来的是把错误信息放到data里,但是正常情况下的data是返回给前端的数据。我们这样把异常数据放进去,会使data的数据有二义性。这样对于前端就不知道里面是数据还是报错信息了哈,这样就可以直接前端展示msg里面的提示即可!
2. 书写ExceptionControllerAdvice
package com.javaClass.zcm.config;
import com.javaClass.zcm.utils.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@Slf4j
@RestControllerAdvice(basePackages = "com.wang.test.demo.controller")
public class ExceptionControllerAdvice {
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public Result handleVaildException(MethodArgumentNotValidException e){
log.error("数据校验出现问题:{},异常类型:{}",e.getMessage(),e.getClass());
BindingResult bindingResult = e.getBindingResult();
StringBuffer stringBuffer = new StringBuffer();
bindingResult.getFieldErrors().forEach(item ->{
//获取错误信息
String message = item.getDefaultMessage();
//获取错误的属性名字
String field = item.getField();
stringBuffer.append(field + ":" + message + " ");
});
return Result.failed(400, stringBuffer + "");
}
@ExceptionHandler(value = Throwable.class)
public Result handleException(Throwable throwable){
log.error("错误",throwable);
return Result.failed(400, "系统异常");
}
}
测试结果:
{
"code": 400,
"data": "",
"msg": "logo:地址必须不为空 name:品牌名必须提交 "
}
关注 “JAVA学习课堂” 微信公众号,获取更多学习笔记。