在Spring Boot中,数据验证是一个常见且重要的任务。无论是从前端传递的请求数据,还是从其他服务接收的数据,正确地验证它们都是确保应用程序健壮性的关键。
Spring Boot通过集成Hibernate Validator和使用Java的Bean Validation API,为开发者提供了一套强大、灵活且易于使用的数据验证机制。
要在Spring Boot应用程序中使用数据验证,首先需要添加相关的依赖,在pom.xml文件中加入以下依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
Java Bean Validation提供了一系列注解,用于在JavaBean的字段上指定验证规则。以下是一些常见的验证注解。
@NotNull:确保字段的值不为null。
@Size(min=, max=):确保字段值的大小/长度在指定的范围内。
@Min(value=):确保字段的值大于或等于给定的最小值。
@Max(value=):确保字段的值小于或等于给定的最大值。
@NotBlank:确保某个字符串属性在验证时不为空,并且其去除首尾空白后的长度至少为1。
@Email:确保字段值是电子邮件地址。
@Pattern(regexp=):确保字段的值与给定的正则表达式匹配。
例如,在demo项目的根包(com.example.demo)下创建dto包,并创建名为UserDTO的类:
public class UserDTO { @NotNull private Long id; @Size(min = 5, max = 30) private String username; @Email private String email; // 省略构造函数、Getter和Setter方法 }
在这个例子中,UserDTO类包含了三个字段:id、username和email,它们分别使用了@NotNull、@Size和@Email注解来定义验证规则。
类似UserDTO这样的类被称为数据传输对象(data transfer object,DTO)。DTO是一种设计模式,用于在不同的系统层次或不同的系统之间传递数据,其核心思想是将内部数据从一个子系统或层次传输到另一个子系统或层次,同时避免直接传递域模型或实体对象。
虽然DTO和JavaBean都是POJO(plain old java objects,普通Java对象),但它们的目的、用途和特性可能有所不同。DTO主要用于数据传输,而JavaBean可以有多种用途,他们的主要区别如下。
(1)DTO通常用于传递数据,尤其是在不同的系统、应用程序或层次之间。例如,它们可以在控制器和服务层之间,或服务层和数据访问层之间,甚至是在不同的微服务之间传递数据。JavaBean更为通用,可以用于多种目的。它们可以代表数据库的实体、UI的模型、配置数据等。
(2)由于DTO经常用于从客户端接收数据或将数据发送到客户端,因此它们通常与验证注解一起使用(如@NotNull、@Size等),以确保数据的正确性。JavaBean不包含这些验证注解,除非它们也被用作数据传输对象。
(3)为了简化和优化数据传输,DTO通常是不可变的。这意味着一旦DTO被创建,它的状态就不能更改。而JavaBean根据需求,可能是可变的或不可变的。
(4)DTO生命周期通常较短,仅在数据传输期间存在。JavaBean可能有更长的生命周期,取决于它们在应用程序中的用途。
创建DTO相关类后,要在Controller中触发验证,通常在方法参数前使用@Valid注解。@Valid用于触发被注解对象的验证,当用于方法参数时(如Controller中的方法参数),Spring MVC会检查该对象的约束并验证它,例如:
@RestController public class UserController { @PostMapping("/users") public ResponseEntity<String> createUser(@Valid @RequestBody UserDTO user) { // 处理用户创建逻辑 return ResponseEntity.ok("用户创建成功"); } }
当验证失败时,通常会抛出MethodArgumentNotValidException异常。可以通过@ExceptionHandler在控制器内捕获此异常,并向前端提供明确的错误消息:
@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<List<String>> handleValidationExceptions (MethodArgumentNotValidException ex) { List<String> errors = ex.getBindingResult() .getAllErrors().stream() .map(ObjectError::getDefaultMessage) .collect(Collectors.toList()); return ResponseEntity.badRequest().body(errors); } }
这段代码定义了一个全局异常处理器,它会捕获并处理MethodArgumentNotValidException异常,@RestControllerAdvice实际上是@ControllerAdvice与@ResponseBody的组合,这意味着它不仅可以处理异常,而且可以直接将返回值作为响应体返回给客户端。
handleValidationExceptions是处理异常的方法,它接收一个MethodArgumentNotValidException参数,这个异常对象包含了关于验证失败的详细信息,此方法的主要逻辑如下。
ex.getBindingResult():从异常对象中获取验证结果。
.getAllErrors():获取所有的验证错误。
.stream():将错误列表转换为Java 8的Stream。
.map(ObjectError::getDefaultMessage):从每个ObjectError对象中提取默认的错误消息。
.collect(Collectors.toList()):将所有的错误消息收集到一个列表中。
最后,这个方法返回了一个ResponseEntity,它带有HTTP状态码400 Bad Request,以及上面收集的错误消息列表作为响应体。
@Valid还可以与任何对象一起使用,不只限于方法参数。例如,你可以在一个对象内的另一个对象上使用@Valid来级联验证。
假设有两个类,Address和User。User类有一个Address类型的属性:
public class Address { @NotBlank private String street; @NotBlank private String city; // 省略构造函数、Getter和Setter方法 } public class User { @NotBlank private String name; @Valid private Address address; // 省略构造函数、Getter和Setter方法 }
在User类中:
name属性使用@NotBlank注解,以确保名称字段不为空。
address属性前的@Valid注解启用了级联验证。这意味着当验证User对象时,除了验证User对象本身的约束,还会验证address属性的约束。
当验证User对象时(例如,在Spring MVC控制器方法中接收到一个User对象),Spring Boot验证框架将自动验证User对象的所有字段,还会验证其内部引用的对象(如Address引用的street)的约束。
级联验证非常适用于处理复杂对象模型,其中一个对象包含其他自定义对象作为其属性。通过在父对象的属性上使用@Valid注解,可以确保整个数据模型在验证时的完整性,从而避免了漏检某些嵌套对象的错误。