Java Validation Api实现原理解析

所属分类: 软件编程 / java 阅读数: 53
收藏 0 赞 0 分享

前言:

  涉及知识点: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的原理说明,敬请指点。

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

更多精彩内容其他人还在看

详解Spring依赖注入:@Autowired,@Resource和@Inject区别与实现原理

这篇文章主要介绍了详解Spring依赖注入:@Autowired,@Resource和@Inject区别与实现原理,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
收藏 0 赞 0 分享

了解spring中的CloudNetflix Hystrix弹性客户端

这篇文章主要介绍了了解spring中的CloudNetflix Hystrix弹性客户端,客户端弹性模式是在远程服务发生错误或表现不佳时保护远程资源(另一个微服务调用或者数据库查询)免于崩溃。,需要的朋友可以参考下
收藏 0 赞 0 分享

Spark学习笔记Spark Streaming的使用

这篇文章主要介绍了Spark学习笔记Spark Streaming的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
收藏 0 赞 0 分享

通过实例讲解springboot整合WebSocket

这篇文章主要介绍了通过实例讲解springboot整合WebSocket,WebSocket为游览器和服务器提供了双工异步通信的功能,即游览器可以向服务器发送消息,服务器也可以向游览器发送消息。,需要的朋友可以参考下
收藏 0 赞 0 分享

java虚拟机学习笔记进阶篇

在本篇内容里小编给大家分享了关于java虚拟机学习笔记的进阶内容,需要的朋友们跟着学习下。
收藏 0 赞 0 分享

java虚拟机学习高级篇

在本篇文章里小编给大家整理了关于java虚拟机学习高级篇的相关内容,有兴趣的朋友们跟着学习参考下。
收藏 0 赞 0 分享

java虚拟机中多线程总结

在本篇内容中小编给大家分享的是关于java虚拟机中多线程的知识点总结内容,需要的朋友们参考学习下。
收藏 0 赞 0 分享

java虚拟机多线程进阶篇总结

在本篇内容里小编给大家整理了关于java虚拟机多线程进阶篇的相关知识点内容,有兴趣的朋友们跟着参考下。
收藏 0 赞 0 分享

java数据结构和算法中数组的简单入门

在本文里小编给大家整理了关于java数据结构和算法中数组的简单入门知识点整理,需要的朋友们学习下。
收藏 0 赞 0 分享

java数据结构和算法中哈希表知识点详解

在本篇文章里小编给大家分享了关于java数据结构和算法中哈希表的相关知识点内容,需要的朋友们学习下。
收藏 0 赞 0 分享
查看更多