之前写的注解校验的不足
前面写到如何使用自定义的注解进行校验,@FieldInfo
主要做法是在注解中加入一些需要校验的信息
然后呢 编写解析注解的方法,然后我们写统一的校验策略来进行校验。
最后在统一的调用这个校验算法来达到校验的目的
但是上面这个过程呢我们并不知道使用该注解进行校验的策略有多少个。
而且如果需要加入新的校验策略时候,那个调用校验策略的类还是需要修改的。、
比如 增加了一个校验策略,那么原先调用的校验策略如果没有使用策略模式的话,肯定要加入新的校验逻辑的.
这个关键是要在调用我们的校验策略类那里下功夫。校验策略类可以使用模板方法模式或者策略模式来进行设计
目标是加入新的校验策略我们不用该调用的逻辑
事件编程给我们的启示
事件编程我们写好这个响应函数之后,注册给监听器,但是逻辑上这段代码并没有立马就被调用执行
而是等待的事件触发之后回调的。
其实呢,校验也是一样,如果我们编写了一个储存了校验信息的注解,( 注意: 范围有些大,
或者我们根据字段来进行区分,针对某一种字段做一个注解,比如身份证号码的注解信息,这样我们可以将校验的范围缩小,有利于我们的实现控制 。注解信息的拆分成各个小的注解类)
我们可以将该注解需要进行校验的策略绑定到该注解上面,这样我们可以知道这个注解上到底使用了哪些校验算法。
如果我们的校验算法有扩充,只要新加入新的校验策略,然后注册到该注解的绑定校验策略的集合上,就可以进行新的校验
我们其他的地方都不要改动了。符合了设计模式的 修改的封闭,进行插入来达到功能的修改。
奥义: 将策略模式应用到注解本身,给注解注册能够提供校验的策略类
@Constraint 注解的威力
我们的注解如果想来进行校验字段,那么依赖 @Constraint 是很不错的选择的
@Documented @Target({ ANNOTATION_TYPE }) @Retention(RUNTIME) public @interface Constraint { Class<? extends ConstraintValidator<?, ?>>[] validatedBy(); }
其中的 validatedBy属性指定了需要进行校验的策略类集合,这是一个数组。
也就是说我们定义的注解可以使用@Constraint 进行修饰指定校验策略.
数组的类型是 ConstraintValidator.上面的两个??是啥呢?看看ConstraintValidator的代码:
ConstraintValidator.java
public interface ConstraintValidator<A extends Annotation, T> { void initialize(A constraintAnnotation); boolean isValid(T value, ConstraintValidatorContext context); }
第一个是我们的注解,也就是我们自己定义的注解,第二个是参数的值
ConstraintValidator是一个接口,也就是我们自己定义的校验类要实现这个接口
使用@Constraint
使用@Constraint 修饰我们的注解
例如 我们 校验手机号,可以写一个 @Mobile的注解
Mobile.java
package miaosha.annotation; import java.lang.annotation.Target; import javax.validation.Constraint; import javax.validation.Payload; import miaosha.validator.MobileValidator; import static java.lang.annotation.ElementType.ANNOTATION_TYPE; import static java.lang.annotation.ElementType.CONSTRUCTOR; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.RetentionPolicy.RUNTIME; import java.lang.annotation.Documented; import java.lang.annotation.Retention; /** * 做一个mobile的注解 注意我们使用的静态导入 * * @author kaifeng1 * */ @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) @Retention(RUNTIME) @Documented @Constraint(validatedBy = { MobileValidator.class }) public @interface Mobile { boolean required() default true; String message() default "手机号码格式错误"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
需要自定义一个校验器 MobileValidator,它使用 ValidatorUtil来完成校验的具体逻辑
MobileValidator.java
package miaosha.validator; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import org.apache.commons.lang3.StringUtils; import miaosha.annotation.Mobile; import miaosha.util.ValidatorUtil; public class MobileValidator implements ConstraintValidator<Mobile, String> { private boolean required = false; /** * 初始化 */ public void initialize(Mobile constraintAnnotation) { required = constraintAnnotation.required(); } /** * 校验 */ public boolean isValid(String value, ConstraintValidatorContext context) { if (required) { return ValidatorUtil.isMobile(value); } else { if (StringUtils.isEmpty(value)) { return true; } else { return ValidatorUtil.isMobile(value); } } } }
ValidatorUtil.java
package miaosha.util; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.lang3.StringUtils; /** * 校验 * * @author kaifeng1 * */ public class ValidatorUtil { private static final Pattern mobile_pattern = Pattern.compile("1\\d{10}"); public static boolean isMobile(String src) { if (StringUtils.isEmpty(src)) { return false; } Matcher m = mobile_pattern.matcher(src); return m.matches(); } }
我们使用该注解的时候:
@Mobile private String mobile;
最终调用点
我们可以使用 spring-boot-starter-validation 框架来进行校验
使用 @Valid 标签即可
这个注解纯粹是 标记接口,里面什么也没有
@Target({ METHOD, FIELD, CONSTRUCTOR, PARAMETER }) @Retention(RUNTIME) public @interface Valid { }
例如 我们在controller的入口参数上面加入,@Valid修饰我们的参数
这样校验就可以生效了
主要使用的是:
org.springframework.validation 来进行的
@RequestMapping("/doLogin") @ResponseBody Result<Boolean> doLogin(@Valid LoginVo vo) { log.info(vo.toString()); return Result.sucess(Boolean.TRUE); }
但是呢如果被校验住的的时候这时候是会出错的。
这里会抛出这个:
org.springframework.validation.BindException
这个异常时调用我们controller之前就发生的,因此需要异常拦截机制
所以我们必须要进行异常的拦截才可以发现这些信息的.
在下节中介绍加入异常的拦截机制.