日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

spring mvc的DataBinder、Validator、BeanWrapper、ConversionService、Formatter

發布時間:2024/1/1 c/c++ 43 豆豆
生活随笔 收集整理的這篇文章主要介紹了 spring mvc的DataBinder、Validator、BeanWrapper、ConversionService、Formatter 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

很多人對spring mvc的請求處理流程都不陌生,今天我們要展開講解的是請求體數據body的綁定、驗證、格式化、類型轉換,它是怎樣實現的呢?其實就是大家熟悉的HandlerAdapter干的事情。

為何要講這個呢?其實和我最近的工作內容是分不開的,剛好在設計開發一個數據聚合組件(它主要是解決微服務化后vo 拆分之疼),當然后續會開源出來的。

目錄

spring mvc知識回顧

BeanWrapper

ConversionService

Formatter

DataBinder

四者關系圖

Validator

非Spring MVC的使用

Spring MVC的使用

HttpMessageConverter和ConversionService是什么關系?


spring mvc知識回顧

?

?

1用戶向服務器發送請求,請求被Spring的DispatcherServlet(簡稱DS)捕獲
2~5DS 首先對URL進行解析,得到請求資源標識符(URI),然后根據該URI,調用HandlerMapping獲得該Handler配置的所有相關的對象(包括Handler對象以及Handler對象對應的攔截器),最后以HandlerExecutionChain對象的形式返回
6~7DS 根據獲得的Handler,選擇一個合適的HandlerAdapter(如果成功獲得HandlerAdapter后,此時將開始執行攔截器的preHandler(...)方法)
8

提取Request中的模型數據,填充Handler入參,開始執行Handler(Controller)。 在填充Handler的入參過程中,根據你的配置,Spring將幫你做一些額外的工作:驗證、格式化、類型轉換

HttpMessageConveter:將請求消息(如Json、xml等數據)轉換成一個對象,將對象轉換為指定的響應信息。

9Handler執行完成后,向DS返回一個ModelAndView對象
10此時DS將開始執行攔截器的postHandler(...)方法
11DS根據返回的ModelAndView,選擇一個適合的ViewResolver(必須是已經注冊到Spring容器中的ViewResolver)。并調用?該ViewResolver 結合Model和View,來渲染視圖。將渲染結果返回給客戶端。

在8中是有關spring mvc request vo的處理,其實Spring官方文檔有專門有些章節闡述“Validation, Data Binding, and Type Conversion”,其實用法很簡單,但它內部是如何實現的卻鮮有人知曉。

BeanWrapper

BeanWrapper是一個方便開發人員使用字符串來對Java Bean的屬性執行get、set操作的工具類。它為那些UI類app提供了極大的便利,是以字符串和用戶交互的。

Foo foo = new Foo(); BeanWrapperImpl fooWrapper = new BeanWrapperImpl(foo); fooWrapper.setPropertyValue("intProperty", "1"); Object intProperty = fooWrapper.getPropertyValue("intProperty");

另外,BeanWrapper內部使用了兩種機制:

1. PropertyEditor

BeanWrapper和java bean的內省模式密切關聯,之前的文章分享過,而PropertyEditor(只提供了String >> Object的轉換隸屬于Java Bean規范。

2.?ConversionService

Spring自3.0之后提供的替代PropertyEditor的機制

注:按照Spring官方文檔的說法,當容器內沒有注冊ConversionService的時候,會退回使用PropertyEditor機制。

ConversionService

ConversionService及其相關一套類型轉換機制是一套通用的類型轉換SPI,相比PropertyEditor只提供String >> Object的轉換,ConversionService能夠提供任意Object >> Object的轉換。

由此我們可以看出,Spring為何要使用ConversionService替代PropertyEditor有三個原因:

  • ConversionService功能更強大,支持的類型轉換范圍更廣
  • ConverterFactory支持一整個class hierarchy的轉換(也就是多態),PropertyEditor則不行
  • Java Bean這個規范最初是和Java GUI(Swing)一起誕生的,PropertyEditor接口里有大量和GUI相關的方法,顯然已經過時了。順便提一句,Java Bean和POJO不是一個概念,Java Bean不僅有setter、getter,還有一系列和Java GUI配套的東西。
  • Formatter

    Formatter SPI是另外一套和PropertyEditor類似的,String<->Object的轉換機制,但是有兩個優點:

  • 接口更干凈,沒有關于GUI的部分,只有 Printer.print() 和 Parser.parse() 兩個方法
  • 基于注解,支持同一類型的屬性根據不同的格式來做String<->Object的轉換。比如日期類型,一個字段的格式是yyyy-MM-dd,另一個格式是yyyyMMdd,如果利用PropertyEditor是比較麻煩,但是在這里就可以利用@DateTimeFormat來達到這個效果。
  • Spring提供了DefaultFormattingConversionService來支持Formatter SPI,也就是說如果要使用Formatter SPI,依然可以利用ConversionService接口。

    注:Formatter SPI必須基于注解才可以使用,這點和ConversionService基于類型不同。

    DataBinder

    DataBinder主要提供了兩個功能:

  • 利用BeanWrapper,給對象的屬性設值
  • 在設值的同時做Validation
  • //引自org.springframework.validation; public class DataBinder implements PropertyEditorRegistry, TypeConverter {public void bind(PropertyValues pvs) {MutablePropertyValues mpvs = pvs instanceof MutablePropertyValues ? (MutablePropertyValues)pvs : new MutablePropertyValues(pvs);this.doBind(mpvs);}protected void doBind(MutablePropertyValues mpvs) {this.checkAllowedFields(mpvs);this.checkRequiredFields(mpvs);this.applyPropertyValues(mpvs);}protected void applyPropertyValues(MutablePropertyValues mpvs) {try {this.getPropertyAccessor().setPropertyValues(mpvs, this.isIgnoreUnknownFields(), this.isIgnoreInvalidFields());} catch (PropertyBatchUpdateException var7) {PropertyAccessException[] var3 = var7.getPropertyAccessExceptions();int var4 = var3.length;for(int var5 = 0; var5 < var4; ++var5) {PropertyAccessException pae = var3[var5];this.getBindingErrorProcessor().processPropertyAccessException(pae, this.getInternalBindingResult());}}} }

    前四者關系圖

    ConversionService有兩種實現,也就是說,如果要支持Formatter SPI,只需要讓BeanWrapper切換使用不同的ConversionService即可。

    • DefaultConversionService,不支持Formatter SPI
    • DefaultFormattingConversionService,支持Formatter SPI

    ?

    Validator

    Validator較簡單,如果類路徑上存在?Bean Validation(例如,Hibernate Validator),則將LocalValidatorFactoryBean?注冊為全局Validator,以便與@Valid一起使用在controller?方法參數上。

    非Spring MVC的使用

    Spring Core Context(要解析spring xml的)其實也使用ConversionService,但是是非強制的。讓Spring Core Context使用conversionService的方式很簡單,配置一個名字叫做conversionService的Bean即可。需要注意的是,因為這個Bean是在非常早的時候就被使用的(AbstractApplicationContext#L834),因此它最好不要依賴過多的其他的Bean,避免造成啟動失敗。

    Spring在讀取xml配置文件的時候,因為xml文件實際上是一個文本文件,所有值的設置都是String,這個時候如果給bean的復雜類型屬性設置值,它會用到PropertyEditor或ConversionService。

    <bean id="someBean" class="a.b.c.SomeBean">
    ? ? <property name="color" value="red"/>
    </bean> ?

    例子中的color屬性是Color類型,這時就會利用到PropertyEditorConversionService

    Spring MVC的使用

    Spring MVC對于conversionService的使用比較特殊,它自己會注冊一個名字叫做mvcConversionService類型為DefaultFormattingConversionService的Bean。因此會存在以下陷阱:

    • 如果Core Context中也定義了一個ConversionService,那么在MVC環境下,會有兩個ConversionService的Bean。
    • 針對Core Context的ConversionService做的Customize如FormatterRegistrar、ConverterRegistryFormatterRegistryConversionServiceFactoryBeanFormattingConversionServiceFactoryBean是不會應用到MVC的那個ConversionService上。

    上圖在后面源碼中細講,對于mvcConversionService的配置途徑見“DataBinder”或“MVC Config API”。

    HttpMessageConverterConversionService是什么關系?

    個人理解,可以他們是不同的兩種東西,二者各司其職,前者轉換請求body信息和響應body信息,后者用于請求參數的轉換。都可以接受文本信息,最終解析成對象。根本的區別:

    HttpMessageConvert

    官方文檔中的說明:We can use the?@RequestBody?annotation?on the argument of a Controller method to indicate?that the body of the HTTP Request is deserialized to that particular Java entity. To determine the appropriate converter, Spring will use the “Content-Type” header from the client request.

    對,@RequestBody決定了要使用HttpMessageConverter,而Content-Type則是選擇具體某一個配置器。HttpMessageConverter<T>,默認有很多配置器:StringHttpMessageConverterByteArrayHttpMessageConverterSourceHttpMessageConverterFormHttpMessageConverter?

    ConversionService

    它使用的是?WebDataBinder (extends DataBinder),處理url或@RequestParam等非@RequestBody參數,它數據綁定是這樣的流程:

  • ServletRequest對象及處理方法入參對象實例傳給DataBinder?
  • DataBinder?調用轉配在Spring Web上下文中的ConversionService進行數據類型轉換、數據格式化等工作,將ServletRequest中的消息填充到入參對象中
  • 調用?Validator?對已經綁定的請求信息數據的入參對象進行數據合法性校驗,生成數據綁定結果?BindingResultBindingResult 包含完成綁定的入參對象和相應的校驗錯誤對象。而后將?BindingResult?中的入參對象及校驗錯誤對象賦給處理方法的入參。
  • // 引自org.springframework.web.bind.annotation.support.HandlerMethodInvoker // 進行數據類型轉換、填充并驗證 private Object[] resolveHandlerArguments(Method handlerMethod, Object handler,NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception {Class<?>[] paramTypes = handlerMethod.getParameterTypes();Object[] args = new Object[paramTypes.length];// controller方法的每個參數for (int i = 0; i < args.length; i++) {MethodParameter methodParam = new SynthesizingMethodParameter(handlerMethod, i);methodParam.initParameterNameDiscovery(this.parameterNameDiscoverer);GenericTypeResolver.resolveParameterType(methodParam, handler.getClass());String paramName = null;String headerName = null;boolean requestBodyFound = false;String cookieName = null;String pathVarName = null;String attrName = null;boolean required = false;String defaultValue = null;boolean validate = false;Object[] validationHints = null;int annotationsFound = 0;Annotation[] paramAnns = methodParam.getParameterAnnotations();for (Annotation paramAnn : paramAnns) {if (RequestParam.class.isInstance(paramAnn)) {RequestParam requestParam = (RequestParam) paramAnn;paramName = requestParam.name();required = requestParam.required();defaultValue = parseDefaultValueAttribute(requestParam.defaultValue());annotationsFound++;}else if (RequestHeader.class.isInstance(paramAnn)) {RequestHeader requestHeader = (RequestHeader) paramAnn;headerName = requestHeader.name();required = requestHeader.required();defaultValue = parseDefaultValueAttribute(requestHeader.defaultValue());annotationsFound++;}else if (RequestBody.class.isInstance(paramAnn)) {requestBodyFound = true;annotationsFound++;}else if (CookieValue.class.isInstance(paramAnn)) {CookieValue cookieValue = (CookieValue) paramAnn;cookieName = cookieValue.name();required = cookieValue.required();defaultValue = parseDefaultValueAttribute(cookieValue.defaultValue());annotationsFound++;}else if (PathVariable.class.isInstance(paramAnn)) {PathVariable pathVar = (PathVariable) paramAnn;pathVarName = pathVar.value();annotationsFound++;}else if (ModelAttribute.class.isInstance(paramAnn)) {ModelAttribute attr = (ModelAttribute) paramAnn;attrName = attr.value();annotationsFound++;}else if (Value.class.isInstance(paramAnn)) {defaultValue = ((Value) paramAnn).value();}else {Validated validatedAnn = AnnotationUtils.getAnnotation(paramAnn, Validated.class);if (validatedAnn != null || paramAnn.annotationType().getSimpleName().startsWith("Valid")) {validate = true;Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(paramAnn));validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[]{hints});}}}if (annotationsFound > 1) {throw new IllegalStateException("Handler parameter annotations are exclusive choices - " +"do not specify more than one such annotation on the same parameter: " + handlerMethod);}if (annotationsFound == 0) {Object argValue = resolveCommonArgument(methodParam, webRequest);if (argValue != WebArgumentResolver.UNRESOLVED) {args[i] = argValue;}else if (defaultValue != null) {args[i] = resolveDefaultValue(defaultValue);}else {Class<?> paramType = methodParam.getParameterType();if (Model.class.isAssignableFrom(paramType) || Map.class.isAssignableFrom(paramType)) {if (!paramType.isAssignableFrom(implicitModel.getClass())) {throw new IllegalStateException("Argument [" + paramType.getSimpleName() + "] is of type " +"Model or Map but is not assignable from the actual model. You may need to switch " +"newer MVC infrastructure classes to use this argument.");}args[i] = implicitModel;}else if (SessionStatus.class.isAssignableFrom(paramType)) {args[i] = this.sessionStatus;}else if (HttpEntity.class.isAssignableFrom(paramType)) {// 調用HttpMessageConvert服務args[i] = resolveHttpEntityRequest(methodParam, webRequest);}else if (Errors.class.isAssignableFrom(paramType)) {throw new IllegalStateException("Errors/BindingResult argument declared " +"without preceding model attribute. Check your handler method signature!");}else if (BeanUtils.isSimpleProperty(paramType)) {paramName = "";}else {attrName = "";}}}if (paramName != null) {// 調用ConversionService服務args[i] = resolveRequestParam(paramName, required, defaultValue, methodParam, webRequest, handler);}else if (headerName != null) {// 調用ConversionService服務args[i] = resolveRequestHeader(headerName, required, defaultValue, methodParam, webRequest, handler);}else if (requestBodyFound) {// 調用HttpMessageConvert服務args[i] = resolveRequestBody(methodParam, webRequest, handler);}else if (cookieName != null) {// 調用HttpMessageConvert服務args[i] = resolveCookieValue(cookieName, required, defaultValue, methodParam, webRequest, handler);}else if (pathVarName != null) {// 調用HttpMessageConvert服務args[i] = resolvePathVariable(pathVarName, methodParam, webRequest, handler);}else if (attrName != null) {// 調用ConversionService服務WebDataBinder binder =resolveModelAttribute(attrName, methodParam, implicitModel, webRequest, handler);boolean assignBindingResult = (args.length > i + 1 && Errors.class.isAssignableFrom(paramTypes[i + 1]));if (binder.getTarget() != null) {doBind(binder, webRequest, validate, validationHints, !assignBindingResult);}args[i] = binder.getTarget();if (assignBindingResult) {args[i + 1] = binder.getBindingResult();i++;}implicitModel.putAll(binder.getBindingResult().getModel());}}return args; }

    最后,HttpMessageConverterConversionService是沒有關系的,這點很多人講的都是錯的!

    總結

    以上是生活随笔為你收集整理的spring mvc的DataBinder、Validator、BeanWrapper、ConversionService、Formatter的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。