Spring数据校验Validation详解,JSR-330使用,

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6


文章目录

  • ​​一、概述​​
  • ​​1、Spring 校验使用场景​​
  • ​​2、JSR 303 Bean Validation​​
  • ​​二、Validator 接口设计​​
  • ​​三、Errors 接口设计​​
  • ​​1、Errors 文案来源​​
  • ​​2、代码实例​​
  • ​​四、自定义 Validator​​
  • ​​五、Spring与Validator的适配​​
  • ​​1、代码实例​​
  • ​​2、与国际化整合​​
  • ​​3、Jakarta Bean Validation(JSR-303)自定义Validator注解​​
  • ​​参考资料​​

一、概述

JSR 303中提出了Bean Validation,表示JavaBean的校验,Hibernate Validation是其具体实现,并对其进行了一些扩展,添加了一些实用的自定义校验注解。

Spring中集成了这些内容,你可以在Spring中以原生的手段来使用校验功能,当然Spring也对其进行了一点简单的扩展,以便其更适用于Java web的开发。

1、Spring 校验使用场景

Spring 常规校验(Validator)
Spring 数据绑定(DataBinder)
Spring Web 参数绑定(WebDataBinder)
Spring WebMVC/WebFlux 处理方法参数校验

2、JSR 303 Bean Validation

JSR 303中提供了诸多实用的校验注解,这里简单罗列:

//校验类型(message="错误提示")
//1、@Null 校验对象是否为null
//2、@NotNull 校验对象是否不为null
//3、@NotBlank 校验字符串去头尾空格后的长度是否大于0或是否为null
//4、@NotEmpty 校验字符串是否为null或是否为empty
//
//5、@AssertTrue 校验Boolean是否为true
//6、@AssertFalse 校验Boolean是否为false
//
//7、@UniqueElements 校验数组/集合的元素是否唯一
//8、@Size(min,max) 校验数组/集合/字符串长度是否在范围之内
//9、@Length(min,max) 校验数组/集合/字符串长度是否在范围之内
//10、@Range(min,max) 校验Integer/Short/Long是否在范围之内
//11、@Min(number) 校验Integer/Short/Long是否大于等于value
//12、@Max(number) 校验Integer/Short/Long是否小于等于value
//13、@Positive 校验Integer/Short/Long是否为正整数
//14、@PositiveOrZero 校验Integer/Short/Long是否为正整数或0
//15、@Negative 校验Integer/Short/Long是否为负整数
//16、@NogativeOrZero 校验Integer/Short/Long是否为负整数或0
//
//17、@DecimalMin(decimal) 校验Float/Double是否大于等于value
//18、@DecimalMax(decimal) 校验Float/Double是否小于等于value
//19、@Digits(integer,fraction) 校验数字是否符合整数位数精度和小数位数精度
//
//20、@Past(date) 校验Date/Calendar是否在当前时间之前
//21、@PastOrPresent(date) 校验Date/Calendar是否在当前时间之前或当前时间
//22、@Future(date) 校验Date/Calendar是否在当前时间之后
//23、@FutureOrPresent(date) 校验Date/Calendar是否在当前时间之后或当前时间
//
//24、@Email 校验字符串是否符合电子邮箱格式
//25、@URL(protocol,host,port) 校验字符串是否符合URL地址格式
//26、@CreditCardNumber 校验字符串是否符合信用卡号格式
//
//27、@Pattern(regexp) 校验字符串是否符合正则表达式的规则
//
//除此之外,我们还可以自定义一些数据校验规则

二、Validator 接口设计

接口职责:
Spring 内部校验器接口,通过编程的方式校验目标对象

核心方法:
supports(Class):校验目标类能否校验
validate(Object,Errors):校验目标对象,并将校验失败的内容输出至 Errors 对象

配套组件:
错误收集器:org.springframework.validation.Errors
Validator 工具类:org.springframework.validation.ValidationUtils

我们分析一下org.springframework.validation.Validator接口的源码:

public interface Validator {
// 是否支持
boolean supports(Class<?> clazz);

void validate(Object target, Errors errors);
}

从它doc文档中基本可以看出,基本只适用于某个model类,其doc中有一个实例,我们一起来看一下:

public class UserLoginValidator implements Validator {

private static final int MINIMUM_PASSWORD_LENGTH = 6;

public boolean supports(Class clazz) {
return UserLogin.class.isAssignableFrom(clazz);
}

public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "userName", "field.required");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "password", "field.required");
UserLogin login = (UserLogin) target;
if (login.getPassword() != null
&& login.getPassword().trim().length() < MINIMUM_PASSWORD_LENGTH) {
errors.rejectValue("password", "field.min.length",
new Object[]{Integer.valueOf(MINIMUM_PASSWORD_LENGTH)},
"The password must be at least [" + MINIMUM_PASSWORD_LENGTH + "] characters in length.");
}
}
}

其中用到了ValidationUtils这个类,这个类中封装了一些简单的方法可以供我们使用。

综合来看,Validator接口总体来说是一个过时、落后的接口,适用度并不强。

三、Errors 接口设计

接口职责:
数据绑定和校验错误收集接口,与 Java Bean 和其属性有强关联性

核心方法:
reject 方法(重载):收集错误文案
rejectValue 方法(重载):收集对象字段中的错误文案

配套组件:
Java Bean 错误描述:org.springframework.validation.ObjectError
Java Bean 属性错误描述:org.springframework.validation.FieldError

我们分析一下org.springframework.validation.Errors源码,似乎看起来挺难以理解的。

它与Spring的数据绑定有着强关联性,后续再慢慢分析。

1、Errors 文案来源

Errors 文案生成步骤
(1)选择 Errors 实现(如:org.springframework.validation.BeanPropertyBindingResult)
(2)调用 reject 或 rejectValue 方法
(3)获取 Errors 对象中 ObjectError 或 FieldError
(4)将 ObjectError 或 FieldError 中的 code 和 args,关联 MessageSource 实现(如:ResourceBundleMessageSource)

2、代码实例

import org.springframework.context.MessageSource;
import org.springframework.context.support.StaticMessageSource;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;

import java.util.List;
import java.util.Locale;

/**
* 错误文案示例
* @see Errors
*/
public class ErrorsMessageDemo {

public static void main(String[] args) {

// 0. 创建 User 对象
User user = new User();
user.setName("张三");
// 1. 选择 Errors - BeanPropertyBindingResult
Errors errors = new BeanPropertyBindingResult(user, "user");
// 2. 调用 reject 或 rejectValue
// reject 会生成 ObjectError
// rejectValue 会生成 FieldError
errors.reject("user.properties.not.null");
// user.name = user.getName()
errors.rejectValue("name", "name.required");

// 3. 获取 Errors 中 ObjectError 和 FieldError
// FieldError is ObjectError
List<ObjectError> globalErrors = errors.getGlobalErrors();
List<FieldError> fieldErrors = errors.getFieldErrors();
List<ObjectError> allErrors = errors.getAllErrors();

// 4. 通过 ObjectError 和 FieldError 中的 code 和 args 来关联 MessageSource 实现
MessageSource messageSource = createMessageSource();

for (ObjectError error : allErrors) {
String message = messageSource.getMessage(error.getCode(), error.getArguments(), Locale.getDefault());
System.out.println(message);
}
}

static MessageSource createMessageSource() {
StaticMessageSource messageSource = new StaticMessageSource();
messageSource.addMessage("user.properties.not.null", Locale.getDefault(), "User 所有属性不能为空");
messageSource.addMessage("id.required", Locale.getDefault(), "the id of User must not be null.");
messageSource.addMessage("name.required", Locale.getDefault(), "the name of User must not be null.");
return messageSource;
}
}

四、自定义 Validator

实现 org.springframework.validation.Validator 接口:

  • 实现 supports 方法
  • 实现 validate 方法
  • 通过 Errors 对象收集错误
  • ObjectError:对象(Bean)错误:
  • FieldError:对象(Bean)属性(Property)错误
  • 通过 ObjectError 和 FieldError 关联 MessageSource 实现获取最终文案
import org.springframework.context.MessageSource;
import org.springframework.context.support.StaticMessageSource;
import org.springframework.validation.*;
import java.util.Locale;

/**
* 自定义 Spring {@link Validator} 示例
* @see Validator
*/
public class ValidatorDemo {


public static void main(String[] args) {
// 1. 创建 Validator
Validator validator = new UserValidator();
// 2. 判断是否支持目标对象的类型
User user = new User();
System.out.println("user 对象是否被 UserValidator 支持检验:" + validator.supports(user.getClass()));
// 3. 创建 Errors 对象
Errors errors = new BeanPropertyBindingResult(user, "user");
validator.validate(user, errors);

// 4. 获取 MessageSource 对象
MessageSource messageSource = createMessageSource();

// 5. 输出所有的错误文案
for (ObjectError error : errors.getAllErrors()) {
String message = messageSource.getMessage(error.getCode(), error.getArguments(), Locale.getDefault());
System.out.println(message);
}
}

static class UserValidator implements Validator {

@Override
public boolean supports(Class<?> clazz) {
return User.class.isAssignableFrom(clazz); // class校验
}

@Override
public void validate(Object target, Errors errors) {
User user = (User) target;
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "id", "id.required");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "name.required");
String userName = user.getName();
// ... 其他校验
}
}

static MessageSource createMessageSource() {
StaticMessageSource messageSource = new StaticMessageSource();
messageSource.addMessage("user.properties.not.null", Locale.getDefault(), "User 所有属性不能为空");
messageSource.addMessage("id.required", Locale.getDefault(), "the id of User must not be null.");
messageSource.addMessage("name.required", Locale.getDefault(), "the name of User must not be null.");
return messageSource;
}
}

五、Spring与Validator的适配

实际上,Spring Validator 接口用起来是比较难用的,更何况还得创建Error对象,所以Spring和Validation 进行了强绑定。

Bean Validation 与 Validator 适配:
• 核心组件 - org.springframework.validation.beanvalidation.LocalValidatorFactoryBean
• 依赖 Bean Validation - JSR-303 或 JSR-349 provider
• Bean 方法参数校验 - org.springframework.validation.beanvalidation.MethodValidationPostProcessor

1、代码实例

使用JSR-303进行校验

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.validation.annotation.Validated;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;

import javax.validation.Valid;
import javax.validation.Validator;
import javax.validation.constraints.NotNull;

/**
* Spring Bean Validation 整合示例
*
* @see Validator
* @see LocalValidatorFactoryBean
*/

public class SpringBeanValidationAnnoDemo {

public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 注册当前类作为 Configuration Class
context.register(SpringBeanValidationAnnoDemo.class, UserProcessor.class);
// 启动 Spring 应用上下文
context.refresh();

UserProcessor userProcessor = context.getBean(UserProcessor.class);
userProcessor.process(new User());

// 关闭 Spring 应用上下文
context.close();
}

@Bean
public LocalValidatorFactoryBean validator(){
return new LocalValidatorFactoryBean();
}

@Bean
public MethodValidationPostProcessor methodValidationPostProcessor(Validator validator){
MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor();
methodValidationPostProcessor.setValidator(validator);
return methodValidationPostProcessor;
}



static class User {

@NotNull
private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
}
@Validated
class UserProcessor {

public void process(@Valid SpringBeanValidationAnnoDemo.User user) {
System.out.println(user);
}

}

如果校验失败,会提示国际化文案:

Exception in thread "main" javax.validation.ConstraintViolationException: process.user.name: 不能为null

2、与国际化整合

从LocalValidatorFactoryBean(Bean Validation 适配器)类中有一个setValidationMessageSource方法中注入MessageSource。

3、Jakarta Bean Validation(JSR-303)自定义Validator注解

这里提供一个简单的实例,关于Jakarta Bean Validation更多后续出一篇文章来介绍,可以先看官方文档:

​https://jakarta.ee/specifications/bean-validation/3.0/jakarta-bean-validation-spec-3.0.html​

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
* null 或 指定长度范围内
*/
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {SizeOrNullValidator.class})
public @interface SizeOrNull {
String message() default "{javax.validation.constraints.Size.message}";

Class<?>[] groups() default { };

Class<? extends Payload>[] payload() default { };

/**
* @return size the element must be higher or equal to
*/
int min() default 0;

/**
* @return size the element must be lower or equal to
*/
int max() default Integer.MAX_VALUE;

boolean notBlank() default false;
}



import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

/**
* null 或 指定长度范围内
*/
public class SizeOrNullValidator implements ConstraintValidator<SizeOrNull, Object> {

private SizeOrNull annotation;

@Override
public void initialize(SizeOrNull constraintAnnotation) {
this.annotation = constraintAnnotation;
}

@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}

if (value instanceof CharSequence) {
CharSequence cs = (CharSequence) value;
if (annotation.notBlank() && StringUtils.isBlank(cs)) {
return false;
}
int length = StringUtils.length(cs);
return length >= annotation.min() && length <= annotation.max();
} else {
int size = CollectionUtils.size(value);
return size >= annotation.min() && size <= annotation.max();
}
}
}
@CaptchaReceiver(groups = {CAPTCHA.class}) // 检查用于接收验证码的手机或邮箱
public class AuthenticationPacket {
// 省略属性
}


import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
* 校验单个Bean
*/
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {CaptchaReceiverValidator.class})
public @interface CaptchaReceiver {

String message() default "参数错误";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default { };
}




import org.apache.commons.lang3.StringUtils;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class CaptchaReceiverValidator implements ConstraintValidator<CaptchaReceiver, AuthenticationPacket> {

// private CaptchaReceiver annotation;

@Override
public void initialize(CaptchaReceiver constraintAnnotation) {
// this.annotation = constraintAnnotation;
}

@Override
public boolean isValid(AuthenticationPacket packet, ConstraintValidatorContext context) {
if (packet.getMode() == AuthenticationMode.MobileCaptcha && StringUtils.isBlank(packet.getMobile())) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("请填写手机号码。").addConstraintViolation();
return false;
}
if (packet.getMode() == AuthenticationMode.EmailCaptcha && StringUtils.isBlank(packet.getEmail())) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("请填写邮箱地址。").addConstraintViolation();
return false;
}
return true;
}
}

参考资料

​​JSR-303校验注解汇总​​ 极客时间-《小马哥讲 Spring 核心编程思想》


阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: Spring

“Spring数据校验Validation详解,JSR-330使用,” 的相关文章