阿里云国内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 方法
- 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 |