上面有关NotNull,NotEmpty,NotBlank,可以参考StringUtils的类似API。
另外,就是上述的@Pattern注解,可以说是最为灵活的注解。许多自定义注解,其实都可以通过@Pattern注解实现。
我认为Validation框架的中级应用有三个:
级联验证:通过@Valid注解实现级联校验。举个例子,我的ScriptionBO中有一个List属性。我希望Validation框架在校验ScriptionBO的时候,不仅仅校验ScriptionBO的属性,还要验证其中List涉及的User们。那么在List上添加@Valid注解,就可以实现了。
分组校验:通过分组Interface与校验注解的group参数,就可以实现分组校验。举个例子,同样是User实体类,既需要满足登录验证(有userId这样的属性),也需要满足注册验证(不需要userId这样的属性)。那么可以在User实体类中,建立用于登录场景的interface LoginGroup {}接口,与用于注册场景的interface RegisterGroup {}。在userId属性上,增加非空校验的@NotNull(groups = LoginGroup.class),就可以实现了。
分组序列:通过分组校验,再加上@GroupSequence({xxxGroup.class,xxxGroup.class}),就可以实现分组序列了。举个例子,登录场景下,User连userId的非空校验都没有通过,那么就更不需要校验手机号码,邮箱等。
3.高级应用:自定义校验注解首先强调一点,正常情况下,常用约束注解配合Validation框架的中级应用,足以应付大多数情况。尤其是@Pattern注解采用了灵活的正则表达式,可以解决大部分复杂问题。
举个例子,正常的Email地址校验,可以通过@Email注解进行校验,更可以通过@Pattern实现更为精准的校验。至于自定义校验注解,则可以实现根据配置,动态验证Email地址的功能。
自定义校验注解,其实就类似于配合自定义注解的切面编程,只不过利用了Validation框架的一些基础方法。
自定义校验注解分为以下三步:
约束注解的定义。
约束验证规则(即自定义约束校验器)
关联约束注解与约束规则
为了更直观的感受,这里给出一个简单的demo。
另外,这里的依赖,需要单独引入,能只依靠springboot自带的validation依赖。
约束注解定义 package tech.jarry.learning.demo.common.anno; import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.*; /** * @author jarry * @description 自定义动态属性校验约束注解 */ @Documented @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) // 关联约束注解与约束规则 @Constraint(validatedBy = DynamicPropertyVerificationValidator.class) public @interface DynamicPropertyVerification { // 约束注解校验失败时的输出信息 String message() default "property verification fail"; // 约束注解在验证时所属的组别 Class<?>[] groups() default {}; // 约束注解的负载(可用来保存一些数据) Class<? extends Payload>[] payload() default {}; } 约束验证规则 package tech.jarry.learning.demo.common.anno; import com.alibaba.fastjson.JSON; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import java.util.ArrayList; import java.util.List; /** * @author jarry * @description 动态属性的自定义约束校验器 */ public class DynamicPropertyVerificationValidator implements ConstraintValidator<DynamicPropertyVerification, String> { // 为了便于进行测试,这里先放入一些本地数据 private static final List<String> REX_LIST = new ArrayList<String>() { { add("auth_1"); add("auth_2"); add("auth_3"); add("auth_4"); } }; @Override public void initialize(DynamicPropertyVerification dynamicPropertyVerification) { // 通过zk等获取远程配置,或加载本地配置(这个看情况了) } @Override public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) { // 判断需要校验的属性属于单个属性值,还是集合属性值 // 这里只针对"Admin"与["auth_1","auth_3","auth_2"]这样的格式进行校验 if (JSON.isValidArray(value)) { // 需要校验的属性,是一个集合类型(如权限列表) List<String> requestValueList = JSON.parseArray(value, String.class); boolean result = requestValueList.stream() .allMatch(requestValue -> isValidRequestValue(requestValue)); return result; } else { // 需要校验的属性,是一个单一属性字符串(如gender) boolean result = isValidRequestValue(value); return result; } } private boolean isValidRequestValue(final String value) { return REX_LIST.stream() .anyMatch(legalValue ->legalValue.equals(value)); } }首先这个注解是真实项目的代码,是我参与的蚂蚁金服某项目的商业平台代码。
为了实现商业化SDK,便需要后端自行负责数据校验。正好当时这块的负责人希望规范代码,所以就交给我,通过统一的Validation框架进行数据校验。
不过这个代码很快就增加禁止字段等,并通过接口实现了逻辑上的关注点分离。