Spring @Validated 注解用法:完整示例与解析
在 Spring 框架中,数据校验是保证应用程序健壮性的重要一环。@Validated
注解是 Spring 提供的用于声明式验证的强大工具,它基于 JSR-303(Bean Validation)规范,可以轻松地对方法参数、方法返回值以及 JavaBean 属性进行校验。本文将深入探讨 @Validated
注解的用法、原理、与 @Valid
的区别,并通过多个示例来展示其在实际开发中的应用。
1. JSR-303(Bean Validation)简介
在深入 @Validated
之前,我们需要先了解 JSR-303。JSR-303 是 Java 平台的一项规范,也称为 Bean Validation。它定义了一套标准的校验 API,用于对 Java Bean 中的字段、方法参数和返回值进行校验。Bean Validation 提供了一组内置的约束注解(如 @NotNull
, @Size
, @Email
等),同时也支持自定义约束注解。
Hibernate Validator 是 Bean Validation 的参考实现,Spring 框架对 Bean Validation 提供了很好的集成支持。
2. @Validated
注解详解
@Validated
是 Spring 框架提供的注解,它是对 JSR-303 Bean Validation 的增强。@Validated
主要用于以下几个方面:
- 方法参数校验: 可以对控制器(Controller)或服务(Service)类中的方法参数进行校验。
- 方法返回值校验: 可以对方法返回值进行校验 (需要配合其他设置).
- 嵌套校验: 可以触发嵌套对象的校验(需要与
@Valid
配合使用)。 - 分组校验: 可以指定在特定场景下应用哪些校验规则(通过
groups
属性)。
@Validated
注解的属性:
value
(或groups
): 这是一个Class<?>[]
类型的属性,用于指定校验分组。 校验分组允许你定义不同的校验规则集合,并在不同的场景下应用不同的集合。例如,你可以定义一个用于创建对象的校验分组和一个用于更新对象的校验分组。
3. @Validated
与 @Valid
的区别
@Valid
也是 JSR-303 规范中的注解,它和 @Validated
都可以用于触发校验,但它们之间存在一些关键的区别:
特性 | @Validated |
@Valid |
---|---|---|
来源 | Spring Framework | JSR-303 (Bean Validation) |
分组校验 | 支持 | 不支持 |
嵌套校验 | 需要与 @Valid 配合使用 |
可以单独使用 |
方法参数校验 | 支持 (Spring 增强) | 不支持(仅用于字段、方法和类型) |
方法返回值校验 | 支持 (Spring AOP 增强, 需要额外配置) | 不支持 |
使用场景 | 控制器层参数校验、服务层方法校验、分组校验 | JavaBean 属性校验、嵌套校验 |
总结:
@Validated
是 Spring 对 JSR-303 的扩展,提供了分组校验、方法参数/返回值校验等更强大的功能。@Valid
是 JSR-303 标准注解,主要用于 JavaBean 的属性校验和嵌套校验。- 在 Spring 环境中,通常建议在控制器层使用
@Validated
进行方法参数校验,而在 JavaBean 中使用@Valid
进行属性校验。 - 对于嵌套校验,
@Validated
可以标注在类级别或者方法参数上, 内部被校验对象需要使用@Valid
.
4. @Validated
使用示例
4.1. 准备工作
首先,我们需要在项目中添加必要的依赖。如果你使用 Maven,可以在 pom.xml
文件中添加以下依赖:
“`xml
“`
spring-boot-starter-validation
包含了 Hibernate Validator 的实现。spring-boot-starter-web
包含了 Spring MVC 的支持。
4.2. JavaBean 校验
我们先创建一个简单的 JavaBean,并使用 JSR-303 注解定义校验规则:
“`java
import javax.validation.constraints.*;
import javax.validation.Valid;
public class User {
public interface Create {} // 分组 - 创建
public interface Update {} // 分组 - 更新
private Long id;
@NotBlank(message = "用户名不能为空", groups = {Create.class, Update.class})
@Size(min = 2, max = 20, message = "用户名长度必须在2到20个字符之间", groups = {Create.class, Update.class})
private String username;
@NotBlank(message = "密码不能为空", groups = {Create.class})
@Size(min = 6, message = "密码长度至少为6个字符", groups = {Create.class})
private String password;
@Email(message = "邮箱格式不正确", groups = {Create.class, Update.class})
private String email;
@NotNull(message = "年龄不能为空",groups = {Create.class, Update.class})
@Min(value = 18, message = "年龄必须大于等于18岁", groups = {Create.class, Update.class})
private Integer age;
@Valid //用于嵌套校验
private Address address;
// Getters and setters...
public Long getId() { return id; }
public void setId(Long id) { this.id = id;}
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username;}
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age;}
public Address getAddress() { return address; }
public void setAddress(Address address) { this.address = address; }
}
public class Address{
@NotBlank(message = “街道不能为空”)
private String street;
@NotBlank(message = "城市不能为空")
private String city;
@NotBlank(message = "邮编不能为空")
private String zipCode;
//getters and setters
public String getStreet() { return street; }
public void setStreet(String street) { this.street = street; }
public String getCity() { return city; }
public void setCity(String city) { this.city = city;}
public String getZipCode() { return zipCode; }
public void setZipCode(String zipCode) { this.zipCode = zipCode;}
}
“`
在这个示例中,我们使用了以下 JSR-303 注解:
@NotBlank
: 字符串不能为空,且去除首尾空格后长度必须大于 0。@Size
: 字符串、集合或数组的长度必须在指定范围内。@Email
: 字符串必须是有效的邮箱地址。@NotNull
: 值不能为null.@Min
: 数值必须大于等于指定值。@Valid
: 用于Address属性, 触发嵌套校验.
我们还定义了两个校验分组:Create
和 Update
。password
字段只在创建用户时进行校验。
4.3. 控制器层方法参数校验
接下来,我们在控制器中使用 @Validated
注解来校验请求参数:
“`java
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.Min;
@RestController
@RequestMapping(“/users”)
@Validated // 可以放在类上,对所有方法生效
public class UserController {
@PostMapping("/create")
public String createUser(@RequestBody @Validated(User.Create.class) User user) {
// ... 保存用户信息
return "User created successfully!";
}
@PutMapping("/update/{id}")
public String updateUser(@PathVariable("id") @Min(1) Long id,
@RequestBody @Validated(User.Update.class) User user) {
// ... 更新用户信息
return "User updated successfully!";
}
@PostMapping("/createWithAddress")
public String createWithAddress(@RequestBody @Validated({User.Create.class}) User user) {
//address 也会被校验
return "User with address created!";
}
@GetMapping("/details/{id}")
public @ResponseBody @Valid User getUserDetails(@PathVariable("id") @Min(1) Long id){
//模拟查询
User user = new User();
user.setId(id);
user.setUsername("TestUser");
user.setAge(20); //如果返回的User对象的age或者username校验不通过, 则会报错.
return user;
}
}
“`
在这个示例中:
@Validated(User.Create.class)
: 在createUser
方法中,我们使用@Validated
注解并指定User.Create
分组,表示只应用User
类中标注了Create
分组的校验规则。@Validated(User.Update.class)
: 在updateUser
方法中,我们指定User.Update
分组,应用User
类中标注了Update
分组的校验规则。@PathVariable("id") @Min(1) Long id
: 对路径参数id
进行校验, 最小值为1.@Validated({User.Create.class})
oncreateWithAddress
: 因为使用了@Valid
在User
类的address
属性上, 所以Address
对象也会被校验.@ResponseBody @Valid User
: 在getUserDetails
方法, 开启对返回值User
的校验. 注意这里需要@Valid
(JSR-303标准注解) 且需要额外配置.@Validated
加在类上, 表示对该控制器中所有方法都进行校验.
4.4. 全局异常处理
为了优雅地处理校验失败的情况,我们可以创建一个全局异常处理器:
“`java
import org.springframework.http.HttpStatus;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.validation.ConstraintViolationException;
import java.util.HashMap;
import java.util.Map;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Map<String, String> handleValidationExceptions(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return errors;
}
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Map<String, String> handleConstraintViolationException(ConstraintViolationException ex) {
Map<String, String> errors = new HashMap<>();
ex.getConstraintViolations().forEach(violation -> {
String fieldPath = violation.getPropertyPath().toString();
String message = violation.getMessage();
errors.put(fieldPath, message);
});
return errors;
}
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public String handleAllException(Exception ex){
return "An unexpected error occurred: "+ ex.getMessage();
}
}
“`
@RestControllerAdvice
: 这是一个组合注解,等同于@ControllerAdvice
+@ResponseBody
。@ExceptionHandler(MethodArgumentNotValidException.class)
: 处理MethodArgumentNotValidException
异常,该异常通常在@RequestBody
参数校验失败时抛出。@ExceptionHandler(ConstraintViolationException.class)
: 处理ConstraintViolationException
异常,该异常通常在方法参数或返回值校验失败时抛出。@ResponseStatus(HttpStatus.BAD_REQUEST)
: 将响应状态码设置为 400(Bad Request)。
4.5 方法返回值校验配置 (MethodValidationPostProcessor)
要开启方法返回值校验, 需要配置MethodValidationPostProcessor
.
“`java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
@Configuration
public class ValidationConfig {
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
}
“`
4.6. 服务层方法校验
除了控制器层,我们也可以在服务层使用 @Validated
进行方法参数校验:
“`java
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.validation.Valid;
import javax.validation.constraints.Min;
@Service
@Validated
public class UserService {
public void createUser(@Valid User user) {
// ... 保存用户信息
}
public void updateUser( @Min(1) Long id, @Valid User user){
// ...
}
}
“`
这里和控制器层类似,@Validated
可以放在类上,对所有方法生效;也可以放在方法参数上,对特定参数进行校验。
5. 自定义校验注解
除了使用 JSR-303 内置的校验注解,我们还可以自定义校验注解来满足特定的业务需求。
示例:自定义一个校验手机号格式的注解
- 创建注解:
“`java
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Documented
@Constraint(validatedBy = PhoneNumberValidator.class)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface PhoneNumber {
String message() default “Invalid phone number”;
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
“`
- 创建校验器:
“`java
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.regex.Pattern;
public class PhoneNumberValidator implements ConstraintValidator
private static final String PHONE_NUMBER_PATTERN = "^1[3-9]\\d{9}$";
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true; // 允许为空
}
return Pattern.matches(PHONE_NUMBER_PATTERN, value);
}
}
“`
- 使用自定义注解:
“`java
public class User {
// … 其他字段
@PhoneNumber(message = "手机号格式不正确", groups = {User.Create.class, User.Update.class})
private String phone;
// ... Getters and setters
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
}
“`
6. 总结
@Validated
注解是 Spring 框架中进行数据校验的强大工具,它基于 JSR-303(Bean Validation)规范,提供了灵活、便捷的校验方式。通过本文的介绍,你应该已经掌握了 @Validated
注解的用法、原理、与 @Valid
的区别,以及如何在实际开发中应用它。记住以下几点:
@Validated
是 Spring 对 JSR-303 的扩展,支持分组校验和方法参数/返回值校验。@Valid
是 JSR-303 标准注解,用于 JavaBean 属性校验和嵌套校验。- 在控制器层使用
@Validated
进行方法参数校验,在 JavaBean 中使用@Valid
进行属性校验。 - 使用全局异常处理器优雅地处理校验失败的情况。
- 可以自定义校验注解来满足特定的业务需求。
希望本文能帮助你更好地理解和使用 Spring 的 @Validated
注解,写出更健壮、更可靠的代码。