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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!