Java Validation Api实现原理解析

前言:

  涉及知识点:AOP、拦截器相关

  功能主要实现类:因为bean validation只提供了接口并未实现,使用时需要加上一个provider的包,例如hibernate-validator

  范围: 注解:@Valid @RequestBudy

  主要实现类:RequestResponseBodyMethodProcessor

  处理器:HandlerMethodArgumentResolver

  注解说明:

  • @Valid:标准JSR-303规范的标记型注解,用来标记验证属性和方法返回值,进行级联和递归校验,@Valid可用于方法、字段、构造器和参数上
  • @RequestBudy 请求的Body体,只能被读取一次

RequestResponseBodyMethodProcessor 类说明:

// @since 3.1
public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {

  @Override
  public boolean supportsParameter(MethodParameter parameter) {
    return parameter.hasParameterAnnotation(RequestBody.class);
  }
  // 类上或者方法上标注了@ResponseBody注解都行
  @Override
  public boolean supportsReturnType(MethodParameter returnType) {
    return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) || returnType.hasMethodAnnotation(ResponseBody.class));
  }

  // 这是处理入参封装校验的入口,也是本文关注的焦点
  @Override
  public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
      NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

    // 它是支持`Optional`容器的
    parameter = parameter.nestedIfOptional();
    // 使用消息转换器HttpInputMessage把request请求转换出来,拿到值~~~
    // 此处注意:比如本例入参是Person类,所以经过这里处理会生成一个空的Person对象出来(反射)
    Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());

    // 获取到入参的名称,其实不叫形参名字,应该叫objectName给校验时用的
    // 请注意:这里的名称是类名首字母小写,并不是你方法里写的名字。比如本利若形参名写为personAAA,但是name的值还是person
    // 但是注意:`parameter.getParameterName()`的值可是personAAA
    String name = Conventions.getVariableNameForParameter(parameter);

    // 只有存在binderFactory才会去完成自动的绑定、校验~
    // 此处web环境为:ServletRequestDataBinderFactory
    if (binderFactory != null) {
      WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);

      // 显然传了参数才需要去绑定校验嘛
      if (arg != null) {

        // 这里完成数据绑定+数据校验~~~~~(绑定的错误和校验的错误都会放进Errors里)
        // Applicable:适合
        validateIfApplicable(binder, parameter);

        // 若有错误消息hasErrors(),并且仅跟着的一个参数不是Errors类型,Spring MVC会主动给你抛出MethodArgumentNotValidException异常
        // 否则,调用者自行处理
        if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
          throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
        }
      }

      // 把错误消息放进去 证明已经校验出错误了~~~
      // 后续逻辑会判断MODEL_KEY_PREFIX这个key的~~~~
      if (mavContainer != null) {
        mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
      }
    }

    return adaptArgumentIfNecessary(arg, parameter);
  }

  // 校验,如果合适的话。使用WebDataBinder,失败信息最终也都是放在它身上~ 本方法是本文关注的焦点
  // 入参:MethodParameter parameter
  protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
    // 拿到标注在此参数上的所有注解们(比如此处有@Valid和@RequestBody两个注解)
    Annotation[] annotations = parameter.getParameterAnnotations();
    for (Annotation ann : annotations) {
      // 先看看有木有@Validated
      Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);

      // 这个里的判断是关键:可以看到标注了@Validated注解 或者注解名是以Valid打头的 都会有效哦
      //注意:这里可没说必须是@Valid注解。实际上你自定义注解,名称只要一Valid开头都成~~~~~
      if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
        // 拿到分组group后,调用binder的validate()进行校验~~~~
        // 可以看到:拿到一个合适的注解后,立马就break了~~~
        // 所以若你两个主机都标注@Validated和@Valid,效果是一样滴~
        Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
        Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
        binder.validate(validationHints);
        break;
      }
    }
  }
  ...
}

  可以看得,这个类应该是陌生的,它能够处理@ResponseBody注解返回值;它还有另一个能力是:它能够处理请求参数(当然也是标注了@RequestBody的JavaBean)
所以它既是个处理返回值的HandlerMethodReturnValueHandler,又是一个处理入参的HandlerMethodArgumentResolver。所以它命名为Processor而不是Resolver/Handler。

  这是使用@RequestBody结合@Valid完成数据校验的基本原理。其实当Spring MVC在处理@RequestPart注解入参数据时,也会执行绑定、校验的相关逻辑。对应处理器是RequestPartMethodArgumentResolver,原理大体上和这相似,它主要处理Multipart相关,本文忽略~

以上就是dui'y对于@Valid标注的@RequestBody的JavaBean的原理说明,敬请指点。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Java Validation Api使用方法实例解析

    在我们应用程序的业务逻辑中,经常会碰到参数校验的情况,手动的在代码层上面进行校验就会带来很不好的体验,阅读.维护的成本会大大增加,造成冗余.因此有了这个JSR 303. Bean Validation为JavaBean提供了相应的API来给我们做参数的验证.通过Bean Validation比如@NotNull @Pattern等方法来对我们字段的值做进一步的教研. Bean Validation是一个运行时框架,在验证之后错误信息会直接返回. 依赖: <!--添加依赖--> <depe

  • Java Validation Api如何实现自定义注解

    背景 官方提供的注解多数可以解决现实业务场景中基本业务校验,但有些特殊场景因业务的复杂性,也还是需要在入口处对入参进行各种角度的校验,以求简化业务层的处理,降低业务处理复杂性与方法入口的强约束性. 以上背景,下面就举个简单Demo进行自定义注解校验的实现. 注解定义类 import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.ElementType; impor

  • Java调用腾讯云短信API接口的实现

    前言 有想使用短信API服务的朋友可以如果没有注册腾讯云的话可以注册一下,好像新用户可以免费领取100条短信服务(PS:绝对不是打广告) 一.登录腾讯云操作平台 登陆腾讯云的平台https://cloud.tencent.com/,登陆成功后搜索短信,进入短信产品的控制台,进入短信平台后,需要创建签名和创建正文模板. 1.创建签名模板 2.创建正文模板 这两步都需要审核,按照指引填写即可,一般时间会在2小时左右. 当你的签名审核通过后,在控制台可以看到签名的id和签名的内容,其中签名的内容在后续

  • Java8新特性时间日期库DateTime API及示例详解

    Java8新特性的功能已经更新了不少篇幅了,今天重点讲解时间日期库中DateTime相关处理.同样的,如果你现在依旧在项目中使用传统Date.Calendar和SimpleDateFormat等API来处理日期相关操作,这篇文章你一定不要错过.来刷新你的知识库吧! 背景 Java对日期.日历及时间的处理一直以来都饱受诟病,比如java.util.Date和java.util.Calendar类易用性差,不支持时区,非线程安全:还有用于格式化日期的类DateFormat也是非线程安全的等问题. J

  • 如何利用Java8 Stream API对Map按键或值排序

    一.什么是Java 8 Stream 使用Java 8 Streams,我们可以按键和按值对映射进行排序.下面是它的工作原理: Java Stream函数式编程?用过都说好,案例图文详解送给你 将Map或List等集合类对象转换为Stream对象 使用Streams的sorted()方法对其进行排序 最终将其返回为LinkedHashMap(可以保留排序顺序) sorted()方法以Comparator作为参数,从而可以按任何类型的值对Map进行排序.如果对Comparator不熟悉,可以看本号

  • 使用java8 API遍历过滤文件目录及子目录和隐藏文件示例详解

    1. 使用Files.list()迭代目录及其子目录文件 Files.list()可以迭代目录及其子目录文件 Files.list(Paths.get(".")) //当前目录 .forEach(System.out::println); 输出: .\filename1.txt .\directory1 .\filename2.txt .\Employee.java 2. 使用 filter表达式过滤文件 过滤器函数引用,isRegularFile表示普通文件 Files.list(P

  • Java 8 Stream Api 中的 map和 flatMap 操作方法

    1.前言 Java 8提供了非常好用的 Stream API ,可以很方便的操作集合.今天我们来探讨两个 Stream中间操作 map(Function<? super T, ? extends R> mapper) 和 flatMap(Function<? super T, ? extends Stream<? extends R>> mapper) 2. map 操作 map 操作是将流中的元素进行再次加工形成一个新流.这在开发中很有用.比如我们有一个学生集合,我们

  • Springboot项目javax.validation使用方法详解

    javax.validation 是基于JSR-303标准开发出来的,使用注解方式实现,及其方便,但是这只是一个接口,没有具体实现. Hibernate-Validator是一个hibernate独立的包,可以直接引用,他实现了javax.validation同时有做了扩展,比较强大.SpringBoot在内部集成了hibernate-validation,可以直接使用. 常用注解: 代码 说明 @Null 被注解的元素必须为null @NotNull 被注解的元素必须不为null @Asser

  • JAVA中通过Hibernate-Validation进行参数验证

    在开发JAVA服务器端代码时,我们会遇到对外部传来的参数合法性进行验证,而hibernate-validator提供了一些常用的参数校验注解,我们可以拿来使用. 1.maven中引入hibernate-validator对应的jar: <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>4

  • java validation 后台参数验证的使用详解

    一.前言 在后台开发过程中,对参数的校验成为开发环境不可缺少的一个环节.比如参数不能为null,email那么必须符合email的格式,如果手动进行if判断或者写正则表达式判断无意开发效率太慢,在时间.成本.质量的博弈中必然会落后.所以把校验层抽象出来是必然的结果,下面说下几种解决方案. 二.几种解决方案 1.struts2的valid可以通过配置xml,xml中描述规则和返回的信息,这种方式比较麻烦.开发效率低,不推荐 2.validation bean 是基于JSR-303标准开发出来的,使

随机推荐