背景:
接上篇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的原理說明,敬請指點。