摘要:例如,服務(wù)端不支持這種,應(yīng)該返回。而當(dāng)使用或者其他等價(jià)方式進(jìn)行配置時(shí),會(huì)把添加在最前面,優(yōu)先級(jí)最高。好了,到此就基本上說完了整個(gè)的匹配規(guī)則或者叫選擇過程。自己能力不是最大,卻大包大攬承擔(dān)最大責(zé)任,處理不了還返回,是甩鍋客戶端的行為。
以下內(nèi)容,如有問題,煩請(qǐng)指出,謝謝!
SpringMVC啟動(dòng)時(shí)會(huì)自動(dòng)配置一些HttpMessageConverter,接收到http請(qǐng)求時(shí),從這些Converters中選擇一個(gè)符合條件的來進(jìn)行Http序列化/反序列化。在不覆蓋默認(rèn)的HttpMessageConverters的情況下,我們添加的Converter可能會(huì)與默認(rèn)的產(chǎn)生沖突,在某些場景中出現(xiàn)不符合預(yù)期的情況。
在上一篇文章的末尾已經(jīng)列舉了一個(gè)jsonConverter沖突的情況:添加一個(gè)最低優(yōu)先級(jí)的FastJsonConverter后會(huì)有兩個(gè)(實(shí)際上三個(gè),有兩個(gè)jackson的)jsonConverter,直接使用瀏覽器訪問接口時(shí)使用的卻是低優(yōu)先級(jí)的FastJsonConverter來進(jìn)行序列化操作。
為了解決converters之間的沖突,或者直接叫優(yōu)先級(jí)問題,需要弄懂SpringMVC是如何選擇一個(gè)HttpMessageMessagerConverter來進(jìn)行Http序列化/反序列化的。這篇文章主要就根據(jù)相關(guān)的代碼來講解SpringMVC的這個(gè)內(nèi)部流程,這塊的邏輯比較清晰,貼貼代碼就基本上都明白了。
首先需要了解一些HTTP的基本知識(shí)(不是強(qiáng)制的而是一種建議與約定):
1、決定resp.body的Content-Type的第一要素是對(duì)應(yīng)的req.headers.Accept屬性的值,又叫做MediaType。如果服務(wù)端支持這個(gè)Accept,那么應(yīng)該按照這個(gè)Accept來確定返回resp.body對(duì)應(yīng)的格式,同時(shí)把resp.headers.Content-Type設(shè)置成自己支持的符合那個(gè)Accept的MediaType。服務(wù)端不支持Accept指定的任何MediaType時(shí),應(yīng)該返回錯(cuò)誤406 Not Acceptable.
例如:req.headers.Accept = text/html,服務(wù)端支持的話應(yīng)該讓resp.headers.Content-Type = text/html,并且resp.body按照html格式返回。
例如:req.headers.Accept = text/asdfg,服務(wù)端不支持這種MediaType,應(yīng)該返回406 Not Acceptable。
2、如果Accept指定了多個(gè)MediaType,并且服務(wù)端也支持多個(gè)MediaType,那么Accept應(yīng)該同時(shí)指定各個(gè)MediaType的QualityValue,也就是q值,服務(wù)端根據(jù)q值的大小來決定這幾個(gè)MediaType類型的優(yōu)先級(jí),一般是大的優(yōu)先。q值不指定時(shí),默認(rèn)視為q=1.
Chrome的默認(rèn)請(qǐng)求的Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,表示服務(wù)端在支持的情況下應(yīng)該優(yōu)先返回text/html,其次是application/xhtml+xml.
前面幾個(gè)都不支持時(shí),服務(wù)器可以自行處理 */*,返回一種服務(wù)器自己支持的格式。
3、一個(gè)HTTP請(qǐng)求沒有指定Accept,默認(rèn)視為指定 Accept: */*;沒有指定Content-Type,默認(rèn)視為 null,就是沒有。當(dāng)然,服務(wù)端可以根據(jù)自己的需要改變默認(rèn)值。
4、Content-Type必須是具體確定的類型,不能包含 *.
SpringMvc基本遵循上面這幾點(diǎn)。
然后是啟動(dòng)時(shí)默認(rèn)加載的Converter。在mvc啟動(dòng)時(shí)默認(rèn)會(huì)加載下面的幾種HttpMessageConverter,相關(guān)代碼在 org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport中的addDefaultHttpMessageConverters方法中,代碼如下。
protected final void addDefaultHttpMessageConverters(List> messageConverters) { StringHttpMessageConverter stringConverter = new StringHttpMessageConverter(); stringConverter.setWriteAcceptCharset(false); messageConverters.add(new ByteArrayHttpMessageConverter()); messageConverters.add(stringConverter); messageConverters.add(new ResourceHttpMessageConverter()); messageConverters.add(new SourceHttpMessageConverter
這段代碼后面還有兩個(gè)別的處理,一次是將Jaxb放在list最后面,第二次是將一個(gè)StringConverter和一個(gè)JacksonConverter添加到list中,所以打印出converter信息中這兩個(gè)有重復(fù)的(第二次的那兩個(gè)來自springboot-autoconfigure.web,重復(fù)了不影響后面的流程)。
接著我們?cè)谧约旱腗VC配置類覆蓋extendMessageConverters方法,使用converter.add(xxx)加上上次自定義Java序列化的那個(gè)的和FastJson的(把自己添加的放在優(yōu)先級(jí)低的位置)。最后的converters按順序展示如下(下面的已經(jīng)去掉重復(fù)的StringHttpMessageConverter和MappingJackson2HttpMessageConverter,后續(xù)的相應(yīng)MediaType也去重)
類名 | 支持的JavaType | 支持的MediaType |
---|---|---|
ByteArrayHttpMessageConverter | byte[] | application/octet-stream, */* |
StringHttpMessageConverter | String | text/plain, */* |
ResourceHttpMessageConverter | Resource | */* |
SourceHttpMessageConverter | Source | application/xml, text/xml, application/*+xml |
AllEncompassingFormHttpMessageConverter | Map |
application/x-www-form-urlencoded, multipart/form-data |
MappingJackson2HttpMessageConverter | Object | application/json, application/*+json |
Jaxb2RootElementHttpMessageConverter | Object | application/xml, text/xml, application/*+xml |
JavaSerializationConverter | Serializable | x-java-serialization;charset=UTF-8 |
FastJsonHttpMessageConverter | Object | */* |
這里只列出重要的兩個(gè)屬性,詳細(xì)的可以去看org.springframework.http.converter包中的代碼。
另外,基本類型都視為對(duì)應(yīng)的包裝類的類型來算。還有,基本類型的json序列化就只有字面值,沒有key,不屬于規(guī)范的json序列化,但是基本上所有json框架都支持基本類型直接序列化。
好了,開始說converter的選擇邏輯。主要的代碼在org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor這個(gè)類以及它的父類中,這里根據(jù)我個(gè)人的理解簡明地說一下。
先說下寫操作的流程,也就是Http序列化。基本都集中在 writeWithMessageConverters 這個(gè)方法中。我們先以Accept = default(*/*)請(qǐng)求 http://localhost:8080/users/1 為例。
第一步是取出請(qǐng)求的MediaType以及我們能夠返回的MediaType,相關(guān)代碼如下:
HttpServletRequest request = inputMessage.getServletRequest(); ListrequestedMediaTypes = getAcceptableMediaTypes(request); List producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType); if (outputValue != null && producibleMediaTypes.isEmpty()) { throw new IllegalArgumentException("No converter found for return value of type: " + valueType); }
getAcceptableMediaTypes
: 大致思路就是從Accept中獲取MediaType,這里為 [*/*] 。
getProducibleMediaTypes
: 大致思路就是根據(jù)是否支持controller方法的返回類型JavaType(這里是User類,實(shí)現(xiàn)了Serializable),一個(gè)個(gè)遍歷檢查配置上的所有Converter,查看他們是否支持這種Java類型的轉(zhuǎn)換,這里返回值為這里為 [application/json, application/*+json, application/json, application/x-java-serialization;charset=UTF-8, */*]。
按照J(rèn)ava類型規(guī)則這里應(yīng)該有Jaxb2RootElementHttpMessageConverter,但是查看其源碼就知道它還需要滿足@XmlRootElement注解這個(gè)條件才行,所以這里只有上面四個(gè)MediaType,對(duì)應(yīng)的三個(gè)Converter分別是Jackson、自定義的、FastJson.
第二步,把Accpet指定的MediaType具體化,意思就是req可以指定 * 這種通配符,但是服務(wù)端不應(yīng)該返回帶 * 的Content-Type,代碼如下:
SetcompatibleMediaTypes = new LinkedHashSet (); for (MediaType requestedType : requestedMediaTypes) { for (MediaType producibleType : producibleMediaTypes) { if (requestedType.isCompatibleWith(producibleType)) { compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType)); } } } if (compatibleMediaTypes.isEmpty()) { if (outputValue != null) { throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes); } return; } List mediaTypes = new ArrayList (compatibleMediaTypes); MediaType.sortBySpecificityAndQuality(mediaTypes); MediaType selectedMediaType = null; for (MediaType mediaType : mediaTypes) { if (mediaType.isConcrete()) { selectedMediaType = mediaType; break; } else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) { selectedMediaType = MediaType.APPLICATION_OCTET_STREAM; break; } }
第一個(gè)for循環(huán),尋找出更具體的MediaType,isCompatibleWith方法判斷是否兼容,getMostSpecificMediaType方法取二者中更具體的那個(gè)。具體的判斷邏輯在MediaType類中,這里就不細(xì)說了,可以把更具體理解為找出 requestedMediaType 有 instanceof 關(guān)系的 producibleMediaType。因?yàn)?*/* 類似于 Object 所以這里一個(gè)都篩不掉,compatibleMediaTypes最后還是那四個(gè)MediaType。
后兩行代碼是排序,按照 q值 和 具體程度 來排序。因?yàn)槲覀儧]有指定q值,所以都是q=1。根據(jù)具體程度排序,帶 * 的會(huì)排到后面。注意那個(gè)LinkedHashSet,先來的會(huì)排在前面,加上前面的都是list迭代,所以最后的順序?yàn)閇application/json, application/x-java-serialization;charset=UTF-8, application/*+json, */*]。
第二個(gè)是默認(rèn)值處理,application/json 是一個(gè)具體的類型,不用再處理,所以最后的produce = application/json。
第三步,選擇一個(gè)能處理最后的produce的Converter,Jackson和FastJson都能處理,根據(jù)添加順序,此時(shí)選擇的是Jackson,也就是Jackson的優(yōu)先級(jí)更高。
if (selectedMediaType != null) { selectedMediaType = selectedMediaType.removeQualityValue(); for (HttpMessageConverter> messageConverter : this.messageConverters) { if (messageConverter instanceof GenericHttpMessageConverter) { if (((GenericHttpMessageConverter) messageConverter).canWrite( declaredType, valueType, selectedMediaType)) { outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType, (Class extends HttpMessageConverter>>) messageConverter.getClass(), inputMessage, outputMessage); if (outputValue != null) { addContentDispositionHeader(inputMessage, outputMessage); ((GenericHttpMessageConverter) messageConverter).write( outputValue, declaredType, selectedMediaType, outputMessage); if (logger.isDebugEnabled()) { logger.debug("Written [" + outputValue + "] as "" + selectedMediaType + "" using [" + messageConverter + "]"); } } return; } } else if (messageConverter.canWrite(valueType, selectedMediaType)) { outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType, (Class extends HttpMessageConverter>>) messageConverter.getClass(), inputMessage, outputMessage); if (outputValue != null) { addContentDispositionHeader(inputMessage, outputMessage); ((HttpMessageConverter) messageConverter).write(outputValue, selectedMediaType, outputMessage); if (logger.isDebugEnabled()) { logger.debug("Written [" + outputValue + "] as "" + selectedMediaType + "" using [" + messageConverter + "]"); } } return; } } }
上面的流程解釋了為什么通過在converters末尾添加FastJsonConverter時(shí),F(xiàn)iddler的默認(rèn)請(qǐng)求(不帶Accept或者Accept: */*),使用的是Jackson序列化,序列化了createTime字段,并且返回的 Content-Type 為application/json。
但是使用瀏覽器直接請(qǐng)求時(shí),Chrome的默認(rèn)請(qǐng)求的Accept為text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8.
根據(jù)上面的邏輯,最后的produce = text/html.
最后選擇時(shí),只有FastJson(MediaType對(duì)應(yīng)的 */*)這唯一一個(gè)converter能夠進(jìn)行處理,所以用的是FastJson序列化,沒有序列化createTime字段。返回的 Content-Type 為 text/html,但是實(shí)際格式是json的。
而當(dāng)使用 converters.add(0, fastJsonConverter) (或者其他等價(jià)方式)進(jìn)行配置時(shí),會(huì)把FastJsonConverter添加在最前面,優(yōu)先級(jí)最高。因?yàn)镕astJsonConverter的MediaType是 */*,所以它會(huì)在前面包攬所有請(qǐng)求的Http序列化和反序列化,就算它們不是json,也說了自己不是json、不要返回json,它還是一意孤行地當(dāng)成json處理。
此時(shí)不論Accept是什么類型,返回的實(shí)際上都是FastJson序列化的json格式,但是返回的Content-Type卻還是別人
Accept 的那種類型,不一定是application/json這類json標(biāo)識(shí)(掛羊頭賣狗肉)。
下面再說下讀取的流程,也就是反序列化流程,主流程在父類的 readWithMessageConverters 方法中,代碼如下:
@SuppressWarnings("unchecked") protectedObject readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException { MediaType contentType; boolean noContentType = false; try { contentType = inputMessage.getHeaders().getContentType(); } catch (InvalidMediaTypeException ex) { throw new HttpMediaTypeNotSupportedException(ex.getMessage()); } if (contentType == null) { noContentType = true; contentType = MediaType.APPLICATION_OCTET_STREAM; } Class> contextClass = (parameter != null ? parameter.getContainingClass() : null); Class targetClass = (targetType instanceof Class ? (Class ) targetType : null); if (targetClass == null) { ResolvableType resolvableType = (parameter != null ? ResolvableType.forMethodParameter(parameter) : ResolvableType.forType(targetType)); targetClass = (Class ) resolvableType.resolve(); } HttpMethod httpMethod = ((HttpRequest) inputMessage).getMethod(); Object body = NO_VALUE; try { inputMessage = new EmptyBodyCheckingHttpInputMessage(inputMessage); for (HttpMessageConverter> converter : this.messageConverters) { Class > converterType = (Class >) converter.getClass(); if (converter instanceof GenericHttpMessageConverter) { GenericHttpMessageConverter> genericConverter = (GenericHttpMessageConverter>) converter; if (genericConverter.canRead(targetType, contextClass, contentType)) { if (logger.isDebugEnabled()) { logger.debug("Read [" + targetType + "] as "" + contentType + "" with [" + converter + "]"); } if (inputMessage.getBody() != null) { inputMessage = getAdvice().beforeBodyRead(inputMessage, parameter, targetType, converterType); body = genericConverter.read(targetType, contextClass, inputMessage); body = getAdvice().afterBodyRead(body, inputMessage, parameter, targetType, converterType); } else { body = getAdvice().handleEmptyBody(null, inputMessage, parameter, targetType, converterType); } break; } } else if (targetClass != null) { if (converter.canRead(targetClass, contentType)) { if (logger.isDebugEnabled()) { logger.debug("Read [" + targetType + "] as "" + contentType + "" with [" + converter + "]"); } if (inputMessage.getBody() != null) { inputMessage = getAdvice().beforeBodyRead(inputMessage, parameter, targetType, converterType); body = ((HttpMessageConverter ) converter).read(targetClass, inputMessage); body = getAdvice().afterBodyRead(body, inputMessage, parameter, targetType, converterType); } else { body = getAdvice().handleEmptyBody(null, inputMessage, parameter, targetType, converterType); } break; } } } } catch (IOException ex) { throw new HttpMessageNotReadableException("I/O error while reading input message", ex); } if (body == NO_VALUE) { if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) || (noContentType && inputMessage.getBody() == null)) { return null; } throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes); } return body; }
因?yàn)樽x取時(shí)的ContentType必定是一個(gè)具體類型(帶有 * 號(hào)會(huì)拋出異常 java.lang.IllegalArgumentException: "Content-Type" cannot contain wildcard subtype "*"),所以步驟少了一些,匹配前就一個(gè)默認(rèn)值處理。
默認(rèn)的請(qǐng)求不帶Content-Type,會(huì)進(jìn)行默認(rèn)值處理,最后 contentType = application/octet-stream,只有FastJsonConverter(MediaType對(duì)應(yīng)的 */*)這唯一一個(gè)converter能夠進(jìn)行處理,所以就沒有反序列化createTime字段,打印信息user.toString()中createTime=null。
但是當(dāng)指定Content-Type: application/json時(shí),contentType = application/json,Jackson和FastJson都能處理,按照順序,輪到Jackson反序列化,所以就反序列化了createTime字段,打印信息user.toString()中createTime不為null。
改成使用 converters.add(0, fastJsonConverter) (或者其他等價(jià)方式)進(jìn)行配置時(shí),會(huì)把FastJsonConverter添加在最前面,順序優(yōu)先級(jí)比Jackson高,指定Content-Type: application/json時(shí)使用的就是FastJson來進(jìn)行反序列化。
但是跟上面說的那樣,因?yàn)?*/* 的原因,此時(shí)不論Content-Type是什么類型,都會(huì)是FastJsonConverter來進(jìn)行反序列化操作。不過,F(xiàn)astJson只是個(gè)json框架,只能處理json,別的格式會(huì)拋出異常,并且還返回 HTTP 400 告訴客戶端你的請(qǐng)求報(bào)文格式不對(duì)(沒有金剛鉆,非要攬瓷器活,明明是自己的錯(cuò),還要說是客戶端的錯(cuò))。
好了,到此就基本上說完了整個(gè)HttpMessageConverter的匹配規(guī)則(或者叫選擇過程)。這次沒有新增代碼,也沒有演示,想要自己演示觀察的,可以在上一篇文章相關(guān)的代碼基礎(chǔ)上進(jìn)行,如下:
https://gitee.com/page12/stud...
https://github.com/page12/stu...
最后再次吐槽下FastJsonHttpMessageConverter,作為非springmvc自帶的組件,默認(rèn)設(shè)置 */* 這種MediaType,是非常不好的。上面也說了,存在掛羊頭賣狗肉、名實(shí)不副的行為,在REST已經(jīng)重新引起人們對(duì)HTTP原生規(guī)范的重視的今天,這是一個(gè)很不好的做法。自己能力不是最大,卻大包大攬承擔(dān)最大責(zé)任,處理不了還返回 HTTP 400,是甩鍋客戶端的行為。阿里作為國內(nèi)第一大開源陣營,其代碼設(shè)計(jì)、質(zhì)量,以及開源奉獻(xiàn)精神還是要進(jìn)一步提升啊。
自己寫代碼也要注意?。捍a中有順序遍歷匹配這種邏輯,或者叫責(zé)任鏈模式時(shí),功能越具體的節(jié)點(diǎn)越是應(yīng)該放在前面,功能最廣最泛的節(jié)點(diǎn)應(yīng)該放在最后面;同時(shí)要按功能分配責(zé)任,千萬不要給功能單一的節(jié)點(diǎn)最大的責(zé)任(FastJsonConverter的功能單一,卻默認(rèn)分配了個(gè)最大的責(zé)任 MediaType = */*)。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/68124.html
摘要:序列化反序列化主要體現(xiàn)在程序這個(gè)過程中,包括網(wǎng)絡(luò)和磁盤。如果是開發(fā)應(yīng)用,一般這兩個(gè)注解對(duì)應(yīng)的就是序列化和反序列化的操作。協(xié)議的處理過程,字節(jié)流內(nèi)部對(duì)象,就涉及這兩種序列化。進(jìn)行第二步操作,也就是序列化和反序列化的核心是。 以下內(nèi)容,如有問題,煩請(qǐng)指出,謝謝! 對(duì)象的序列化/反序列化大家應(yīng)該都比較熟悉:序列化就是將object轉(zhuǎn)化為可以傳輸?shù)亩M(jìn)制,反序列化就是將二進(jìn)制轉(zhuǎn)化為程序內(nèi)部的...
摘要:簡介注解用于修飾的方法,根據(jù)的的內(nèi)容,通過適當(dāng)?shù)霓D(zhuǎn)換為客戶端需要格式的數(shù)據(jù)并且寫入到的數(shù)據(jù)區(qū),從而不通過視圖解析器直接將數(shù)據(jù)響應(yīng)給客戶端。并且這些解析器都實(shí)現(xiàn)了接口,在接口中有四個(gè)最為主要的接口方法。 SpringMVC 細(xì)節(jié)方面的東西很多,所以在這里做一篇簡單的 SpringMVC 的筆記記錄,方便以后查看。 Spring MVC是當(dāng)前最優(yōu)秀的MVC框架,自從Spring 2.5版本...
摘要:環(huán)境要求使用純來搭建環(huán)境,要求的版本必須在以上。即視圖解析器解析文件上傳等等,如果都不需要配置的話,這樣就可以了??梢詫⒁粋€(gè)字符串轉(zhuǎn)為對(duì)象,也可以將一個(gè)對(duì)象轉(zhuǎn)為字符串,實(shí)際上它的底層還是依賴于具體的庫。中,默認(rèn)提供了和的,分別是和。 在 Spring Boot 項(xiàng)目中,正常來說是不存在 XML 配置,這是因?yàn)?Spring Boot 不推薦使用 XML ,注意,并非不支持,Spring...
摘要:關(guān)鍵注解的關(guān)鍵注解主要有其中主要是用于標(biāo)記該類是一個(gè)控制器,用于指示的哪一個(gè)類或方法來處理請(qǐng)求動(dòng)作,即用于標(biāo)識(shí)具體的處理器。默認(rèn)已經(jīng)裝配了作為組件的實(shí)現(xiàn)類,而由使用,將請(qǐng)求信息轉(zhuǎn)換為對(duì)象。 關(guān)鍵注解 springmvc的關(guān)鍵注解主要有@Controller/@RequestMapping/@RequestParam/@PathVariable/@RequestHeader/@Cooki...
摘要:分發(fā)處理器將會(huì)掃描使用了該注解的類的方法,并檢測該方法是否使用了注解。的作用相當(dāng)于,只不過按照自動(dòng)注入。作用該注解用于將的方法返回的對(duì)象,通過適當(dāng)?shù)霓D(zhuǎn)換為指定格式后,寫入到對(duì)象的數(shù)據(jù)區(qū)。用于注解層,在類上面注解。 原文地址 Controller 在SpringMVC中,控制器Controller負(fù)責(zé)處理由DispatcherServlet分發(fā)的請(qǐng)求,它把用戶請(qǐng)求的數(shù)據(jù)經(jīng)過業(yè)務(wù)處理層處理...
閱讀 2386·2021-11-15 11:37
閱讀 2638·2021-09-23 11:21
閱讀 2967·2021-09-07 10:11
閱讀 3175·2019-08-30 15:53
閱讀 2835·2019-08-29 15:13
閱讀 1618·2019-08-26 13:57
閱讀 1112·2019-08-26 12:23
閱讀 2451·2019-08-26 11:51