Java Bean Validation(也称为 JSR 380,为 Bean Validation 2.0 规范)提供了一套基本的注解,用于定义和验证 Java Bean 的属性。例如:
@NotNull:属性不能为空
@Size:字符串、集合或数组的大小有约束
@Min 和 @Max:数值的最小值和最大值
@Pattern:字符串必须匹配正则表达式
等等
这些注解是由 Bean Validation 规范定义的,可以通过例如 Hibernate Validator 的实现来使用。
0. Maven 依赖
<dependency><groupId>jakarta.validation</groupId><artifactId>jakarta.validation-api</artifactId></dependency><dependency><groupId>org.hibernate.validator</groupId><artifactId>hibernate-validator</artifactId></dependency>
1. 定义一个注解接口
@Constraint(validatedBy = RegexValidator.class)
@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface RegexValidation {String message() default "Invalid value";String pattern();Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};
解析
类注解
@Constraint(validatedBy = RegexValidator.class)
@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface RegexValidation {
@Constraint(validatedBy = RegexValidator.class):
标记这个注解作为一个验证注解。
validatedBy 属性指定了用于实际验证的类,这里是 RegexValidator.class。这意味着会有一个 RegexValidator 类来实现对这个注解的验证逻辑。
@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE }):
这个元注解指定了 @RegexValidation 可以应用的程序元素类型。
ElementType.FIELD:表示这个注解能用于字段上。
ElementType.ANNOTATION_TYPE:表示这个注解可以用在其他注解上(元注解)。
@Retention(RetentionPolicy.RUNTIME):
这个元注解指定了 @RegexValidation 的保留策略。
RetentionPolicy.RUNTIME:表示注解将在运行时保留,可以通过反射进行读取。
定义注解的属性
String message() default "Invalid value";
message():
定义验证失败时的默认错误消息。
default "Invalid value":提供了一个默认值 "Invalid value"。
String pattern();
pattern():
定义一个字符串类型的属性,用于存储正则表达式模式。
没有默认值,所以这是一个必须属性,使用注解时必须提供。
Class<?>[] groups() default {};
groups():
用于分组验证,这使得注解可以在不同的验证组中使用。
默认是一个空数组。
Class<? extends Payload>[] payload() default {};
payload():
提供了负载信息的一种方式,可以用于传递元数据给约束。
默认是一个空数组。
整个注解定义总结
@RegexValidation 注解是一个自定义的验证注解,允许你对字段进行正则表达式匹配验证。该注解需要一个 pattern 属性来指定要匹配的正则表达式,并且通过 message 属性自定义验证失败时的错误信息。
2. 定义一个验证器
public class RegexValidator implements ConstraintValidator<RegexValidation, String> {private Pattern pattern;@Overridepublic void initialize(RegexValidation constraintAnnotation) {this.pattern = Pattern.compile(constraintAnnotation.pattern());}@Overridepublic boolean isValid(String string, ConstraintValidatorContext context) {if (string == null || string.isBlank()) {return true;}Matcher matcher = pattern.matcher(string);StringBuilder invalidChars = new StringBuilder();while (matcher.find()) {invalidChars.append(matcher.group());}if (invalidChars.length() > 0) {context.disableDefaultConstraintViolation();context.buildConstraintViolationWithTemplate(String.format("Invalid characters found: \"%s\"", invalidChars)).addConstraintViolation();return false;}return true;}
}
解析:
RegexValidator 类的作用是基于自定义注解 @RegexValidation 对字符串进行正则表达式匹配验证。其核心逻辑是初始化时编译正则表达式,然后验证时检查输入字符串是否完全匹配该正则模式,若存在不匹配字符则记录并返回验证失败。此过程实现了灵活且详细的输入验证,非常适合用于需要复杂输入约束的场景。
3. 定义一个组合注解类
@Constraint(validatedBy = {})
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@NotBlank(message = "Name must not be blank")
@Length(min = 3, max = 256, message = "Name length must be between 3 and 256 characters")
@SafeHtml
@RegexValidation(pattern = "[\\x{0001f300}-\\x{0001f64f}]|[\\x{0001f680}-\\x{0001f6ff}<>#%]")
public @interface LongName {String message() default "Invalid name";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};}
解析
这个注解结合了多个验证器来对目标字段进行综合验证。逐行解析这个注解的定义,有助于理解它是如何工作的,以及它在实际使用中可以提供哪些功能。注解定义部分
@Constraint(validatedBy = {})
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {}):这一行表明这是一个验证注解。
validatedBy 属性指定了需要用哪个验证器类来实现这个注解的验证逻辑。在这里,它是一个空数组 {},表示不指定特定的验证器类。实际上,验证的逻辑由注解内部包含的其他注解来实现。
@Target({ ElementType.FIELD }):该元注解描述了 @LongName 可以被应用到程序元素的类型上。
ElementType.FIELD 表示这个注解只能用在字段上。
@Retention(RetentionPolicy.RUNTIME):该元注解描述了 @LongName 的生命周期。
RetentionPolicy.RUNTIME 表示这个注解将在运行时保留,因此可以通过反射机制读取注解信息。
验证注解部分
@NotBlank(message = "Name must not be blank")
@Length(min = 3, max = 256, message = "Name length must be between 3 and 256 characters")
@SafeHtml
@RegexValidation(pattern = "[\\x{0001f300}-\\x{0001f64f}]|[\\x{0001f680}-\\x{0001f6ff}<>#%]")
@NotBlank(message = "Name must not be blank"):表示被注解的字段不能为空白字符串。
如果验证失败,会显示消息 "Name must not be blank"。
@Length(min = 3, max = 256, message = "Name length must be between 3 and 256 characters"):验证字符串长度需要在 3 到 256 个字符之间。
如果长度不符合,会显示消息 "Name length must be between 3 and 256 characters"。
@SafeHtml:表示被注解的字段必须是安全的 HTML 内容。这个注解通常会用来防止跨站脚本(XSS)攻击。
@RegexValidation(pattern = "[\\x{0001f300}-\\x{0001f64f}]|[\\x{0001f680}-\\x{0001f6ff}<>#%]"):自定义一个正则表达式验证:只能包含特定的 Unicode 范围字符和某些标点符号。
应用的正则表达式模式为 "[\\x{0001f300}-\\x{0001f64f}]|[\\x{0001f680}-\\x{0001f6ff}<>#%]"。
组合注解定义的属性部分
public @interface LongName {String message() default "Invalid name";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};}
String message() default "Invalid name";:
提供默认的错误消息,尽管这个信息对组合注解本身没有太大意义,因为每个具体注解都有自己的错误消息。
Class<?>[] groups() default {};:
用于分组验证。
Class<? extends Payload>[] payload() default {};:
用于传递支付信息。
总结
@LongName 是一个组合注解,它结合了多个标准验证注解(例如 @NotBlank, @Length, @SafeHtml and @RegexValidation)的功能。这个组合注解对于需要进行多种综合验证的场景非常有用,使代码更加简洁和可读。同时,组合注解可以减少重复代码,提高开发效率。
4. 实体类字段中注解的使用
public class User {@LongNameprivate String name;// getters 和 setters
}
name 字段将会受到四个不同注解的联合验证:@NotBlank, @Length, @SafeHtml, 和 @RegexValidation。这简化了代码,将多重验证集中在一个地方声明。此外,组合注解提高了代码的可读性和维护性。
5. Spring 注解的优势
Spring 框架的优势
Spring 框架在Bean Validation的基础上进行了很多增强和扩展,为开发者提供了更多的便利和功能:
更强的依赖注入和配置管理:
Spring 框架提供了功能强大且易于使用的依赖注入容器,帮助你轻松管理对象的创建和依赖。
自动校验:
Spring MVC 和 Spring Data 可以自动与 Bean Validation 集成。例如,你可以在控制器方法中直接使用 @Valid 注解,使得请求参数在进入控制器方法之前就被验证。
@PostMapping(“/users”)
public ResponseEntity createUser(@Valid @RequestBody User user) {
// 如果 user 无效,Spring 会自动返回 400 错误代码
return ResponseEntity.ok(userService.save(user));
}
简化的配置:
Spring Boot 通过自动配置大大简化了 Bean Validation 的集成,可以快速上手,并且减少了繁琐的配置过程。
国际化 (i18n) 支持:
Spring 提供了强大的国际化支持,可以轻松地为不同的地区和语言配置验证消息。
高级功能和扩展:
通过自定义注解和验证器类,Spring 框架可以更灵活地满足复杂的业务需求。例如,可以将自定义验证逻辑注入到服务中来实现高级验证逻辑。
@Component
public class CustomValidator implements ConstraintValidator<CustomConstraint, String> {
@Override
public void initialize(CustomConstraint constraintAnnotation) {
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {return "specialValue".equals(value);
}
}
与其他Spring模块的深度集成:
Spring 框架与 Spring Security、Spring Data、Spring Cloud 等其他模块紧密集成,这意味着你的验证逻辑可以更加一致和集中管理。