zoukankan      html  css  js  c++  java
  • Java Validation Api (原理篇) の @Valid+@RequestBody

    背景:
      接上篇validation使用篇【https://www.cnblogs.com/guoguochong/p/12886303.html

    前言:
      涉及知识点:AOP、拦截器相关
      功能主要实现类:因为bean validation只提供了接口并未实现,使用时需要加上一个provider的包,例如hibernate-validator
      范围: 注解:@Valid @RequestBudy
      主要实现类:RequestResponseBodyMethodProcessor
      处理器:HandlerMethodArgumentResolver
      注解说明:

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

    RequestResponseBodyMethodProcessor 类说明:

     1 // @since 3.1
     2 public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
     3 
     4     @Override
     5     public boolean supportsParameter(MethodParameter parameter) {
     6         return parameter.hasParameterAnnotation(RequestBody.class);
     7     }
     8     // 类上或者方法上标注了@ResponseBody注解都行
     9     @Override
    10     public boolean supportsReturnType(MethodParameter returnType) {
    11         return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) || returnType.hasMethodAnnotation(ResponseBody.class));
    12     }
    13     
    14     // 这是处理入参封装校验的入口,也是本文关注的焦点
    15     @Override
    16     public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
    17             NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    18         
    19         // 它是支持`Optional`容器的
    20         parameter = parameter.nestedIfOptional();
    21         // 使用消息转换器HttpInputMessage把request请求转换出来,拿到值~~~
    22         // 此处注意:比如本例入参是Person类,所以经过这里处理会生成一个空的Person对象出来(反射)
    23         Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
    24 
    25         // 获取到入参的名称,其实不叫形参名字,应该叫objectName给校验时用的
    26         // 请注意:这里的名称是类名首字母小写,并不是你方法里写的名字。比如本利若形参名写为personAAA,但是name的值还是person
    27         // 但是注意:`parameter.getParameterName()`的值可是personAAA
    28         String name = Conventions.getVariableNameForParameter(parameter);
    29 
    30         // 只有存在binderFactory才会去完成自动的绑定、校验~
    31         // 此处web环境为:ServletRequestDataBinderFactory
    32         if (binderFactory != null) {
    33             WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
    34 
    35             // 显然传了参数才需要去绑定校验嘛
    36             if (arg != null) {
    37 
    38                 // 这里完成数据绑定+数据校验~~~~~(绑定的错误和校验的错误都会放进Errors里)
    39                 // Applicable:适合
    40                 validateIfApplicable(binder, parameter);
    41 
    42                 // 若有错误消息hasErrors(),并且仅跟着的一个参数不是Errors类型,Spring MVC会主动给你抛出MethodArgumentNotValidException异常
    43                 // 否则,调用者自行处理
    44                 if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
    45                     throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
    46                 }
    47             }
    48         
    49             // 把错误消息放进去 证明已经校验出错误了~~~
    50             // 后续逻辑会判断MODEL_KEY_PREFIX这个key的~~~~
    51             if (mavContainer != null) {
    52                 mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
    53             }
    54         }
    55 
    56         return adaptArgumentIfNecessary(arg, parameter);
    57     }
    58 
    59     // 校验,如果合适的话。使用WebDataBinder,失败信息最终也都是放在它身上~  本方法是本文关注的焦点
    60     // 入参:MethodParameter parameter
    61     protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
    62         // 拿到标注在此参数上的所有注解们(比如此处有@Valid和@RequestBody两个注解)
    63         Annotation[] annotations = parameter.getParameterAnnotations();
    64         for (Annotation ann : annotations) {
    65             // 先看看有木有@Validated
    66             Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
    67 
    68             // 这个里的判断是关键:可以看到标注了@Validated注解 或者注解名是以Valid打头的 都会有效哦
    69             //注意:这里可没说必须是@Valid注解。实际上你自定义注解,名称只要一Valid开头都成~~~~~
    70             if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
    71                 // 拿到分组group后,调用binder的validate()进行校验~~~~
    72                 // 可以看到:拿到一个合适的注解后,立马就break了~~~
    73                 // 所以若你两个主机都标注@Validated和@Valid,效果是一样滴~
    74                 Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
    75                 Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
    76                 binder.validate(validationHints);
    77                 break;
    78             }
    79         }
    80     }
    81     ...
    82 }

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


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

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

  • 相关阅读:
    莫比乌斯反演
    CDQ 分治
    二分图的最大匹配、完美匹配和匈牙利算法
    网络流简介
    BSGS && EXBSGS
    fhq-treap
    炸鱼w咕咕咕
    路由器配置——静态路由
    路由器配置——静态路由-回环地址测试
    路由器配置——单臂路由实现VLAN间通信
  • 原文地址:https://www.cnblogs.com/guoguochong/p/13227778.html
Copyright © 2011-2022 走看看