shuijibaobao

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
统计
 
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



posted on   水吉z  阅读(111)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
 
点击右上角即可分享
微信分享提示