Spring Validation
- Spring Validation
- 核心概念
- 核心组件
- 常用注解
- 使用示例
- 高级特性
- 工作原理
- 深入细节
- 实践中的Spring Validation
- 结论
Spring Validation
在现代Web应用开发中,确保用户输入数据的正确性和合法性是至关重要的。Spring Validation作为Spring框架中的一个重要组成部分,提供了一套强大而灵活的机制来处理数据验证。本文将深入探讨Spring Validation的各个方面,包括其工作原理、核心组件、常用注解、高级特性以及最佳实践。
spring validation允许通过注解的方式来定义对象校验规则,把校验和业务逻辑分离开,让代码编写更加方便。
Spring Validation其实就是对Hibernate Validator进一步的封装,方便在Spring中使用。在Spring中有多种校验的方式
第一种是通过实现org.springframework.validation.Validator接口,然后在代码中调用这个类
第二种是按照Bean Validation方式来进行校验,即通过注解的方式。
第三种是基于方法实现校验
除此之外,还可以实现自定义校验
核心概念
Spring Validation是Spring框架提供的一个特性,用于在应用程序中实现数据验证。这一机制通常用于校验用户输入数据的合法性,确保数据在被进一步处理之前符合特定的规则或标准。Spring Validation提供了一种便捷的方式来进行声明式的数据验证,通过注解和配置,可以轻松地应用在Spring管理的beans上。
核心组件
- Validator接口:这是Spring Validation的核心接口,定义了
validate(Object, Errors)
方法用于执行验证逻辑。 - Errors接口:用于记录验证过程中发现的所有错误。
- BindingResult:是
Errors
接口的一个扩展,通常用于表单验证中,包含了验证结果。
常用注解
Spring还支持JSR-303和JSR-349(Bean Validation规范),使得在实体或DTO类上通过注解声明验证规则变得非常简单。一些常用的注解包括:
@NotNull
:确保字段值非空@Min
和@Max
:验证数字值的范围@Size
:验证字符串、集合、数组的大小@Email
:验证字符串是否为合法邮箱@Pattern
:验证字符串是否匹配正则表达式@NotEmpty
只作用于字符串类型,字符串不为空,并且长度不为0@NotBlank
只作用于字符串类型,字符串不为空,并且trim()后不为空串@DecimalMax(value)
限制必须为一个不大于指定值的数字@DecimalMin(value)
限制必须为一个不小于指定值的数字@Email
验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式
使用示例
spring-web模块使用了hibernate-validation,并且databind模块也提供了相应的数据绑定功能。
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
</dependencies>
或者
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId>
</dependency>
我们只需要引入spring-boot-starter-web依赖即可,如果查看其子依赖,可以发现如下的依赖:
<dependency><groupId>org.hibernate</groupId><artifactId>hibernate-validator</artifactId>
</dependency>
<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId>
</dependency>
但是为了更加细度的理解,引入如下的依赖:
<dependencies><dependency><groupId>org.hibernate.validator</groupId><artifactId>hibernate-validator</artifactId><version>7.0.5.Final</version></dependency><dependency><groupId>org.glassfish</groupId><artifactId>jakarta.el</artifactId><version>4.0.1</version></dependency>
</dependencies>
假设有一个简单的用户注册的场景,需要验证用户输入的数据:
import javax.validation.constraints.Email;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;public class UserRegistrationDto {@NotEmpty(message = "Name cannot be empty")private String name;@Email(message = "Email should be valid")private String email;@Size(min = 8, message = "Password should have at least 8 characters")private String password;// Getters and Setters
}
在Controller中,你可以这样使用Spring Validation:
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;@Validated
@RestController
public class UserRegistrationController {@PostMapping("/register")public String registerUser(@Valid @RequestBody UserRegistrationDto userDto, BindingResult result) {if (result.hasErrors()) {// 处理验证失败的情况return "Validation errors";}// 保存用户或其他业务逻辑return "User registered successfully";}
}
在上面的例子中,@Valid
注解触发了UserRegistrationDto
上的验证逻辑,如果数据不满足条件,BindingResult
将会包含相应的错误信息,可以根据这些信息给出响应。
而如果请求参数并不是实体对象,仅仅是 String 类型的参数,那么则需要再该参数前加上校验的注解,在 Controller 层之上添加 @Validated
启动校验逻辑。
Spring Validation是处理复杂数据验证逻辑时的一个强大工具,通过简单的配置和注解,可以极大地减少手动验证数据的工作量。
高级特性
Spring Validation不仅仅局限于基本的数据类型验证,它还提供了一些高级特性来处理更复杂的验证场景。
当然,我们可以通过更详细地探讨Spring Validation的工作原理和使用细节来加深理解。
工作原理
Spring Validation的工作流程大致如下:
- 客户端请求到达Controller:客户端发送的请求首先到达Spring MVC的Controller层。
- 数据绑定:Spring MVC通过HTTP请求参数来填充目标对象的属性,这一过程称为数据绑定(Data Binding)。
- 触发验证:如果Controller方法参数使用了
@Valid
或@Validated
注解,Spring会自动触发验证过程。 - 执行验证规则:Spring通过注册的Validator实现或者JSR-303/JSR-349 Bean Validation的注解来检查对象的属性是否满足条件。
- 处理验证结果:验证过程中发现的错误被记录在
BindingResult
或Errors
对象中。开发者可以查询这个对象,了解哪些验证规则没有通过,并据此给出相应的响应。
深入细节
分组验证:在复杂的场景中,你可能希望根据不同的场景执行不同的验证规则。Spring Validation支持通过分组来实现这一需求。你可以定义多个接口作为分组标识,然后在验证注解中指定组名,最后在触发验证时通过@Validated
注解指定需要验证的组。
自定义验证器:除了使用内置的注解外,你还可以通过实现Validator
接口创建自定义的验证逻辑。自定义验证器让你能够处理那些标准注解无法覆盖的复杂场景。
public class CustomValidator implements Validator {@Overridepublic boolean supports(Class<?> clazz) {return SomeObject.class.isAssignableFrom(clazz);}@Overridepublic void validate(Object target, Errors errors) {SomeObject obj = (SomeObject) target;// 实现你的验证逻辑if (/* 验证逻辑 */) {errors.rejectValue("fieldName", "error.code", "Default message");}}
}
上面定义的类,其实就是实现接口中对应的方法,supports方法用来表示此校验用在哪个类型上,validate是设置校验逻辑的地点,其中ValidationUtils,是Spring封装的校验工具类,帮助快速实现校验。
测试:
public class TestMethod1 {public static void main(String[] args) {//创建UserRegistrationDto对象UserRegistrationDto user = new UserRegistrationDto ();user.setName("lucy");// 创建UserRegistrationDto 对应的DataBinderDataBinder binder = new DataBinder(user);// 设置校验binder.setValidator(new CustomValidator());// 由于UserRegistrationDto对象中的属性为空,所以校验不通过binder.validate();//输出结果BindingResult results = binder.getBindingResult();System.out.println(results.getAllErrors());}}
细解Bean Validation注解实现方式
使用Bean Validation校验方式,就是如何将Bean Validation需要使用的javax.validation.ValidatorFactory
和javax.validation.Validator
注入到容器中。spring 默认有一个实现类 LocalValidatorFactoryBean,它实现了上面Bean Validation中的接口,并且也实现了org.springframework.validation.Validator接口。
1.创建配置类,配置LocalValidatorFactoryBean
@Configurationpublic class ValidationConfig {@Beanpublic LocalValidatorFactoryBean validator() {return new LocalValidatorFactoryBean();}}
2.创建实体类,使用注解定义校验规则
public class User {@NotNullprivate String name;@Min(0)@Max(120)private int age;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}}
3.使用两种不同的校验器实现
- 使用jakarta.validation.Validator校验
@Service public class MyService1 {@Autowiredprivate Validator validator;public boolean validator(User user){Set<ConstraintViolation<User>> sets = validator.validate(user);return sets.isEmpty();}}
- 使用org.springframework.validation.Validator校验
@Service public class MyService2 {@Autowiredprivate Validator validator;public boolean validaPersonByValidator(User user) {BindException bindException = new BindException(user, user.getName());validator.validate(user, bindException);return bindException.hasErrors();} }
4.测试
public class TestMethod2 {@Testpublic void testMyService1() {ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class);MyService1 myService = context.getBean(MyService1.class);User user = new User();user.setAge(-1);boolean validator = myService.validator(user);System.out.println(validator);}@Testpublic void testMyService2() {ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class);MyService2 myService = context.getBean(MyService2.class);User user = new User();user.setName("lucy");user.setAge(130);user.setAge(-1);boolean validator = myService.validaPersonByValidator(user);System.out.println(validator);}
}
基于方法实现校验
1.创建配置类,配置MethodValidationPostProcessor
@Configuration
@ComponentScan("com.atguigu.spring6.validation.method3")
public class ValidationConfig {@Beanpublic MethodValidationPostProcessor validationPostProcessor() {return new MethodValidationPostProcessor();}
}
2.创建实体类,使用注解设置校验规则
public class User {@NotNullprivate String name;@Min(0)@Max(120)private int age;@Pattern(regexp = "^1(3|4|5|7|8)\\d{9}$",message = "手机号码格式错误")@NotBlank(message = "手机号码不能为空")private String phone;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String getPhone() {return phone;}public void setPhone(String phone) {this.phone = phone;}
}
3.定义Service类,通过注解操作对象
@Service
@Validated
public class MyService {public String testParams(@NotNull @Valid User user) {return user.toString();}}
4.测试
public class TestMethod3 {@Testpublic void testMyService1() {ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class);MyService myService = context.getBean(MyService.class);User user = new User();user.setAge(-1);myService.testParams(user);}
}
实现自定义注解校验
1.自定义校验注解
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {CannotBlankValidator.class})
public @interface CannotBlank {//默认错误消息String message() default "不能包含空格";//分组Class<?>[] groups() default {};//负载Class<? extends Payload>[] payload() default {};//指定多个时使用@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})@Retention(RetentionPolicy.RUNTIME)@Documented@interface List {CannotBlank[] value();}
}
2.编写真正的校验类
public class CannotBlankValidator implements ConstraintValidator<CannotBlank, String> {@Overridepublic void initialize(CannotBlank constraintAnnotation) {}@Overridepublic boolean isValid(String value, ConstraintValidatorContext context) {//null时不进行校验if (value != null && value.contains(" ")) {//获取默认提示信息String defaultConstraintMessageTemplate = context.getDefaultConstraintMessageTemplate();System.out.println("default message :" + defaultConstraintMessageTemplate);//禁用默认提示信息context.disableDefaultConstraintViolation();//设置提示语context.buildConstraintViolationWithTemplate("can not contains blank").addConstraintViolation();return false;}return true;}
}
全局异常处理:在Spring MVC中,你可以使用@ControllerAdvice
注解创建一个全局异常处理器。这个处理器可以捕获MethodArgumentNotValidException
异常,该异常在对象验证失败时抛出。这样,你就可以在一个地方统一处理所有的验证失败情况,而不是在每个Controller中单独处理。
@ControllerAdvicepublic class GlobalExceptionHandler {@ExceptionHandler(MethodArgumentNotValidException.class)public ResponseEntity<Object> handleValidationExceptions(MethodArgumentNotValidException ex) {BindingResult result = ex.getBindingResult();List<String> errorMessages = result.getAllErrors().stream().map(ObjectError::getDefaultMessage).collect(Collectors.toList());// 返回一个合适的响应体return new ResponseEntity<>(errorMessages, HttpStatus.BAD_REQUEST);}}
通过深入了解Spring Validation的工作原理和使用细节,你可以更灵活和有效地利用这个功能来确保数据的正确性和合法性。这不仅可以提高应用程序的健壯性,还可以通过减少不必要的数据库操作等来提升性能。
实践中的Spring Validation
为了更有效地使用Spring Validation,应该遵循一些最佳实践:
-
合理使用验证分组:分组可以帮助你在不同的场景下复用相同的模型,避免了为每个场景创建单独的验证模型。
-
全局异常处理:利用
@ControllerAdvice
来统一处理验证异常,可以减少重复的错误处理代码,使得异常处理逻辑更加集中和一致。 -
自定义验证消息:通过在验证注解中提供消息模板,可以使得错误消息更加用户友好和容易理解。
-
综合利用内置和自定义验证器:内置的验证注解处理大部分常见的验证需求,对于更特殊的验证逻辑,应当考虑实现自定义的验证器。
-
持续关注新版本的特性:Spring不断地更新和改进,包括其验证框架。关注并利用这些新特性可以让你的应用更加健壮和高效。
结论
Spring Validation是一个强大的框架,它提供了灵活的验证机制,可以帮助开发者确保应用中的数据的正确性和合法性。通过理解和合理使用它的各种特性和最佳实践,可以极大地提高应用的质量和用户体验。随着对Spring Validation更深入的理解和实践,你将能够更有效地利用这个框架来构建健壮且可维护的Web应用。