首页 > 其他分享 >JSR 303全解析

JSR 303全解析

时间:2024-08-19 21:49:28浏览次数:7  
标签:return JSR 验证 303 校验 public AddGroup 解析 class

1. JSR 303是什么?

JSR 303(Java Specification Request 303),也称为Bean Validation,是Java中的一个规范,用于定义Java对象的校验规则。
1.1 JSR 303的主要功能

    注解驱动:通过注解直接在Java类上定义校验规则。
    内置约束:如@NotNull、@Size、@Min、@Max等。
    自定义约束:可以定义自定义的校验注解和逻辑。
    分组校验:支持对不同场景(如创建和更新)进行分组校验。

1.2 常用注解

    @NotNull:验证注解的元素值不是null。
    @Size:验证注解的元素的大小在指定范围内。
    @Min和@Max:验证注解的元素值在指定范围内。
    @Email:验证注解的元素是一个合法的电子邮件地址。

2. 使用步骤

JSR 303 是一个规范,所以需要具体的实现。Hibernat Bean Validator 就是Bean Validator的实现

2.1.引入库

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>2.0.1.Final</version>
        </dependency>

        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>8.0.1.Final</version>
        </dependency>

2.2 实体类编写校验规则

代码如下(示例):

@Data
public class BrandEntity implements Serializable {
  
    /**
     * 品牌ID,用于标识品牌。
     * 
     * 此字段通过注解进行了不同的验证逻辑配置,以适应不同的业务场景。
     * 在更新操作(UpdateGroup)中,要求此字段不为空,确保了更新操作有明确的目标品牌ID。
     * 在新增操作(AddGroup)中,要求此字段为空,因为新增品牌时不应该预先指定ID。
     * 这种通过注解进行验证的方式,提高了代码的灵活性和可维护性,避免了在业务逻辑中硬编码验证逻辑。
     */
    @NotNull(message = "修改必须指定品牌id",groups = {UpdateGroup.class})
    @Null(message = "新增不能指定id",groups = {AddGroup.class})
    private Long brandId;


    /**
     * 品牌名称字段。
     * 
     * 该字段是必填的,不允许为空字符串,这在添加和更新品牌信息时都必须遵守。
     * 使用@NotBlank注解来强制验证品牌名的非空性,如果为空,则会触发验证失败,
     * 返回相应的错误消息。
     */
    @NotBlank(message = "品牌名必须提交",groups = {AddGroup.class,UpdateGroup.class})
    private String name;


  
    /**
     * 品牌logo地址
     * 
     * 此字段在添加(AddGroup)和更新(UpdateGroup)时都必须是一个合法的URL地址,
     * 以确保公司徽标的链接是有效和可访问的。使用@URL注解进行验证,
     * 如果不符合URL格式,则会提示指定的错误信息。
     * 
     * 使用@NotBlank注解确保在添加时该字段不为空,为空则认为是无效的输入。
     * 这是因为在更新时,如果用户没有提供新的徽标URL,可以保留旧的URL,
     * 所以在更新组(UpdateGroup)中,@NotBlank约束被移除,允许为空。
     */
    @NotBlank(groups = {AddGroup.class})
    @URL(message = "logo必须是一个合法的url地址",groups={AddGroup.class,UpdateGroup.class})
    private String logo;

    
    /**
     * 展示状态字段,用于标记对象的展示状态。
     * 显示状态[0-不显示;1-显示]
     * 
     * 此字段受到两个验证组(AddGroup, UpdateStatusGroup)的约束。
     * 在这两个组中,该字段不能为空(@NotNull)且其值必须在预定义的列表中(@ListValue)。
     * 这样的设计确保了在添加对象和更新状态操作时,展示状态的值是有效且受控的。
     * 
     * @NotNull(groups = {AddGroup.class, UpdateStatusGroup.class})
     * 表明在AddGroup和UpdateStatusGroup验证组中,此字段不能为空。
     * @ListValue(vals={0,1},groups = {AddGroup.class, UpdateStatusGroup.class})
     * 表明在AddGroup和UpdateStatusGroup验证组中,此字段的值必须是0或1。
     */
    @NotNull(groups = {AddGroup.class, UpdateStatusGroup.class})
    @ListValue(vals={0,1},groups = {AddGroup.class, UpdateStatusGroup.class})
    private Integer showStatus;

    /**
     * 字段firstLetter用于存储实体的首字母。
     * 该字段的验证有以下规则:
     * 1. 在添加(AddGroup)时,不能为空,确保数据完整性。
     * 2. 在添加(AddGroup)和更新(UpdateGroup)时,必须是一个字母,确保数据的格式符合预期。
     * 这些验证规则通过注解的方式进行声明,以在运行时对数据进行校验。
     */
    @NotEmpty(groups={AddGroup.class})
    @Pattern(regexp="^[a-zA-Z]$",message = "检索首字母必须是一个字母",groups={AddGroup.class,UpdateGroup.class})
    private String firstLetter;

 
    /**
     * 排序字段,用于控制元素的显示顺序。
     * 
     * @NotNull 标注指示该字段在添加(AddGroup)时不能为空,确保了排序值的有效性。
     * @Min 标注指定了排序值必须大于等于0,适用于添加(AddGroup)和更新(UpdateGroup)操作,保证了排序的逻辑正确性。
     */
    @NotNull(groups={AddGroup.class})
    @Min(value = 0,message = "排序必须大于等于0",groups={AddGroup.class,UpdateGroup.class})
    private Integer sort;
    
}

2.3 自定义约束

通过上面的代码可以看出,如果要指定注解作用的范围,就要自己添加分组。

AddGroup

public interface AddGroup {
}

UpdateGroup

public interface UpdateGroup {
}

UpdateStatusGroup

public interface UpdateStatusGroup {
}

上述代码中为了对状态取值进行验证,我们采用了自定义验证器的方式

ListValue

@Documented
@Constraint(validatedBy = { ListValueConstraintValidator.class })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {

    String message() default "{com.xunqi.common.valid.ListValue.message}";

    Class<?>[] groups() default { };

    Class<? extends Payload>[] payload() default { };

    int[] vals() default { };
}

3. 业务层使用

/**
 * <p>
 * 商品品牌 前端控制器
 * </p>
 *
 * @author shiqi
 * @version 1.0.0
 * @createTime 2024-06-26
 */
@RestController
@RequestMapping("product/brand")
public class BrandController {
    /**
     * 保存品牌信息。
     * <p>
     * 该方法通过@RequestMapping注解映射了"/save"的HTTP请求,用于保存BrandEntity对象。
     * 使用@Validated注解对brandEntity参数进行验证,确保添加或修改品牌时数据的合法性。
     * BindingResult参数用于接收验证后的错误信息,可以进一步处理和反馈给前端。
     * <p>
     * 方法返回一个R对象,通常表示操作的成功或失败状态。
     *
     * @param brandEntity 品牌实体对象,包含待保存的品牌信息。
     * @param bindingResult 验证结果对象,用于存储brandEntity验证过程中产生的错误信息。
     * @return 返回一个表示操作结果的对象,通常是一个包含成功状态和相关消息的R对象。
     */
    @RequestMapping("/save")
    public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brandEntity, BindingResult bindingResult) {
        // 方法体中应包含保存品牌信息的具体逻辑,此处省略。
        return R.ok();
    }


}

4. 封装统一异常

4.1 业务异常状态枚举类

/**
 * <p>
 * 描述:业务异常枚举类
 * </p>
 *
 * @author shiqi
 * @version 1.0.0
 * @createTime 2024-06-26
 */
public enum BizCodeEnum {
    UNKNOWN_EXCEPTION(10000,"系统未知异常"),
    VALID_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,"商品库存不足"),
    LOGIN_ACCOUNT_PASSWORD_EXCEPTION(15003,"账号或密码错误");


    private int code;

    private String message;


    BizCodeEnum(int code, String message) {

        this.code = code;

        this.message = message;
    }


    public int getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }
}

4.2 封装统一返回结果

/**
 * <p>
 *  统一返回结果
 * </p>
 *
 * @author shiqi
 * @version 1.0.0
 * @createTime 2024-06-26
 */
 public class R extends HashMap<String, Object> {
    private static final long serialVersionUID = 1L;

    public R setData(Object data) {
        put("data",data);
        return this;
    }

    //利用fastjson进行反序列化
    public <T> T getData(TypeReference<T> typeReference) {
        Object data = get("data");    //默认是map
        String jsonString = JSON.toJSONString(data);
        T t = JSON.parseObject(jsonString, typeReference);
        return t;
    }

    //利用fastjson进行反序列化
    public <T> T getData(String key,TypeReference<T> typeReference) {
        Object data = get(key);    //默认是map
        String jsonString = JSON.toJSONString(data);
        T t = JSON.parseObject(jsonString, typeReference);
        return t;
    }

    public R() {
        put("code", 0);
        put("msg", "success");
    }

    public static R error() {
        return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, "未知异常,请联系管理员");
    }

    public static R error(String msg) {
        return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, msg);
    }

    public static R error(int code, String msg) {
        R r = new R();
        r.put("code", code);
        r.put("msg", msg);
        return r;
    }

    public static R ok(String msg) {
        R r = new R();
        r.put("msg", msg);
        return r;
    }

    public static R ok(Map<String, Object> map) {
        R r = new R();
        r.putAll(map);
        return r;
    }

    public static R ok() {
        return new R();
    }

    public R put(String key, Object value) {
        super.put(key, value);
        return this;
    }

    public Integer getCode() {

        return (Integer) this.get("code");
    }

}

自定义校验异常处理器

/**
 * <p>
 *  集中处理所有异常
 * </p>
 *
 * @author shiqi
 * @version 1.0.0
 * @createTime 2024-06-26
 */

@Slf4j
@RestControllerAdvice(basePackages = {"com.shiqi.jsr303demo"})
public class CustomExceptionControllerAdvice {

    /**
     * 处理方法参数不合法异常。
     * 当方法参数不满足验证条件时,Spring MVC会抛出MethodArgumentNotValidException异常。
     * 该异常处理器专门捕获此类异常,以统一的方式处理参数验证失败的情况。
     *
     * @param e MethodArgumentNotValidException异常实例,包含验证失败的详细信息。
     * @return 返回一个包含错误信息的响应对象。
     */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public R handleMethodArgumentNotValidException(MethodArgumentNotValidException e){
        // 获取验证结果对象,其中包含了具体的验证错误信息。
        BindingResult bindingResult = e.getBindingResult();

        // 初始化一个映射,用于存储字段名和对应的错误信息。
        HashMap<String,String> errMap=new HashMap<String,String>();

        // 检查是否有验证错误,如果有,则遍历所有字段错误,并将字段名和错误信息添加到errMap中。
        if (bindingResult.hasErrors()){
            bindingResult.getFieldErrors().forEach((item)->{
                errMap.put(item.getField(),item.getDefaultMessage());
            });
        }

        // 返回一个包含错误代码、错误消息和具体错误详情的响应对象。
        // 错误代码为400,表示客户端请求错误,错误消息为"参数校验不合法"。
        // errMap作为数据部分的一部分,包含了所有验证失败的字段和对应的错误信息。
        return R.error(400,"参数校验不合法").put("data",errMap);
    }

    /**
     * 处理所有异常的控制器异常处理器。
     * <p>
     * 该方法旨在捕获控制器层抛出的任何异常,无论是预期的业务异常还是未预期的运行时异常。
     * 它的目的是统一异常的处理方式,向客户端返回一个标准的响应体,而不是直接暴露服务器内部错误信息。
     *
     * @param throwable 抛出的异常对象,无论异常类型为何。
     * @return 返回一个表示错误响应的R对象。这个响应体可以帮助客户端识别请求处理过程中发生了什么错误。
     */
    @ExceptionHandler(value = Throwable.class)
    private R handleValidException(Throwable throwable) {
        // 记录异常信息到日志系统,以便后续的问题排查和分析。
        log.error("出现异常{},异常类型{}", throwable.getMessage(), throwable.getClass());
        // 返回一个通用的错误响应体,通知客户端请求处理过程中发生了错误。
        return R.error();
    }


}

5. 实际业务中的应用

    表单校验:确保用户输入的数据合法,如用户注册、登录、表单提交等。
    数据传输对象(DTO)校验:在进行数据传输时,确保传输的数据符合预期,如API请求和响应。
    领域对象校验:确保业务逻辑中的对象状态合法,如订单处理、支付处理等。

使用JSR 303可以有效减少手动校验代码,简化代码结构,提高代码可读性和维护性。在实际应用中,常结合Spring框架和Hibernate Validator一起使用。

                        
原文链接:https://blog.csdn.net/Hi_alan/article/details/139997617



标签:return,JSR,验证,303,校验,public,AddGroup,解析,class
From: https://www.cnblogs.com/shuijibaobao/p/18368182

相关文章

  • Android开发 - DisplayMetrics 类控制布局图形的缩放显示解析
    DisplayMetrics是什么DisplayMetrics类在Android中用于获取设备的显示属性(像素等)DisplayMetrics的主要属性metrics.density:屏幕密度,用于决定屏幕上每英寸的像素数DisplayMetricsmetrics=newDisplayMetrics();density=metrics.density;常见值:0.75(低密度)、1.0......
  • 深度解析软件开发中的视频“上墙”技术实现
    目录引言技术概述1.有线连接技术2.无线连接技术2.1Miracast2.2AirPlay2.3Chromecast3.软件解决方案3.1AirServer3.2ApowerMirror4.实现细节与注意事项5.开发工具与资源结论引言   随着科技的发展,视频上墙技术已成为现代生活中不可或缺的一部......
  • 【数据结构】详细介绍栈和队列,解析栈和队列每一处细节
    目录一.栈1. 栈的概念2. 栈的实现2.1栈的结构2.2初始化栈2.3入栈2.4出栈2.5获取栈顶元素2.6获取栈中有效个数2.7判断栈是否为空2.8销毁栈 二.队列1.队列的概念2.队列的实现 2.1队列的结构2.2队列初始化 2.3销毁队列 2.4入队列(队尾) ......
  • 《灵魂面甲》风灵月影使用教程解析及注意事项
    《灵魂面甲》(Soulmask)是CampfireStudio发行的一款开放世界生存制作游戏,本教程将指导您如何安全、有效地使用风灵月影修改器来增强您的游戏体验。请注意,使用修改器可能会影响游戏的平衡性和乐趣,也可能违反游戏服务条款,请在了解并接受这些风险后再进行操作。准备工作1.确......
  • Android开发 - HorizontalScrollView 类水平滚动超长视图使用解析
    基本概念HorizontalScrollView是一个容器,它允许包含的内容在水平方向上滚动。如果你有一块内容(比如一张宽大的图片或一个长长的水平列表),HorizontalScrollView能让用户通过左右滑动来查看超出屏幕的部分基本使用在布局文件(如activity_main.xml)中定义一个HorizontalScrol......
  • axios取消请求CancelToken的原理解析及用法示例
    文章目录一、axios的实例与请求流程二、CancelToken的作用三、CancelToken的实现原理四、取消请求的流程五、CancelToken用法六、利用拦截器取消请求1、axios请求拦截器2、axios响应拦截器3、利用路由导航守卫取消请求一、axios的实例与请求流程下图是axios实例......
  • Linux DNS域名解析服务
    目录一、系统的作用及类型1.DNS概念 2.域名体系结构3.DNS的域名结构二、DNS两种查询方式1.递归查询: 2.迭代查询: 三、DNS系统类型1.缓存域名服务器 2.主域名服务器 3.从域名服务器 四、DNS解析过程1.就近原则2.解析方式3.找就近的DNS服务器(外部)4.迭......
  • ZoneJs 源码解析
    ZoneJs源码解析ZoneJs是什么,它能干什么,它是怎么做到的?Zone是为js的执行提供了一个共享的数据上下文。为js函数执行维护了一个逻辑上的调用栈。同时提供了对于函数执行方法的拦截,在函数执行前后,添加一些通用的逻辑(例如日志,异常处理)。统一的任务模型,提供对于宏任务/微任务/......
  • 通过python脚本查询自己阿里云账号里的某个域名的A记录解析情况,以及测拨,用于排查未使
    安装sdkpipinstallaliyun-python-sdk-alidns代码全文importjsonimportrequestsfromaliyunsdkcore.clientimportAcsClientfromaliyunsdkalidns.request.v20150109importDescribeDomainRecordsRequest#替换为你的阿里云AccessKeyID和AccessKeySecret......
  • 2024年高教社杯数学建模国赛B题思路解析+代码+论文
    2024年高教社杯全国大学生数学建模竞赛(以下简称国赛)将于9月5日晚6时正式开始。下文包含:2024国赛思路解析​、国赛参赛时间及规则信息说明、好用的数模技巧及如何备战数学建模竞赛C君将会第一时间发布选题建议、所有题目的思路解析、相关代码、参考文献、参考论文等多项资料,帮......