編程常見錯誤50例03springweb篇6講10丨解析_第1頁
編程常見錯誤50例03springweb篇6講10丨解析_第2頁
編程常見錯誤50例03springweb篇6講10丨解析_第3頁
編程常見錯誤50例03springweb篇6講10丨解析_第4頁
編程常見錯誤50例03springweb篇6講10丨解析_第5頁
已閱讀5頁,還剩13頁未讀 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)

文檔簡介

案例1:接受Header使用錯MapSpringHeader個名為myHeaderName的Header,我們會書寫代碼如下:代代 (path="/hi",method=publicStringhi(@RequestHeader("myHeaderName")String//省略body4@RequestHeaderHeader需要解析的Header很多時,按照上面的方式很明顯會使得參數(shù)越來越多。在這種情況MapHeaderMap代代1234(path="/hi1",method=publicStringhi1(@RequestHeader()Mapmap){returnmap.toString();粗略測試程序,你會發(fā)現(xiàn)一切都很好。而且上面的代碼也符合針對接口編程的范式,即使pmyheader:h1myheader:h2HeadermyHeaderHeader代代1{myheader=h1,host=localhost:8080,connection=Keep-Alive,user-agent=Apache-Key:1Map2,我們就不能拿到所有的值。這里我們可以翻閱代碼查下Map是如何接收到所有請求的。對于一個Header的解析,主要有兩種方式,分別實現(xiàn)在RequestHeaderMethodArgumentResolver和RequestHeaderMapMethodArgumentResolver中,它們都繼承于比下它們的supportsParameter(),來對比它們適合的場景:RequestHeaderMapMethodArgumentResolver以發(fā)現(xiàn),對于一個標(biāo)記了@RequestHeader的參數(shù),如果它的類型是Map,則使用在我們的案例中,很明顯,參數(shù)類型定義為Map,所以使用的自然是Header的,關(guān)鍵代碼參考resolveArgument():代publicObjectresolveArgument(MethodParameterparameter,@NullableNativeWebRequestwebRequest,@NullableWebDataBinderFactoryClass<?>paramType=if(MultiValueMap.class.isAssignableFrom(paramType))MultiValueMap<String,String>if(HttpHeaders.class.isAssignableFrom(paramType))result=new elseresult=new for tor<String> tor=webRequest.getHeaderNames(); StringheaderName= String[]headerValues=if(headerValues!=null)for(StringheaderValue:headerValues)result.add(headerName, return elseMap<String,String>result=newfor tor<String> tor=webRequest.getHeaderNames(); StringheaderName= StringheaderValue=if(headerValue!=null)result.put(headerName,}}return36}針對我們的案例,這里并不是MultiValueMap,所以我們會走入else分支。這個分支首先會定義一個LinkedHashMap,然后將請求一一放置進(jìn)去,并返回。其中第29行是去解析獲取Header值的實際調(diào)用,在不同的容器下實現(xiàn)不同。例如在Tomcat容器下,它的執(zhí)行方法參考MimeHeaders#getValue:代代publicMessageBytesgetValue(Stringname)for(inti=0;i<count;i++)3(headers[i].getName().equalsIgnoreCase(name))4return5}6}78當(dāng)一個請求出現(xiàn)多個同名Header時,我們只要匹配上任何一個即立馬返回。所以在本案例中,只返回了一個Header的值。其實換一個角度思考這個問題,畢竟前面已經(jīng)定義的接收類型是LinkedHashMap,它的Value的泛型類型是String,也不適合去組織多個值的情況。綜上,不管是結(jié)合代碼還是,本案例的代碼都不能獲取到myHeader的所有值。在RequestHeaderMapMethodArgumentResolver的resolveArgument()中,假設(shè)我們的參數(shù)類型是MultiValueMap,我們一般會創(chuàng)建一個LinkedMultiValueMap,然后使用下面的語句來獲取Header的值并添加到Map中去:String[]headerValues=Header設(shè)我們定義的是HttpHeaders(也是一種MultiValueMap),我們會直接創(chuàng)建一個HttpHeaders來所有的Header。有了上面的解析,我們可以得出這樣一個結(jié)論:要完整接收到所有的Header,不能直接使用Map而應(yīng)該使用MultiValueMap。我們可以采用以下兩種方式來修正這個問題:代代//方式@RequestHeader()MultiValueMap//方式@RequestHeader()HttpHeaders[myheader:"h1","h2",host:"localhost:8080",connection:"Keep-Alive",user- /4.5.12(Java/11.0.6)",accept-encoding:"gzip,deflate"]2Header取Content-Type直接調(diào)用它的getContentType()即可,諸如此類,非常好用。2Header在HTTP協(xié)議中,Header的名稱是無所謂大小寫的。在使用各種框架構(gòu)建Web時,我們都會把這個事實銘記于心。我們可以驗證下這個想法。例如,我們有一個Web服務(wù)接口如代代 (path="/hi2",method=publicStringhi2(@RequestHeader("MyHeader")Stringreturn4myheader:另外,結(jié)合案例1,我們知道可以使用Map來接收所有的Header,那么這種方式下是否代代 (path="/hi2",method=publicStringhi2(@RequestHeader("MyHeader")StringmyHeader,@RequestHeaderreturnmyHeader+"comparewith:"+4myheadervaluecomparewith:綜合來看,直接獲取Header是可以忽略大小寫的,但是如果從接收過來的Map中獲取Header是不能忽略大小寫的。稍微不注意,我們就很容易認(rèn)為Header在任何情況下,我們知道,對于"@RequestHeader("MyHeader")StringmyHeader"的定義,Spring用的是RequestHeaderMethodArgumentResolver來做解析。解析的方法參考代代protectedObjectresolveName(Stringname,MethodParameterparameter,String[]headerValues=if(headerValues!=null)return(headerValues.length==1?headerValues[0]: elsereturn 9從上述方法的關(guān)鍵調(diào)用"requtrValues(name)r的最根本方法,即org.apache.tomcat.ut.httpumerator#fext:代代privatevoidfindNext()for(;pos<size;pos++)MessageBytesn1=headers.getName(posif(n1.equalsIgnoreCase(name))next=headers.getValue(pos 99}}在上述方法中,name即為查詢的HeaderMapHeader,MapHeader1存取Map的Header1HeaderkeywebRequest.getHeaderNames()的返回結(jié)果。而這個方法的執(zhí)行過程參考23for(;pos<size;pos++)4next=headers.getName(pos5for(intj=0;j<pos;j++)6if(headers.getName(jnext))7//89}}if(next!=null)//it'snota}}//nexttimefindNextiscalleditwilltry//next20這里,返回結(jié)果并沒有針對Header從Map中獲取的HeaderLinkedHashMapLinkedHashMapgetMap中獲取Header代代 (path="/hi2",method=publicStringhi2(@RequestHeader("MyHeader")StringmyHeader,@RequestHeaderreturnmyHeader+"comparewith:"+4HTTPHeaders取Header是否可以忽略大小寫呢?代代publicHttpHeaders()this(CollectionUtils.toMultiValueMap(newLinkedCaseInsensitiveMap<>(8,3LinkedCaseInsensitiveMapLinkedHashMap。代代 (path="/hi2",method=publicStringhi2(@RequestHeader("MyHeader")StringmyHeader,@RequestHeaderreturnmyHeader+"comparewith:"+4myheadervaluecomparewith:通過這個案例,我們可以看出:HTTP案例3:試圖在Controller中隨意自定義CONTENT_TYPE和開頭我們提到的Header和URL不同,Header可以出現(xiàn)在返回中。正因為如此,一些HeaderSpringBootTomcat開發(fā)中,存在下面這樣一段代碼去設(shè)置兩個Header,其中一個是常用的CONTENT_TYPE,另外一個是自定義的,命名為myHeader。代代123456 (path="/hi3",method=RequestMethod.GET)publicStringhi3(HttpServletResponsehttpServletResponse){httpServletResponse.addHeader("myheader",return運行程序測試下( ),我們會得到如下結(jié)果HTTP/1.1myheader:Content-Type:text/in;charset=UTF-8Content-Length:2Date:Wed,17Mar202108:59:56Keep-Alive:timeout=60Connection:keep-alive可以看到myHeader設(shè)置成功了,但是Content-TypeSpringBootTomcatHeader代123代12345privatevoidaddHeader(Stringname,Stringvalue,Charsetcharset)charif(cc=='C'||cc=='c')//判斷是不是Content-Type這個Header作為header添加到6if(checkSpecialHeader(name,789}11getCoyoteResponse().addHeader(name,代1privateboolean代1privatebooleancheckSpecialHeader(Stringname,Stringvalue)567}return}2(name.equalsIgnoreCase("Content-Type"))34return最終我們獲取到的Response從上圖可以看出,Headers里并沒有Content-Type,而我們設(shè)置的Content-Type已經(jīng)作為coyoteResponse成員的值了。當(dāng)然也不意味著后面一定不會返回,我們可以繼續(xù)跟代代123456789publicvoidhandleReturnValue(@NullableObjectreturnValue,MethodParameterreModelAndViewContainermavContainer,NativeWebRequestwebRequest)throwsIOException,HttpMediaTypeNotAcceptableException,ServletServerHttpRequestinputMessage=createInputMessage(webRequest);ServletServerHttpResponseoutputMessage=createOutputMessage(webRequest);writeWithMessageConverters(returnValue,returnType,inputMessage,outputMes}而在上述代碼的調(diào)用中,writeWithMessageConverters決定用哪一種MediaType代代123456789//決策返回值是何種MediaTypeselectedMediaType=MediaTypecontentType=outputMessage.getHeaders().getContentType();booleanisContentTypePreset=contentType!=null&&contentType.isConcrete//如果header中有contentType,則用其作為選擇的selectedMediaTypeif(isContentTypePreset){selectedMediaType=contentType;}else{HttpServletRequestrequest=inputMessage.getServletRequest();List<MediaType>acceptableTypes=getAcceptableMediaTypes(request);List<MediaType>producibleTypes=getProducibleMediaTypes(request,valueList<MediaType>mediaTypesToUse=newArrayList<>();for(MediaTyperequestedType:acceptableTypes){for(MediaTypeproducibleType:producibleTypes)ifpatibleWith(producibleType))}}}for(MediaTypemediaType:mediaTypesToUse)(mediaType.isConcrete()){selectedMediaType=mediaType;}}這里我解釋一下,上述代碼是先根據(jù)是否具有Content-Type頭來決定返回的MediaType,通過前面的分析它是一種特殊的Header,在Controller層并沒有被添加到Header中去,所以在這里只能根據(jù)返回的類型、請求的Accept等信息協(xié)商出最終用哪種MediaType。實際上這里最終使用的是MediaType#TEXT_IN。這里還需要補充說明下,沒有選擇JSON是因為在都支持的情況下,TEXT_IN默認(rèn)優(yōu)先級更高,參考代碼WebMvcConfigurationSupport#addDefaultHttpMessageConverters是有優(yōu)先順序的,所以用上述代碼中的getProducibleMediaTypes()遍歷Converter來收集可用MediaType也是有順序的。決定完代代123456789for(HttpMessageConverter<?>converter:this.messageConverters)GenericHttpMessageConvertergenericConverter=(converterinstanceof(GenericHttpMessageConverter<?>)converter:null);if(genericConverter!=null((GenericHttpMessageConverter)converter).canWrite(targetType,converter.canWrite(valueType,selectedMediaType)){if(body!=null)if(genericConverter!=null)genericConverter.write(body,targetType,}else((HttpMessageConverter)converter).write(body,}}}}如代碼所示,即結(jié)合targetType(String)、valueType(String)、Converter。常見候選Converter可以參考下圖:最終,本案例選擇的是StringHttpMessageConverter,在最終調(diào)用父類方法HttpMessageConverter#writeContent-Type。具體 代代protectedvoidaddDefaultHeaders(HttpHeadersheaders,Tt,@Nullableif(headers.getContentType()==null)MediaTypecontentTypeToUse=if(contentType==null||contentType.isWildcardType()||contentTypeToUse= elseif(MediaType.APPLICATION_OCTET_STREAM.equals(contentType))MediaTypemediaType=contentTypeToUse=(mediaType!=null?mediaType: if(contentTypeToUse!=null) if(contentTypeToUse.getCharset()==null) CharsetdefaultCharset= if(defaultCharset!=null) contentTypeToUse=newMediaType(contentTypeToUse, 23結(jié)合案例,參考代碼,我們可以看出,我們使用的是MediaType#TEXT_IN作為Content-Type的Header,畢竟之前我們添加Content-Type這個Header并沒有成 通過案例分析,我們可以總結(jié)出,雖然我們在Controller設(shè)置了Content-Type,但是它Header,所以SpringBootTomcat功,最終返回的Content-Type是根據(jù)實際的返回值及類型等多個因素來決定的。針對這個問題,如果想設(shè)置成功,我們就必須讓其真正的返回就是JSON類型,這樣才能剛好生效。而且從上面的分析也可以看出,返回符合預(yù)期也并非是在Controller設(shè)置的功修改請求中的Accept代代AcceptMediaTypeAccept11代(path="/hi3",method=RequestMethod.GET,produces=

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

最新文檔

評論

0/150

提交評論