一、参数校验处理逻辑
在Spring Boot中,实现参数校验主要依赖于Java Bean Validation API(JSR 380),以及Spring框架对该API的集成支持。以下是具体步骤:
- 引入依赖:首先确保项目中引入了Spring Validation相关的依赖。如果使用的是Spring Boot 2.3.x之前的版本,spring-boot-starter-web会自动包含hibernate-validator。对于2.3.x及以后的版本,可能需要手动添加spring-boot-starter-validation依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
- 定义校验约束:使用Hibernate Validator提供的注解来标记你想要校验的字段。例如,@NotNull表示该字段不能为null,@Size(min=3, max=50)表示字符串长度必须在3到50个字符之间,@Email用于校验邮箱格式等。
public class User {
@NotNull(message = "用户名不能为空")
private String username;
@Email(message = "邮箱格式不正确")
private String email;
// getter和setter方法
}
- 控制器层校验:在控制器方法的参数上使用@Valid注解,这样Spring MVC会自动触发参数校验。如果校验失败,会抛出MethodArgumentNotValidException异常。
@PostMapping("/user")
public ResponseEntity<?> createUser(@Valid @RequestBody User user) {
// 创建用户逻辑
return ResponseEntity.ok().build();
}
- 全局异常处理:通过创建一个@ControllerAdvice类来统一处理校验异常,返回标准化的响应。
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Map<String, Object> handleValidationExceptions(
MethodArgumentNotValidException ex) {
Map<String, Object> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return errors;
}
}
- 自定义校验注解:如果内置注解无法满足需求,可以创建自定义注解和相应的校验器。
二、常用校验注解
JSR 380 (Bean Validation API) 提供了一系列基本校验注解,用于在Java Bean属性上执行不同类型的校验。下面是一些常用的校验注解及其说明:
注解 | 描述说明 |
---|---|
@NotNull | 验证指定的元素不为null |
@Null | 验证指定的元素必须为null |
@NotBlank | 验证字符串字段非null,且长度大于0,不只包含空白字符 |
@NotEmpty | 验证集合、数组、Map或字符串不为null,且不为空 |
@Size(min=, max=) | 验证字符串、集合、数组或Map的大小在特定范围内 |
@Length(min=, max=) | 验证注解的元素值长度是否在min和max区间内 |
@Range(min=, max=) | 验证注解的元素值在min和max之间 |
@Min | 验证数字类型的字段值必须大于等于指定的最小值 |
@Max | 验证数字类型的字段值必须小于等于指定的最大值 |
@DecimalMin | 验证数字类型的字段值必须大于等于指定的最小值,支持小数点 |
@DecimalMax | 验证数字类型的字段值必须小于等于指定的最大值,支持小数点 |
@Pattern | 验证字符串是否符合正则表达式的模式 |
@Future | 检查日期类型的字段值是否在当前时间之后 |
@Past | 检查日期类型的字段值是否在当前时间之前 |
检查字符串字段是否是有效的电子邮件地址 | |
@AssertFalse | 验证注解的元素值是false |
@AssertTrue | 验证注解的元素值是true |
三、@Validated与@Valid区别
@Validated和@Valid都是用于Java Bean验证的注解,它们之间的主要区别在于功能和使用场景:
- 分组校验(Groups):
- @Validated支持校验组,允许开发者根据不同的场景定义一组校验注解,并在运行时指定要执行哪组校验。
- @Valid不支持校验组,它会触发所有的校验注解。
- 嵌套校验:
- @Validated不支持嵌套校验。
- @Valid支持嵌套校验。
- 使用范围:
- @Validated 一般用在
类
或者方法参数
,但不能用于字段
。 - @Valid 可以用在
方法参数
、字段
、构造器参数
上。
- @Validated 一般用在
- 集成方式:
- @Validated是Spring Framework提供的扩展注解,它在JSR 303/JSR 349/J憝 380的基础上增加了额外的特性,比如对校验组的支持,因此它的使用通常与Spring应用程序结合得更加紧密。
- @Valid是JSR 303/JSR 349/JSR 380规范的核心注解之一,大多数遵循该规范的框架都支持@Valid。
- 默认行为:
- @Validated允许通过设置ignoreEmpty属性来忽略那些值为空的非空约束注解。
- @Valid在校验时不会忽略非空的约束注解,即使它们的值是空的也会触发校验。
四、分组校验
在Spring Boot中进行参数分组校验,可以通过定义校验组接口和在注解中指定这些校验组来实现。下面是一个具体的例子:
- 首先,定义校验组接口:
public interface DefaultGroup {
}
public interface AdvancedGroup extends DefaultGroup {
}
- 然后,在参数实体类中使用这些校验组,通过groups来指定校验注解所属的组。
public class User {
@NotNull(groups = DefaultGroup.class)
private String username;
@NotNull(groups = AdvancedGroup.class)
private String password;
// Getter and Setter methods
}
- 最后,在控制器方法中使用@Validated注解,并指定校验组。
这里只能使用@Validated注解,使用@Valid注解会报错
。
@RestController
public class UserController {
@PostMapping("/user")
public String createUser(@Validated({c}) @RequestBody User user) {
// 业务逻辑处理
return "User created successfully";
}
@PostMapping("/advancedUser")
public String createAdvancedUser(@Validated({AdvancedGroup.class}) @RequestBody User user) {
// 业务逻辑处理
return "User created successfully";
}
}
在上面的例子中,createUser方法仅应用了DefaultGroup校验组,因此只有username字段会被校验。
而createAdvancedUser方法应用了AdvancedGroup校验组,而在定义AdvancedGroup组的时候继承了DefaultGroup组,
这就相当于同时应用了两个组,所以password和username字段都会被校验。
如果不同的组之间不存在继承关系,又想同时多个组一起校验,
那就使用@Validated({@Validated({AdvancedGroup.class}), AdvancedGroup.class})指定多个组就好。
五、嵌套校验
嵌套校验指的是接收参数的实体里面还嵌套了其他实体对象,需要连同其他的实体对象中的参数一起校验。以下是一个示例来说明如何对嵌套对象进行校验;
- 首先,假设我们有两个实体类Parent和Child用来接收参数,其中Parent类包含一个Child类型的属性:
public class Parent {
@Min(value = 1)
private Integer id;
private String name;
private Child child;
// 省略getter和setter方法
}
public class Child {
@Min(value = 1)
private Integer id;
@NotNull(message = "Child name cannot be null")
private String name;
// 省略getter和setter方法
}
这里Parent 类的id字段做最小值的校验,Child 类的id以及nanme字段都做了校验。
- 然后,在控制器方法中对Parent对象及其嵌套的Child对象进行参数校验。
其实这里如果只是像上面第一步那样设置接收参数的实体类的话,无论使用@Valid或@Validated注解都无法对Child 对象的属性进行校验,想要达到嵌套校验的效果还需要对第一步接收参数的实体稍作改动
。
@RestController
public class ParentController {
@PostMapping("/parent")
public String createParent(@Valid @RequestBody Parent parent) {
// 此处可以进行业务逻辑处理,如果校验失败,则会抛出异常
// ...
return "Parent created successfully";
}
}
- 对第一步定义的Parent 类进行改造,
往嵌套的Child对象上添加@Valid注解
,只有在需要嵌套校验的对象上添加@Valid注解,才会在接口进行参数校验时,连同子对象一起校验。由于字段属性上只能使用@Valid注解,无法使用@Validated注解,这就是为什么@Validated注解不支持嵌套校验,@Valid注解支持的原因
。
public class Parent {
@Min(value = 1)
private Integer id;
private String name;
@Valid // 实现嵌套校验的关键就在这个注解上
private Child child;
// 省略getter和setter方法
}
- 第三步修改完后,现在控制器方法中无论使用@Valid或@Validated注解,在调用这个接口的时候,Parent 以及Child 中有注解校验的字段都会进行校验。
六、自定义校验注解
在Spring Boot中创建自定义校验注解涉及以下几个步骤:
- 定义校验注解:使用@Constraint注解来定义你的自定义校验注解。你需要指定约束的类型、消息、错误代码和分组。
@Documented
@Constraint(validatedBy = MyConstraintValidator.class)
@Target({ ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface MyConstraint {
String message() default "自定义校验信息";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
- 实现校验器:创建一个实现ConstraintValidator接口的类。这个类包含initialize和isValid方法。isValid方法用于实际的校验逻辑。
public class MyConstraintValidator implements ConstraintValidator<MyConstraint, Object> {
@Override
public void initialize(MyConstraint constraintAnnotation) {
// 初始化代码,如果需要可以从注解中读取配置信息
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
// 这里编写具体的校验逻辑
if (value == null) {
return true; // 或者返回false,取决于你希望null值是否通过校验
}
// 例如,检查value是否为特定的字符串
return "expectedValue".equals(value.toString());
}
}
- 在实体或DTO中使用自定义注解:在需要校验的字段上应用你的自定义注解。
public class User {
@MyConstraint(message = "用户名不符合预期")
private String username;
// 其他字段和方法
}