①. JSR是Java Specification Requests的缩写,意思是Java规范提案,JSR-303是JAVA EE6中的一项子规范,叫做Bean Validation即,JSR 303,Bean Validation规范 ,为Bean验证定义了元数据模型和API。默认的元数据模型是通过Annotations来描述的,但是也可以使用XML来重载或者扩展。
②. 如何使用
在Java中提供了一系列的校验方式,它这些校验方式在“javax.validation.constraints”包中,提供了如@Email,@NotNull等注解
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-validationartifactId>
dependency>
注解 | 解释 |
---|---|
@NotNull | 属性不能为null,无法查检长度为0的字符串 |
@NotBlank | 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格 |
@NotEmpty | 该字段不能为null或"" |
被注释的元素必须是电子邮箱地址 | |
@Length | 被注释的字符串的大小必须在指定的范围内 |
@Range | 被注释的元素必须在合适的范围内 |
@Pattern(value) | 被注释的元素必须符合指定的正则表达式 |
@Min(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@Max(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 品牌id
*/
@TableId
private Long brandId;
/**
* 品牌名
*/
@NotBlank(message = "品牌名称不能为空")
private String name;
/**
* 品牌logo地址
*/
@NotEmpty
@URL(message = "logo必须是一个合法的url地址")
private String logo;
/**
* 介绍
*/
private String descript;
/**
* 显示状态[0-不显示;1-显示]
*/
private Integer showStatus;
/**
* 检索首字母
*/
@NotEmpty
@Pattern(regexp = "/^[a-zA-Z]$/",message = "检索首字母必须是一个字母")
private String firstLetter;
/**
* 排序
*/
@NotNull
@Min(value = 0)
private Integer sort;
}
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand){
brandService.save(brand);
return R.ok();
}
{
"timestamp": "2020-04-29T09:36:04.125+0000",
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"NotBlank.brandEntity.name",
"NotBlank.name",
"NotBlank.java.lang.String",
"NotBlank"
],
"arguments": [
{
"codes": [
"brandEntity.name",
"name"
],
"arguments": null,
"defaultMessage": "name",
"code": "name"
}
],
"defaultMessage": "品牌名称不能为空",
"objectName": "brandEntity",
"field": "name",
"rejectedValue": "",
"bindingFailure": false,
"code": "NotBlank"
}
],
"message": "Validation failed for object='brandEntity'. Error count: 1",
"path": "/product/brand/save"
}
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand,
BindingResult result){ // 手动处理异常
if( result.hasErrors()){
Map<String,String> map=new HashMap<>();
//1.获取错误的校验结果
result.getFieldErrors().forEach((item)->{
//获取发生错误时的message
String message = item.getDefaultMessage();
//获取发生错误的字段
String field = item.getField();
map.put(field,message);
});
return R.error(400,"提交的数据不合法").put("data",map);
}
brandService.save(brand);
return R.ok();
}
@Slf4j
@RestControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")//管理的controller
public class GulimallExceptionControllerAdvice {
@ExceptionHandler(value = Exception.class) // 也可以返回ModelAndView
public R handleValidException(MethodArgumentNotValidException exception){
Map<String,String> map=new HashMap<>();
// 获取数据校验的错误结果
BindingResult bindingResult = exception.getBindingResult();
// 处理错误
bindingResult.getFieldErrors().forEach(fieldError -> {
String message = fieldError.getDefaultMessage();
String field = fieldError.getField();
map.put(field,message);
});
log.error("数据校验出现问题{},异常类型{}",exception.getMessage(),exception.getClass());
return R.error(400,"数据校验出现问题").put("data",map);
}
}
//如果在异常类上这些都没有进行匹配,最后都会由这个异常进行处理
@ExceptionHandler(value = Throwable.class)//异常的范围更大
public R handleException(Throwable throwable){
log.error("未知异常{},异常类型{}",
throwable.getMessage(),
throwable.getClass());
return R.error(BizCodeEnum.UNKNOW_EXEPTION.getCode(),
BizCodeEnum.UNKNOW_EXEPTION.getMsg());
}
①. 上面代码中,针对于错误状态码,是我们进行随意定义的,然而正规开发过程中,错误状态码有着严格的定义规则,如该在项目中我们的错误状态码定义
上面的用法主要是通过@RestControllerAdvice+@ExceptionHandler来进行异常拦截处理
②. 为了定义这些错误状态码,我们可以单独定义一个常量类,用来存储这些错误状态码
package com.atguigu.common.exception;
/***
* 错误码和错误信息定义类
* 1. 错误码定义规则为5为数字
* 2. 前两位表示业务场景,最后三位表示错误码。例如:100001。10:通用 001:系统未知异常
* 3. 维护错误码后需要维护错误描述,将他们定义为枚举形式
* 错误码列表:
* 10: 通用
* 001:参数格式校验
* 11: 商品
* 12: 订单
* 13: 购物车
* 14: 物流
*/
public enum BizCodeEnum {
UNKNOW_EXEPTION(10000,"系统未知异常"),
VALID_EXCEPTION( 10001,"参数格式校验失败");
private Integer code;
private String msg;
BizCodeEnum(Integer code,String msg){
this.code=code;
this.msg=msg;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
/**
* 集中处理所有的异常
*/
@Slf4j
//@ControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
@RestControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
//@RestControllerAdvice=@ControllerAdvice+@ResponseBody
public class GulimallExceptionControllerAdvice {
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public R handleValidException(MethodArgumentNotValidException exception){
log.error("数据校验出现问题{},异常类型{}", exception.getMessage(), exception.getClass());
if (exception.getBindingResult().hasErrors()){
Map<String,String>map=new HashMap<>();
exception.getBindingResult().getFieldErrors().forEach((result)->{
map.put(result.getField(),result.getDefaultMessage());
});
return R.error(BizCodeEnum.VALID_EXCEPTION.getCode(),BizCodeEnum.VALID_EXCEPTION.getMsg()).put("data",map);
}
return R.error();
}
}
①. 前面解决了统一异常处理,但是现状有新的需求是对同一实体类参数也要区分场景
如果新增和修改两个接口需要验证的字段不同,比如id字段,新增可以不传递,但是修改必须传递id,我们又不可能写两个vo来满足不同的校验规则。所以就需要用到分组校验来实现。
②.分组校验步骤:
/**
* 品牌id
*/
@NotNull(message ="修改必须指定品牌id",groups ={UpdateGroup.class} )
@Null(message ="新增不能指定品牌id",groups ={AddGroup.class} )
@TableId
private Long brandId;
/**
* 品牌logo地址 修改可以不带上logoURL
*/
//注意下面因为@NotBlank没有指定UpdateGroup分组,所以不生效。
//此时update时可以不携带,但带了一定得是合法的url地址
@NotBlank(groups = {AddGroup.class})
@URL(message = "logo必须是一个合法的URL地址", groups={AddGroup.class, UpdateGroup.class})
private String logo;
@RequestMapping("/save")
//public R save(@Valid @RequestBody BrandEntity brand/*, BindingResult result*/){
public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand/*, BindingResult result*/){
// if(result.hasErrors()){
// Mapmap=new HashMap<>();
// //1.获取错误的校验结果
// result.getFieldErrors().forEach((item)->{
// //获取发生错误的字段
// String field = item.getField();
// //获取发生错误时的message
// String message = item.getDefaultMessage();
// map.put(field,message);
// });
// return R.error(400,"提交的数据不合法").put("data",map);
// }
brandService.save(brand);
return R.ok();
}
/**
* 显示状态[0-不显示;1-显示]
*/
//自定义注解
@ListValue(vals={0,1},groups = AddGroup.class)
private Integer showStatus;
<!--校验-->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
@Documented
@Constraint(validatedBy = {ListValueConstraintValidator.class})
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ListValue {
String message() default "{com.atguigu.common.valid.ListValue.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
int []vals()default {};
}
com.atguigu.common.valid.ListValue.message=必须提交指定的值[0,1]
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {//<注解,校验值类型>
private Set<Integer> set = new HashSet<>();
//初始化方法
@Override
// 存储所有可能的值
public void initialize(ListValue constraintAnnotation) {
// 获取后端写好的限制 // 这个value就是ListValue里的value,我们写的注解是@ListValue(value={0,1})
int[] vals = constraintAnnotation.vals();
for (int val : vals) {
set.add(val);
}
}
//判断是否校验成功
/**
*
* @param value 需要校验的值
* @param context
* @return
*/
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
// 看是否在限制的值里
return set.contains(value);
}
}
@Constraint(validatedBy = { ListValueConstraintValidator.class})
/**
* 显示状态[0-不显示;1-显示]
*/
//自定义注解
@ListValue(vals={0,1},groups = AddGroup.class)
private Integer showStatus;