很多人對(duì)spring mvc的請(qǐng)求處理流程都不陌生,今天我們要展開(kāi)講解的是請(qǐng)求體數(shù)據(jù)body的綁定、驗(yàn)證、格式化、類(lèi)型轉(zhuǎn)換,它是怎樣實(shí)現(xiàn)的呢?其實(shí)就是大家熟悉的HandlerAdapter 干的事情。
為何要講這個(gè)呢?其實(shí)和我最近的工作內(nèi)容是分不開(kāi)的,剛好在設(shè)計(jì)開(kāi)發(fā)一個(gè)數(shù)據(jù)聚合組件(它主要是解決微服務(wù)化后vo 拆分之疼),當(dāng)然后續(xù)會(huì)開(kāi)源出來(lái)的。
目錄
spring mvc知識(shí)回顧
BeanWrapper
ConversionService
Formatter
DataBinder
四者關(guān)系圖
Validator
非Spring MVC的使用
Spring MVC的使用
HttpMessageConverter和ConversionService是什么關(guān)系?
spring mvc知識(shí)回顧
?
?
1 用戶(hù)向服務(wù)器發(fā)送請(qǐng)求,請(qǐng)求被Spring的DispatcherServlet (簡(jiǎn)稱(chēng)DS )捕獲 2~5 DS 首先對(duì)URL進(jìn)行解析,得到請(qǐng)求資源標(biāo)識(shí)符(URI ),然后根據(jù)該URI ,調(diào)用HandlerMapping 獲得該Handler 配置的所有相關(guān)的對(duì)象(包括Handler對(duì)象以及Handler對(duì)象對(duì)應(yīng)的攔截器),最后以HandlerExecutionChain 對(duì)象的形式返回6~7 DS 根據(jù)獲得的Handler,選擇一個(gè)合適的HandlerAdapter (如果成功獲得HandlerAdapter 后,此時(shí)將開(kāi)始執(zhí)行攔截器的preHandler(...)方法)8 提取Request中的模型數(shù)據(jù),填充Handler入?yún)?#xff0c;開(kāi)始執(zhí)行Handler(Controller)。 在填充Handler的入?yún)⑦^(guò)程中,根據(jù)你的配置,Spring將幫你做一些額外的工作:驗(yàn)證、格式化、類(lèi)型轉(zhuǎn)換
HttpMessageConveter:將請(qǐng)求消息(如Json、xml等數(shù)據(jù))轉(zhuǎn)換成一個(gè)對(duì)象,將對(duì)象轉(zhuǎn)換為指定的響應(yīng)信息。
9 Handler執(zhí)行完成后,向DS 返回一個(gè)ModelAndView 對(duì)象 10 此時(shí)DS 將開(kāi)始執(zhí)行攔截器的postHandler(...)方法 11 DS 根據(jù)返回的ModelAndView ,選擇一個(gè)適合的ViewResolver (必須是已經(jīng)注冊(cè)到Spring容器中的ViewResolver)。并調(diào)用?該ViewResolver 結(jié)合Model和View,來(lái)渲染視圖。將渲染結(jié)果返回給客戶(hù)端。
在8中是有關(guān)spring mvc request vo的處理,其實(shí)Spring官方文檔有專(zhuān)門(mén)有些章節(jié)闡述“Validation, Data Binding, and Type Conversion”,其實(shí)用法很簡(jiǎn)單,但它內(nèi)部是如何實(shí)現(xiàn)的卻鮮有人知曉。
BeanWrapper
BeanWrapper 是一個(gè)方便開(kāi)發(fā)人員使用字符串來(lái)對(duì)Java Bean的屬性執(zhí)行g(shù)et、set操作的工具類(lèi)。它為那些UI類(lèi)app提供了極大的便利,是以字符串和用戶(hù)交互的。
Foo foo = new Foo();
BeanWrapperImpl fooWrapper = new BeanWrapperImpl(foo);
fooWrapper.setPropertyValue("intProperty", "1");
Object intProperty = fooWrapper.getPropertyValue("intProperty");
另外,BeanWrapper 內(nèi)部使用了兩種機(jī)制:
1. PropertyEditor
BeanWrapper 和java bean的內(nèi)省模式密切關(guān)聯(lián),之前的文章分享過(guò),而PropertyEditor( 只提供了String >> Object的轉(zhuǎn)換) 隸屬于Java Bean規(guī)范。
2.?ConversionService
Spring自3.0之后提供的替代PropertyEditor 的機(jī)制
注:按照Spring官方文檔的說(shuō)法,當(dāng)容器內(nèi)沒(méi)有注冊(cè)ConversionService的時(shí)候,會(huì)退回使用PropertyEditor機(jī)制。
ConversionService
ConversionService 及其相關(guān)一套類(lèi)型轉(zhuǎn)換機(jī)制是一套通用的類(lèi)型轉(zhuǎn)換SPI,相比PropertyEditor 只提供String >> Object的轉(zhuǎn)換,ConversionService 能夠提供任意Object >> Object的轉(zhuǎn)換。
由此我們可以看出,Spring為何要使用ConversionService 替代PropertyEditor有三個(gè)原因:
ConversionService 功能更強(qiáng)大,支持的類(lèi)型轉(zhuǎn)換范圍更廣ConverterFactory支持一整個(gè)class hierarchy的轉(zhuǎn)換(也就是多態(tài)),PropertyEditor 則不行 Java Bean這個(gè)規(guī)范最初是和Java GUI(Swing)一起誕生的,PropertyEditor 接口里有大量和GUI相關(guān)的方法,顯然已經(jīng)過(guò)時(shí)了。順便提一句,Java Bean和POJO不是一個(gè)概念,Java Bean不僅有setter、getter,還有一系列和Java GUI配套的東西。
Formatter
Formatter SPI是另外一套和PropertyEditor類(lèi)似的,String<->Object的轉(zhuǎn)換機(jī)制,但是有兩個(gè)優(yōu)點(diǎn):
接口更干凈,沒(méi)有關(guān)于GUI的部分,只有 Printer .print() 和 Parser .parse() 兩個(gè)方法 基于注解,支持同一類(lèi)型的屬性根據(jù)不同的格式 來(lái)做String<->Object的轉(zhuǎn)換。比如日期類(lèi)型,一個(gè)字段的格式是yyyy-MM-dd,另一個(gè)格式是yyyyMMdd,如果利用PropertyEditor是比較麻煩,但是在這里就可以利用@DateTimeFormat 來(lái)達(dá)到這個(gè)效果。
Spring提供了DefaultFormattingConversionService 來(lái)支持Formatter SPI,也就是說(shuō)如果要使用Formatter SPI,依然可以利用ConversionService 接口。
注:Formatter SPI必須基于注解才可以使用,這點(diǎn)和ConversionService基于類(lèi)型不同。
DataBinder
DataBinder主要提供了兩個(gè)功能:
利用BeanWrapper,給對(duì)象的屬性設(shè)值 在設(shè)值的同時(shí)做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());}}}
}
前四者關(guān)系圖
ConversionService有兩種實(shí)現(xiàn),也就是說(shuō),如果要支持Formatter SPI,只需要讓BeanWrapper切換使用不同的ConversionService即可。
DefaultConversionService ,不支持Formatter SPIDefaultFormattingConversionService ,支持Formatter SPI
?
Validator
Validator較簡(jiǎn)單,如果類(lèi)路徑上存在?Bean Validation(例如,Hibernate Validator),則將LocalValidatorFactoryBean? 注冊(cè)為全局Validator,以便與@Valid 一起使用在controller?方法參數(shù)上。
非Spring MVC的使用
Spring Core Context(要解析spring xml的)其實(shí)也使用ConversionService ,但是是非強(qiáng)制的。讓Spring Core Context使用conversionService的方式很簡(jiǎn)單,配置一個(gè)名字叫做conversionService的Bean即可。需要注意的是,因?yàn)檫@個(gè)Bean是在非常早的時(shí)候就被使用的(AbstractApplicationContext#L834),因此它最好不要依賴(lài)過(guò)多的其他的Bean,避免造成啟動(dòng)失敗。
Spring在讀取xml配置文件的時(shí)候,因?yàn)閤ml文件實(shí)際上是一個(gè)文本文件,所有值的設(shè)置都是String,這個(gè)時(shí)候如果給bean的復(fù)雜類(lèi)型屬性設(shè)置值,它會(huì)用到PropertyEditor或ConversionService。
<bean id="someBean" class="a.b.c.SomeBean"> ? ? <property name="color" value="red"/> </bean> ?
例子中的color屬性是Color類(lèi)型,這時(shí)就會(huì)利用到PropertyEditor 和ConversionService 。
Spring MVC的使用
Spring MVC對(duì)于conversionService的使用比較特殊,它自己會(huì)注冊(cè)一個(gè)名字叫做mvcConversionService類(lèi)型為DefaultFormattingConversionService 的Bean。因此會(huì)存在以下陷阱:
如果Core Context中也定義了一個(gè)ConversionService ,那么在MVC環(huán)境下,會(huì)有兩個(gè)ConversionService 的Bean。 針對(duì)Core Context的ConversionService做的Customize如FormatterRegistrar、ConverterRegistry 、FormatterRegistry 、ConversionServiceFactoryBean 、FormattingConversionServiceFactoryBean 是不會(huì)應(yīng)用到MVC的那個(gè)ConversionService 上。
上圖在后面源碼中細(xì)講,對(duì)于mvcConversionService的配置途徑見(jiàn)“DataBinder”或“MVC Config API”。
HttpMessageConverter 和ConversionService 是什么關(guān)系?
個(gè)人理解,可以他們是不同的兩種東西,二者各司其職,前者轉(zhuǎn)換請(qǐng)求body信息和響應(yīng)body信息,后者用于請(qǐng)求參數(shù)的轉(zhuǎn)換。都可以接受文本信息,最終解析成對(duì)象。根本的區(qū)別:
HttpMessageConvert
官方文檔中的說(shuō)明: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.
對(duì),@RequestBody 決定了要使用HttpMessageConverter ,而Content-Type則是選擇具體某一個(gè)配置器。HttpMessageConverter <T>,默認(rèn)有很多配置器:StringHttpMessageConverter ,ByteArrayHttpMessageConverter ,SourceHttpMessageConverter ,FormHttpMessageConverter? 。
ConversionService
它使用的是?WebDataBinder (extends DataBinder) ,處理url或@RequestParam 等非@RequestBody 參數(shù),它數(shù)據(jù)綁定是這樣的流程:
將ServletRequest 對(duì)象及處理方法入?yún)?duì)象實(shí)例傳給DataBinder? DataBinder? 調(diào)用轉(zhuǎn)配在Spring Web上下文中的ConversionService 進(jìn)行數(shù)據(jù)類(lèi)型轉(zhuǎn)換、數(shù)據(jù)格式化等工作,將ServletRequest 中的消息填充到入?yún)?duì)象中調(diào)用?Validator? 對(duì)已經(jīng)綁定的請(qǐng)求信息數(shù)據(jù)的入?yún)?duì)象進(jìn)行數(shù)據(jù)合法性校驗(yàn),生成數(shù)據(jù)綁定結(jié)果?BindingResult 。BindingResult 包含完成綁定的入?yún)?duì)象和相應(yīng)的校驗(yàn)錯(cuò)誤對(duì)象。而后將?BindingResult? 中的入?yún)?duì)象及校驗(yàn)錯(cuò)誤對(duì)象賦給處理方法的入?yún)ⅰ?/li>
// 引自org.springframework.web.bind.annotation.support.HandlerMethodInvoker
// 進(jìn)行數(shù)據(jù)類(lèi)型轉(zhuǎn)換、填充并驗(yàn)證
private Object[] resolveHandlerArguments(Method handlerMethod, Object handler,NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception {Class<?>[] paramTypes = handlerMethod.getParameterTypes();Object[] args = new Object[paramTypes.length];// controller方法的每個(gè)參數(shù)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)) {// 調(diào)用HttpMessageConvert服務(wù)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) {// 調(diào)用ConversionService服務(wù)args[i] = resolveRequestParam(paramName, required, defaultValue, methodParam, webRequest, handler);}else if (headerName != null) {// 調(diào)用ConversionService服務(wù)args[i] = resolveRequestHeader(headerName, required, defaultValue, methodParam, webRequest, handler);}else if (requestBodyFound) {// 調(diào)用HttpMessageConvert服務(wù)args[i] = resolveRequestBody(methodParam, webRequest, handler);}else if (cookieName != null) {// 調(diào)用HttpMessageConvert服務(wù)args[i] = resolveCookieValue(cookieName, required, defaultValue, methodParam, webRequest, handler);}else if (pathVarName != null) {// 調(diào)用HttpMessageConvert服務(wù)args[i] = resolvePathVariable(pathVarName, methodParam, webRequest, handler);}else if (attrName != null) {// 調(diào)用ConversionService服務(wù)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;
}
最后,HttpMessageConverter 和ConversionService 是沒(méi)有關(guān)系的,這點(diǎn)很多人講的都是錯(cuò)的!
總結(jié)
以上是生活随笔 為你收集整理的spring mvc的DataBinder、Validator、BeanWrapper、ConversionService、Formatter 的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
如果覺(jué)得生活随笔 網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔 推薦給好友。