1. 程式人生 > 程式設計 >Java Validation Api實現原理解析

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的原理說明,敬請指點。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。