当前位置: 首页 > news >正文

商城项目10_JSR303常用注解、在项目中如何使用、统一处理异常、分组校验功能、自定义校验注解

文章目录

  • ①. JSR303校验概述、注解
  • ②. 项目中JSR303如何使用
  • ③. 统一异常处理@ExceptionHandler
  • ④. 处理错误状态码
  • ⑤. 分组校验功能(多场景校验)
  • ⑥. 自定义校验注解

①. JSR303校验概述、注解

  • ①. 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等注解

<!--jsr3参数校验器-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
  • ③. 常用的注解说明
注解解释
@NotNull属性不能为null,无法查检长度为0的字符串
@NotBlank检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格
@NotEmpty该字段不能为null或""
@Email被注释的元素必须是电子邮箱地址
@Length被注释的字符串的大小必须在指定的范围内
@Range被注释的元素必须在合适的范围内
@Pattern(value)被注释的元素必须符合指定的正则表达式
@Min(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值

②. 项目中JSR303如何使用

  • ①. 在类中使用JSR303注解
    在message写上我们自己的内容,这样通过测试就返回的数据就是我们自己填写的
@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;
}

在这里插入图片描述

  • ②. controller中加校验注解@Valid,开启校验
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand){
    brandService.save(brand);
    return R.ok();
}
  • ③. 第一次测试:http://localhost:88/api/product/brand/save
    在postman种发送上面的请求,可以看到返回的甚至不是R对象
{
    "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"
}
  • ④. 修改代码,进行第二次测试
  1. 给校验的Bean后,紧跟一个BindResult,就可以获取到校验的结果。拿到校验的结果,就可以自定义的封装
  2. 这种是针对于该请求设置了一个内容校验,如果针对于每个请求都单独进行配置,显然不是太合适,实际上可以统一的对于异常进行处理
@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();
}

在这里插入图片描述

③. 统一异常处理@ExceptionHandler

  • ①. 抽取一个异常处理类
  1. @ControllerAdvice标注在类上,通过“basePackages”能够说明处理哪些路径下的异常。
  2. @ExceptionHandler(value = 异常类型.class)标注在方法上4
  3. @RestControllerAdvice=@ControllerAdvice+@ResponseBody
  • ②. 代码展示 掌握
@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);
    }
}
  • ③. 测试 http://localhost:88/api/product/brand/save
    在这里插入图片描述
  • ④. 默认异常处理
//如果在异常类上这些都没有进行匹配,最后都会由这个异常进行处理
@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();
    }
}

  • ④. 测试: http://localhost:88/api/product/brand/save
    在这里插入图片描述

⑤. 分组校验功能(多场景校验)

  • ①. 前面解决了统一异常处理,但是现状有新的需求是对同一实体类参数也要区分场景
    如果新增和修改两个接口需要验证的字段不同,比如id字段,新增可以不传递,但是修改必须传递id,我们又不可能写两个vo来满足不同的校验规则。所以就需要用到分组校验来实现。

  • ②.分组校验步骤:

  1. 创建分组接口AddGroup.java和UpdateGroup.java
  2. 在VO的属性中标注@NotBlank等注解,并指定要使用的分组,如@NotNull(message = “用户姓名不能为空”,groups = {AddGroup.class,UpdateGroup.class})
    (这个意思是:在新增、修改的时候该属性不能为空)
  3. controller的方法上或者方法参数上写要处理的分组的接口信息,如@Validated(AddGroup.class)
/**
 * 品牌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()){
//		    Map<String,Object>map=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();
    }

⑥. 自定义校验注解

  • ①. 场景:要校验showStatus的0/1状态,可以用正则,但我们可以利用其他方式解决复杂场景。比如我们想要下面的场景,当前台输入0或者1的时候是合法的,当前台输入不是0或者1就要给出相对于的提示
/**
 * 显示状态[0-不显示;1-显示]
 */
//自定义注解
@ListValue(vals={0,1},groups = AddGroup.class)
private Integer showStatus;
  • ②. 导入依赖,自定义校验注解有三步:
  1. 编写一个自定义的校验注解
  2. 编写一个自定义的校验器ConstraintValidator
  3. 关联自定义的校验器和自定义的校验注解
  4. 注意{ListValueConstraintValidator.class(可以指定多个不同的校验器,适配不同类型的校验)}
<!--校验-->
<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>2.0.1.Final</version>
</dependency>
  • ③. 自定义校验注解,必须有3个属性
    (@Constraint(validatedBy = {ListValueConstraintValidator.class}) ListValueConstraintValidator会在第二步进行定义)
  1. message()错误信息
  2. groups()分组校验
  3. payload()自定义负载信息
  4. 因为上面的message值对应的最终字符串需要去ValidationMessages.properties中获得,所以我们在common中新建文件ValidationMessages.properties(如果出现了乱码删除文件重新定义)
@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]
  • ④. 自定义校验器ConstraintValidator
  1. 上面只是定义了异常消息,但是怎么验证是否异常还没说,下面的ConstraintValidator就是说的比如我们要限定某个属性值必须在一个给定的集合里,那么就通过重写initialize()方法,指定可以有哪些元素而controller接收到的数据用isValid(验证)
  2. 具体的校验类需要实现ConstraintValidator接口,第一个泛型参数是所对应的校验注解类型,第二个是校验对象类型。在初始化方法initialize中,我们可以先做一些别的初始化工作,例如这里我们获取到注解上的value并保存下来,然后生成set对象。
  3. 真正的验证逻辑由isValid完成,如果传入形参的属性值在这个set里就返回true,否则返回false
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;
  • ⑦. 使用postman进行测试,当我们将showStatus字段输入数字3,显示乱码。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

相关文章:

  • 一天时间迅速准备前端面试|JS基础—原型和原型链【三座大山之一,必考】
  • Spring Security详细讲解(JWT+SpringSecurity登入案例)
  • 【Network】网络基础@应用层 —— 协议 | http | https
  • UGUI学习笔记(九)自制3D轮播图
  • R统计-单因素ANOVA/Kruskal-Wallis置换检验
  • 动态开点线段树(C++实现)
  • pytorch保存和加载模型权重以及CUDA在pytorch中的使用
  • UDF提权(mysql)
  • linux内核漏洞(CVE-2022-0847)
  • kubekey 离线部署 kubesphere v3.3.0
  • Git史上最详细教程(详细图解)
  • Python科学计算库练习题
  • 高性能MySQL实战第10讲:搭建稳固的MySQL运维体系
  • java毕业设计茶叶企业管理系统Mybatis+系统+数据库+调试部署
  • JAVA安装教程 (windows)
  • (ckeditor+ckfinder用法)Jquery,js获取ckeditor值
  • [nginx文档翻译系列] 控制nginx
  • 《剑指offer》分解让复杂问题更简单
  • 【附node操作实例】redis简明入门系列—字符串类型
  • 10个确保微服务与容器安全的最佳实践
  • 2018以太坊智能合约编程语言solidity的最佳IDEs
  • DataBase in Android
  • es6要点
  • gitlab-ci配置详解(一)
  • Laravel5.4 Queues队列学习
  • MySQL主从复制读写分离及奇怪的问题
  • STAR法则
  • Webpack 4 学习01(基础配置)
  • web标准化(下)
  • 爱情 北京女病人
  • 简单基于spring的redis配置(单机和集群模式)
  • 事件委托的小应用
  • 学习ES6 变量的解构赋值
  • 一个JAVA程序员成长之路分享
  • - 语言经验 - 《c++的高性能内存管理库tcmalloc和jemalloc》
  • 掌握面试——弹出框的实现(一道题中包含布局/js设计模式)
  • No resource identifier found for attribute,RxJava之zip操作符
  • JavaScript 新语法详解:Class 的私有属性与私有方法 ...
  • ​一些不规范的GTID使用场景
  • $.extend({},旧的,新的);合并对象,后面的覆盖前面的
  • (14)学习笔记:动手深度学习(Pytorch神经网络基础)
  • (5)STL算法之复制
  • (9)STL算法之逆转旋转
  • (二)PySpark3:SparkSQL编程
  • (二)丶RabbitMQ的六大核心
  • (附源码)springboot美食分享系统 毕业设计 612231
  • (附源码)ssm智慧社区管理系统 毕业设计 101635
  • (理论篇)httpmoudle和httphandler一览
  • (六)激光线扫描-三维重建
  • (十)T检验-第一部分
  • (转)创业家杂志:UCWEB天使第一步
  • (转)详解PHP处理密码的几种方式
  • .Net Core和.Net Standard直观理解
  • .NET Windows:删除文件夹后立即判断,有可能依然存在
  • /使用匿名内部类来复写Handler当中的handlerMessage()方法