探索Spring Validation:优雅实现后端数据验证的艺术
在现代Web应用开发中,数据验证是一项至关重要的任务,确保应用程序接收到的用户输入符合预期规范,不仅能够提高系统的健壮性,也能有效防止潜在的安全漏洞。Spring Framework通过其内置的Spring Validation模块,为我们提供了强大的数据验证功能,本文将带你深入了解Spring Validation的实现原理及其最佳实践。
一、Spring Validation简介
Spring Validation是基于Java Bean Validation规范(JSR 303/349 for Java EE 6/7,JSR 380 for Java EE 8)实现的,主要依赖的是Hibernate Validator作为默认的实现引擎。它允许我们在Java Bean的属性上声明验证注解,从而在运行时对这些属性进行条件性验证。
在实际项目我们需要对客户端传递到服务端的参数进行校验,用于判定请求参数的合法性,假如请求参 数不合法则不可以再去执行后续的业务了。那如何校验呢?
一种方式是我们在控制层方法中每次都自己进行参数有效值的判断,不合法可以抛出异常,但是工作量 和代码复杂度会比较高;
第二种方式就是采用市场上主流的 Spring Validation 框架去实现校验,所以 Spring Validation 框架的主要作用是 检查参数的基本有效性。
二、集成与配置
1. 引入依赖
在Maven或Gradle构建项目中,我们需要引入Hibernate Validator的相关依赖,例如在Maven的pom.xml文件中添加:
Xml
1<dependency>
2 <groupId>org.springframework.boot</groupId>
3 <artifactId>spring-boot-starter-validation</artifactId>
4</dependency>
2.使用注解
Spring Validation提供了丰富的预定义注解,如@NotNull,@NotEmpty,@NotBlank,@Size,@Range等,可以直接应用于实体类的字段上:
@NotNull注解
作用:用于验证对象是否为 null
用法: @NotNull 注解用于对象类型上
示例
@NotNull(message = "用户名不能为null")
private String username;
@NotEmpty注解
作用:用于验证字符串是否为空,并且会检查是否为 null 值(为null值时报错)
用法:用于字符串类型上
示例
@NotEmpty(message = "用户名不能为空")
private String username;
@NotBlank注解
作用:不允许为空白,即不允许是“仅由空格、TAB等空白值组成的字符串”,也不允许为空字符串,也不允许为空值null
用法:用于字符串类型上
示例
@NotBlank(message = "用户名不能为空白串")
private String username;
@Size注解
作用:可以指定最小值和最大值限制字符串的长度串,也不允许为空值null
用法:用于字符串类型参数
示例
@Size(min = 6, max = 20, message = "用户名长度必须在6到20之间")
private String username;
@Range 注解
作用:用于验证数字类型字段的取值范围,通过配置min和max属性来限制数值类型参数的值区间 包括最小值和最大值
用法:用于数值类型参数
示例
@Range(min = 1, max = 10, message = "年龄必须在1-10岁之间")
private int age;
@Range(min = 0.1, max = 1.0, message = "成绩必须在0.1到1.0之间")
private double score;
三、验证流程与控制器处理
-
控制器层处理: 在Spring MVC的Controller中,我们可以使用
@Valid
注解来触发模型对象的验证,紧接着一个BindingResult
对象用于捕获验证结果:1@PostMapping("/users") 2public ResponseEntity<?> createUser(@Valid @RequestBody User user, BindingResult bindingResult) { 3 if (bindingResult.hasErrors()) { 4 // 处理验证错误 5 return handleValidationError(bindingResult); 6 } else { 7 // 验证通过,继续处理业务逻辑 8 userService.createUser(user); 9 return ResponseEntity.ok().build(); 10 } 11}
-
自定义注解与验证器: 对于更复杂或特定的验证需求,Spring Validation允许我们自定义注解并编写相应的验证器实现
javax.validation.ConstraintValidator
接口:1@Target({ElementType.FIELD}) 2@Retention(RetentionPolicy.RUNTIME) 3@Constraint(validatedBy = EncryptedIdValidator.class) 4public @interface EncryptedId { 5 String message() default "加密ID格式不正确"; 6 Class<?>[] groups() default {}; 7 Class<? extends Payload>[] payload() default {}; 8} 9 10public class EncryptedIdValidator implements ConstraintValidator<EncryptedId, String> { 11 // 实现自定义的验证逻辑 12 ... 13}
四、快速入门
1. 在处理请求的方法的参数列表中,在POJO类型的参数上添加 @Validated 注解,表示需要通过 Spring Validation框架检查此参数,例如UserController中注册功能:
@ApiOperation(value = "注册功能")
@PostMapping("reg")
public JsonResult reg(@RequestBody @Validated UserRegDTO userRegDTO){}
2. 在此POJO类中的属性上,添加对应的检查注解,以配置检查规则, 例如,添加 @NotNull 注解,就表示“不允许为 null ”的规则!
在 UserRegDTO 类
@Data
public class UserRegDTO {
@NotNull
@ApiModelProperty(value = "用户名", required = true, example = "赵丽颖")
private String username;
@ApiModelProperty(value = "密码", required = true, example = "123456")
private String password;
@ApiModelProperty(value = "昵称", required = true, example = "萤火虫")
private String nickname;
}
3. 重启工程,在Knife4j中测试,当提交请求时,如果没有提交username参数,服务器端将响应 400 错误。
同时在终端也会出现异常
五、异常处理与响应
1.处理异常
为了给前端提供友好的反馈,通常我们会统一处理验证失败时的异常,将其转换成HTTP状态码和错误信息:
第1步:全局异常处理器 GlobalExceptionHandler 中定义处理异常方法
@ExceptionHandler
public JsonResult handleBindException(MethodArgumentNotValidException ex){
return new JsonResult(3002, "请求参数错误");
}
第2步:重启工程,在Knife4j中测试
2.明确提示信息
当提交的 username 的值为 null 时,可以发现异常已被处理!
但是,处理结果并不合适,因为,客户端得到此结果后,仍无法明确出现了什么错误!
所有的检查注解都可以配置 message 参数,用于对错误进行描述。
第1步: @NotNull 注解中添加 message 参数
@NotNull(message = "必须提交用户名")
private String username;
第2步:自定义枚举状态码 StatusCode
VALIDATE_ERROR(3002, "参数校验失败")
第3步:异常方法中获取提示信息 message
在处理异常时,需要调用 MethodArgumentNotValidException 对象的
getFieldError().getDefaultMessage() 获取以上配置的描述文本
@ExceptionHandler
public JsonResult handleBindException(MethodArgumentNotValidException ex){
/*
ex.getFieldError().getDefaultMessage():获取 @NotNull(message="xxx") 中
message的消息
*/
String message = ex.getFieldError().getDefaultMessage();
return new JsonResult(StatusCode.VALIDATE_ERROR, message);
}
第3步:重启工程,在Knife4j中测试
六、非POJO类参数校验
在 Spring Validation 中,除了对 POJO(Plain Old Java Object)进行校验的功能外,还支持对非 POJO 进行校验,比如 String、Integer、Double 等类型的参数。
1.使用流程
- 在当前方法所在的类上添加 @Validated 注解
- 在参数上添加对应的检查注解
2.使用示例
对于微博详情页的 id 参数进行范围校验,范围只能在1-10之间
第1步:在类 WeiboController 中添加 @Validated 注解
@Validated
public class WeiboController {}
第2步:在控制器方法参数 id 上添加对应的检查注解
public JsonResult selectById(@Range(min = 1, max=10, message = "请提交合法的ID
值!") int id)
第3步:重启工程,在Knife4j或者浏览器中测试
七、总结
Spring Validation的使用极大地简化了Java应用程序中的数据验证过程,通过标准化的注解机制实现了业务逻辑和数据验证的分离,提高了代码的可读性和可维护性。同时,它也具有良好的扩展性,让我们能够轻松应对各类复杂的验证场景,确保了系统数据质量的同时提升了用户体验。
通过深入理解和灵活运用Spring Validation,开发者能够在后端建立起坚固的数据过滤防线,为构建安全可靠的应用程序打下坚实基础。而随着RESTful API设计的流行,Spring Validation更是成为了保障API契约得到严格遵循的重要工具之一。