安徽省建设干部学校网站首页,杭州排名推广,上海浦东人才市场招聘会,wordpress添加页面本笔记内容为尚硅谷谷粒商城JSR303数据校验和集中异常处理部分
目录
一、简介
二、SR303数据校验使用步骤
1、引入依赖
2、给参数对象添加校验注解
常见的注解
3、接口参数前增加Valid 开启校验
三、异常的统一处理
四、分组解决校验
1、创建Groups
2、添加分组
…本笔记内容为尚硅谷谷粒商城JSR303数据校验和集中异常处理部分
目录
一、简介
二、SR303数据校验使用步骤
1、引入依赖
2、给参数对象添加校验注解
常见的注解
3、接口参数前增加Valid 开启校验
三、异常的统一处理
四、分组解决校验
1、创建Groups
2、添加分组
五、自定义校验注解
1、创建约束规则
2、创建约束校验器
3、自定义校验注解校验失败后的返回信息 一、简介 JSR-303 是 JAVA EE 6 中的一项子规范叫做 Bean Validation。 在任何时候当你要处理一个应用程序的业务逻辑数据校验是你必须要考虑和面对的事情。应用程序必须通过某种手段来确保输入进来的数据从语义上来讲是正确的。在通常的情况下应用程序是分层的不同的层由不同的开发人员来完成。很多时候同样的数据验证逻辑会出现在不同的层这样就会导致代码冗余和一些管理的问题比如说语义的一致性等。为了避免这样的情况发生最好是将验证逻辑与相应的域模型进行绑定。 Bean Validation 为 JavaBean 验证定义了相应的元数据模型和 API。缺省的元数据是 Java Annotations通过使用 XML 可以对原有的元数据信息进行覆盖和扩展。在应用程序中通过使用 Bean Validation 或是你自己定义的 constraint例如 NotNull, Max, ZipCode 就可以确保数据模型JavaBean的正确性。constraint 可以附加到字段getter 方法类或者接口上面。对于一些特定的需求用户可以很容易的开发定制化的 constraint。Bean Validation 是一个运行时的数据验证框架在验证之后验证的错误信息会被马上返回。 后端在处理前端传过来的数据时尽管前端表单已经加了校验逻辑但是作为严谨考虑在后端对接口传输的数据做校验也必不可少。
二、SR303数据校验使用步骤 1、引入依赖
使用spring-boot-starter-web包里面有hibernate-validator包,不需要引用hibernate validator依赖。 2、给参数对象添加校验注解
常见的注解 Bean Validation 中内置的 constraint 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(regex,flag) 被注释的元素必须符合指定的正则表达式 Hibernate Validator 附加的 constraint NotBlank(message ) 验证字符串非null且长度必须大于0 Email 被注释的元素必须是电子邮箱地址 Length(min,max) 被注释的字符串的大小必须在指定的范围内 NotEmpty 被注释的字符串的必须非空 Range(min,max,message) 被注释的元素必须在合适的范围内 示例 3、接口参数前增加Valid 开启校验
Controller 中需要校验的参数Bean前添加 Valid 开启校验功能紧跟在校验的Bean后添加一个BindingResultBindingResult封装了前面Bean的校验结果。 /*** 保存*/RequestMapping(/save)//RequiresPermissions(product:brand:save)public R save(Valid RequestBody BrandEntity brand, BindingResult result){if (result.hasErrors()){MapString,String mapnew HashMap();//1.获取校验错误结果result.getFieldErrors().forEach((item)-{//获取到错误提示String message item.getDefaultMessage();//获取到错误的属性名String fielditem.getField();map.put(field,message);});return R.error(400,提交数据不合法).put(data,map);}else{brandService.save(brand);}return R.ok();}
三、异常的统一处理 参数校验不通过时会抛出 BingBindException 异常可以在统一异常处理中做统一处理这样就不用在每个需要参数校验的地方都用 BindingResult 获取校验结果了。 可以在common中新建一个枚举用于存放我们异常类型
例
package com.atguigu.common.exception;/*** Description: 错误状态码枚举** 错误码和错误信息定义类* 1. 错误码定义规则为5为数字* 2. 前两位表示业务场景最后三位表示错误码。例如100001。10:通用 001:系统未知异常* 3. 维护错误码后需要维护错误描述将他们定义为枚举形式* 错误码列表* 10: 通用* 001参数格式校验* 002短信验证码频率太高* 11: 商品* 12: 订单* 13: 购物车* 14: 物流* 15用户*****/public enum BizCodeEnum {UNKNOW_EXCEPTION(10000,系统未知异常),VAILD_EXCEPTION(10001,参数格式校验失败),TO_MANY_REQUEST(10002,请求流量过大请稍后再试),SMS_CODE_EXCEPTION(10002,验证码获取频率太高请稍后再试),PRODUCT_UP_EXCEPTION(11000,商品上架异常),USER_EXIST_EXCEPTION(15001,存在相同的用户),PHONE_EXIST_EXCEPTION(15002,存在相同的手机号),NO_STOCK_EXCEPTION(21000,商品库存不足),LOGINACCT_PASSWORD_EXCEPTION(15003,账号或密码错误),;private Integer code;private String message;BizCodeEnum(Integer code, String message) {this.code code;this.message message;}public Integer getCode() {return code;}public String getMessage() {return message;}
}在exception包下创建一个异常类
例
package com.atguigu.gulimall.product.exception;import com.atguigu.common.exception.BizCodeEnum;
import com.atguigu.common.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;import java.util.HashMap;
import java.util.Map;/*** Description: 集中处理异常**/
Slf4j
/*
RestController
ControllerAdvice(basePackages com.atguigu.gulimall.product.controller)
*/
RestControllerAdvice(basePackages com.atguigu.gulimall.product.controller)
public class GulimallExceptionControlleAdvice {//捕获异常指定异常 MethodArgumentNotValidExceptionExceptionHandler(value MethodArgumentNotValidException.class)public R handValidException(MethodArgumentNotValidException e){log.error(数据校验出现问题{}异常类型{},e.getMessage(),e.getClass());BindingResult bindingResult e.getBindingResult();MapString,String mapnew HashMap();bindingResult.getFieldErrors().forEach((item)-{map.put(item.getField(),item.getDefaultMessage());});return R.error(BizCodeEnum.VAILD_EXCEPTION.getCode(), BizCodeEnum.VAILD_EXCEPTION.getMessage()).put(data,map);}//任意异常类型ExceptionHandler(value Throwable.class)public R handException(Throwable throwable){return R.error(BizCodeEnum.UNKNOW_EXCEPTION.getCode(), BizCodeEnum.UNKNOW_EXCEPTION.getMessage());}
}然后只需要校验的参数Bean前添加 Valid 开启校验功能就可以了 例
RequestMapping(/save)public R save(Valid RequestBody BrandEntity brand){brandService.save(brand);return R.ok();} 四、分组解决校验 新增和修改对于实体的校验规则是不同的例如id是自增的时新增时id要为空修改则必须不为空新增和修改若用的恰好又是同一种实体那就需要用到分组校验。 校验注解都有一个groups属性可以将校验注解分组。 1、创建Groups 从源码可以看出 groups 是一个Class?类型的数组那么就可以创建一个Groups 或
package com.xxxx.common.validator.group;/*** 新增数据 Group*/
public interface AddGroup {
}package com.xxxx.common.validator.group;/*** 更新数据 Group**/public interface UpdateGroup {}2、添加分组
给参数对象的校验注解添加分组
例
package com.atguigu.gulimall.product.entity;import com.atguigu.common.valid.AddGroup;
import com.atguigu.common.valid.ListValue;
import com.atguigu.common.valid.UpdateGroup;
import com.atguigu.common.valid.UpdateStatusGroup;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;import java.io.Serializable;
import java.util.Date;
import lombok.Data;
import org.hibernate.validator.constraints.URL;import javax.validation.constraints.*;/*** 品牌* */
Data
TableName(pms_brand)
public class BrandEntity implements Serializable {private static final long serialVersionUID 1L;/*** 品牌id*/NotNull(message 修改必须指定品牌ID,groups {UpdateGroup.class})Null(message 新增不能指定ID,groups {AddGroup.class})TableIdprivate Long brandId;/*** 品牌名*/NotBlank(message 品牌名必须提交,groups {AddGroup.class,UpdateGroup.class})private String name;/*** 品牌logo地址*/NotBlank(groups {AddGroup.class})URL(message logo必须是一个合法的url地址,groups {AddGroup.class,UpdateGroup.class})private String logo;/*** 介绍*/private String descript;/*** 显示状态[0-不显示1-显示]*/NotNull(groups {AddGroup.class, UpdateStatusGroup.class})ListValue(vals {0,1},groups {AddGroup.class, UpdateStatusGroup.class})private Integer showStatus;/*** 检索首字母*/NotEmpty(groups {AddGroup.class})//Pattern自定义可以传入正则表达式Pattern(regexp ^[a-zA-Z]$,message 检索字母必须是一个字母,groups {AddGroup.class,UpdateGroup.class})private String firstLetter;/*** 排序*/NotNull(groups {AddGroup.class})Min(value 0,message 排序必须大于等于0,groups {AddGroup.class,UpdateGroup.class})private Integer sort;}Controller 中原先的Valid不能指定分组 需要替换成Validated RequestMapping(/save)//RequiresPermissions(product:brand:save)public R save(/*Valid*/ Validated({AddGroup.class}) RequestBody BrandEntity brand/*, BindingResult result*/){/*if (result.hasErrors()){MapString,String mapnew HashMap();//1.获取校验错误结果result.getFieldErrors().forEach((item)-{//获取到错误提示String message item.getDefaultMessage();//获取到错误的属性名String fielditem.getField();map.put(field,message);});return R.error(400,提交数据不合法).put(data,map);}else{brandService.save(brand);}*/brandService.save(brand);return R.ok();}
五、自定义校验注解 虽然JSR303和springboot-validator 已经提供了很多校验注解但是当面对复杂参数校验时还是不能满足我们的要求这时候我们就需要自定义校验注解。 假设一个字段规定使用0和1两种状态 点进去NotNull查看 发现这些接口都有这些相同的信息 1、创建约束规则
我们可以跟着创建约束规则ListValue
/*** Description: 自定义注解**/Documented
Constraint(validatedBy { ListValueConstraintValidator.class }) /*关联注解器-可以指定多个*/
Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
Retention(RUNTIME)
public interface ListValue {// 默认会找ValidationMessages.propertiesString message() default {com.atguigu.common.valid.ListValue.message};Class?[] groups() default { };Class? extends Payload[] payload() default { };// 可以指定数据只能是vals数组指定的值int[] vals() default { };}
2、创建约束校验器
新建自定义校验器ListValuaConstraintValidator并实现ConstraintValidator /*** Description: 校验器**/
public class ListValueConstraintValidator implements ConstraintValidatorListValue,Integer {private SetInteger set new HashSet();/*** 初始化方法* param constraintAnnotation*/Overridepublic void initialize(ListValue constraintAnnotation) {int[] vals constraintAnnotation.vals();for (int val : vals) {set.add(val);}}/*** 判断是否效验成功* param value 需要效验的值* param context* return*/Overridepublic boolean isValid(Integer value, ConstraintValidatorContext context) {//判断是否有包含的值boolean contains set.contains(value);return contains;}}3、自定义校验注解校验失败后的返回信息
新建配置文件ValidationMessages.properties定义我们自定义校验注解校验失败后的返回信息
com.xxx.common.valid.ListValue.message 必须提交指定参数 例
com.atguigu.common.valid.ListValue.message必须提交指定的值
记得在刚刚编写好的自定义注解ListVaue中关联我们自定义的检验器 最后我们在使用ListValue时就可以固定设定只能接收哪些值了 测试 六、总结 JSR303
* 1、给Bean添加校验注解:javax.validation.constraints并定义自己的message提示
* 2)、开启校验功能Valid
* 效果校验错误以后会有默认的响应
* 3、给校验的bean后紧跟一个BindingResult就可以获取到校验的结果
* 4、分组校验多场景的复杂校验
* 1)、 NotBlank(message 品牌名必须提交,groups {AddGroup.class,UpdateGroup.class})
* 给校验注解标注什么情况需要进行校验
* 2、Validated({AddGroup.class})
* 3)、默认没有指定分组的校验注解NotBlank在分组校验情况Validated({AddGroup.class})下不生效只会在Validated生效
*
* 5、自定义校验
* 1、编写一个自定义的校验注解
* 2、编写一个自定义的校验器 ConstraintValidator
* 3、关联自定义的校验器和自定义的校验注解
* Documented
* Constraint(validatedBy { ListValueConstraintValidator.class【可以指定多个不同的校验器适配不同类型的校验】 })
* Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
* Retention(RUNTIME)
* public interface ListValue { 统一的异常处理
* ControllerAdvice
* 1、编写异常处理类使用ControllerAdvice。
* 2、使用ExceptionHandler标注方法可以处理的异常。结束