javascript
一篇文章教你弄懂SpringMvc中的HttpMessageConverter
文章目錄
- 一、HttpMessageConverter介紹
- 二、自定義HttpMessageConverter
寫在前面:
我是「境里婆娑」。我還是從前那個少年,沒有一絲絲改變,時間只不過是考驗,種在心中信念絲毫未減,眼前這個少年,還是最初那張臉,面前再多艱險不退卻。
寫博客的目的就是分享給大家一起學習交流,如果您對 Java感興趣,可以關注我,我們一起學習。
導語:相信使用過Spring的開發人員都用過@RequestBody、@ResponseBody注解,可以直接將輸入解析成Json、將輸出解析成Json,但HTTP 請求和響應是基于文本的,意味著瀏覽器和服務器通過交換原始文本進行通信,而這里其實就是HttpMessageConverter發揮著作用。
一、HttpMessageConverter介紹
Http請求響應報文其實都是字符串,當請求報文到java程序會被封裝為一個ServletInputStream流,開發人員再讀取報文,響應報文則通過ServletOutputStream流,來輸出響應報文。
從流中只能讀取到原始的字符串報文,同樣輸出流也是。那么在報文到達SpringMVC / SpringBoot出去,都存在一個字符串到java對象的轉化問題。這一過程,在SpringMVC / SpringBoot中,是通過HttpMessageConverter來解決的。
HttpMessageConverter接口提供了5個方法:
- canRead:判斷該轉換器是否能將請求內容轉換成Java對象
- canWrite:判斷該轉換器是否可以將Java對象轉換成返回內容
- getSupportedMediaTypes:獲得該轉換器支持的MediaType類型
- read:讀取請求內容并轉換成Java對象
- write:將Java對象轉換后寫入返回內容
其中read和write方法的參數分別有有HttpInputMessage和HttpOutputMessage對象,這兩個對象分別代表著一次Http通訊中的請求和響應部分,可以通過getBody方法獲得對應的輸入流和輸出流。
其中read和write方法的參數分別有有HttpInputMessage和HttpOutputMessage對象,這兩個對象分別代表著一次Http通訊中的請求和響應部分,可以通過getBody方法獲得對應的輸入流和輸出流。
缺省配置
平常我們在發送http請求時,沒有配置任何 MessageConverter,但是數據前后傳遞依舊好用,是因為 SpringMVC 啟動時會自動配置一些HttpMessageConverter,在 WebMvcConfigurationSupport 類中添加了缺省 MessageConverter:
HttpMessageConverter匹配過程
當使用 @RequestBody和 @ResponseBody注解時,RequestMappingHandlerAdapter就使用它們來進行讀取或者寫入相應格式的數據。
@RequestBody 據Request對象header部分的Content-Type類型,逐一匹配合適的HttpMessageConverter來讀取數據。 底層調用的是RequestResponseBodyMethodProcessor readWithMessageConverters方法
protected <T> Object 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.getContainingClass();Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);if (targetClass == null) {ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);targetClass = (Class<T>) resolvableType.resolve();}HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);Object body = NO_VALUE;EmptyBodyCheckingHttpInputMessage message;try {message = new EmptyBodyCheckingHttpInputMessage(inputMessage);for (HttpMessageConverter<?> converter : this.messageConverters) {Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();GenericHttpMessageConverter<?> genericConverter =(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :(targetClass != null && converter.canRead(targetClass, contentType))) {if (message.hasBody()) {HttpInputMessage msgToUse =getAdvice().beforeBodyRead(message, parameter, targetType, converterType);body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);}else {body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);}break;}}}catch (IOException ex) {throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);}if (body == NO_VALUE) {if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||(noContentType && !message.hasBody())) {return null;}throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);}MediaType selectedContentType = contentType;Object theBody = body;LogFormatUtils.traceDebug(logger, traceOn -> {String formatted = LogFormatUtils.formatValue(theBody, !traceOn);return "Read \"" + selectedContentType + "\" to [" + formatted + "]";});return body;}@ResponseBody 底層調用的AbstractMessageConverterMethodProcessor
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {Object body;Class<?> valueType;Type targetType;if (value instanceof CharSequence) {body = value.toString();valueType = String.class;targetType = String.class;}else {body = value;valueType = getReturnValueType(body, returnType);targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());}if (isResourceType(value, returnType)) {outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes");if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null &&outputMessage.getServletResponse().getStatus() == 200) {Resource resource = (Resource) value;try {List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value());body = HttpRange.toResourceRegions(httpRanges, resource);valueType = body.getClass();targetType = RESOURCE_REGION_LIST_TYPE;}catch (IllegalArgumentException ex) {outputMessage.getHeaders().set(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength());outputMessage.getServletResponse().setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value());}}}MediaType selectedMediaType = null;MediaType contentType = outputMessage.getHeaders().getContentType();if (contentType != null && contentType.isConcrete()) {if (logger.isDebugEnabled()) {logger.debug("Found 'Content-Type:" + contentType + "' in response");}selectedMediaType = contentType;}else {HttpServletRequest request = inputMessage.getServletRequest();List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);if (body != null && producibleTypes.isEmpty()) {throw new HttpMessageNotWritableException("No converter found for return value of type: " + valueType);}List<MediaType> mediaTypesToUse = new ArrayList<>();for (MediaType requestedType : acceptableTypes) {for (MediaType producibleType : producibleTypes) {if (requestedType.isCompatibleWith(producibleType)) {mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));}}}if (mediaTypesToUse.isEmpty()) {if (body != null) {throw new HttpMediaTypeNotAcceptableException(producibleTypes);}if (logger.isDebugEnabled()) {logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);}return;}MediaType.sortBySpecificityAndQuality(mediaTypesToUse);for (MediaType mediaType : mediaTypesToUse) {if (mediaType.isConcrete()) {selectedMediaType = mediaType;break;}else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;break;}}if (logger.isDebugEnabled()) {logger.debug("Using '" + selectedMediaType + "', given " +acceptableTypes + " and supported " + producibleTypes);}}if (selectedMediaType != null) {selectedMediaType = selectedMediaType.removeQualityValue();for (HttpMessageConverter<?> converter : this.messageConverters) {GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?(GenericHttpMessageConverter<?>) converter : null);if (genericConverter != null ?((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :converter.canWrite(valueType, selectedMediaType)) {body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,(Class<? extends HttpMessageConverter<?>>) converter.getClass(),inputMessage, outputMessage);if (body != null) {Object theBody = body;LogFormatUtils.traceDebug(logger, traceOn ->"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");addContentDispositionHeader(inputMessage, outputMessage);if (genericConverter != null) {genericConverter.write(body, targetType, selectedMediaType, outputMessage);}else {((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);}}else {if (logger.isDebugEnabled()) {logger.debug("Nothing to write: null body");}}return;}}}if (body != null) {throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);}}二、自定義HttpMessageConverter
一般springboot會有默認的消息管理器,StringHttpMessageConverter(字符串轉換器),FastJsonHttpMessageConverter ,MappingJackson2HttpMessageConverter等等。
今天我要說的是如何自定義消息管理器-FastJsonHttpMessageConverter
1.引用fastjson依賴
2. 創建配置文件JsonHttpMessageConfiguration.java
@Configuration public class MyJsonHttpMessageConfiguration {@Bean@ConditionalOnMissingBean(FastJsonHttpMessageConverter.class)public FastJsonHttpMessageConverter fastJsonHttpMessageConverter() {//1.需要定義一個convert轉換消息的對象FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();//2.添加fastJson的配置信息;FastJsonConfig config = new FastJsonConfig();//添加配置-config.setSerializerFeatures() 方法中可以添加多個配置(以,分開)config.setSerializerFeatures(SerializerFeature.WriteMapNullValue,SerializerFeature.DisableCircularReferenceDetect);//添加配置-config.setSerializerFeatures() 方法中可以添加多個配置(以,分開)config.setSerializerFeatures(SerializerFeature.WriteMapNullValue,SerializerFeature.DisableCircularReferenceDetect);fastJsonHttpMessageConverter.setFastJsonConfig(config);fastJsonHttpMessageConverter.setSupportedMediaTypes(getSupportedMediaTypes());fastJsonHttpMessageConverter.setDefaultCharset(Charset.forName("UTF-8"));return fastJsonHttpMessageConverter;}/*** 配置全局支持的媒體類型*/private List<MediaType> getSupportedMediaTypes() {List<MediaType> supportedMediaTypes = new ArrayList<>();supportedMediaTypes.add(MediaType.APPLICATION_JSON);supportedMediaTypes.add(MediaType.APPLICATION_ATOM_XML);supportedMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED);supportedMediaTypes.add(MediaType.APPLICATION_OCTET_STREAM);supportedMediaTypes.add(MediaType.APPLICATION_PDF);supportedMediaTypes.add(MediaType.APPLICATION_RSS_XML);supportedMediaTypes.add(MediaType.APPLICATION_XHTML_XML);supportedMediaTypes.add(MediaType.APPLICATION_XML);supportedMediaTypes.add(MediaType.IMAGE_GIF);supportedMediaTypes.add(MediaType.IMAGE_JPEG);supportedMediaTypes.add(MediaType.IMAGE_PNG);supportedMediaTypes.add(MediaType.TEXT_EVENT_STREAM);supportedMediaTypes.add(MediaType.TEXT_HTML);supportedMediaTypes.add(MediaType.TEXT_MARKDOWN);supportedMediaTypes.add(MediaType.TEXT_PLAIN);supportedMediaTypes.add(MediaType.TEXT_XML);return supportedMediaTypes;} }SerializerFeature配置的屬性解釋
| QuoteFieldNames | 輸出key時是否使用雙引號,默認為true |
| UseSingleQuotes | 使用單引號而不是雙引號,默認為false |
| WriteMapNullValue | 是否輸出值為null的字段,默認為false應用場景:前端必須需要所有字段 |
| UseISO8601DateFormat | Date使用ISO8601格式輸出,默認為false |
| WriteNullListAsEmpty | List字段如果為null,輸出為[],而不是null |
| WriteNullStringAsEmpty | 字符類型字段如果為null,輸出為"",而不是null |
| WriteNullNumberAsZero | 數值字段如果為null,輸出為0,而非null |
| WriteNullBooleanAsFalse | Boolean字段如果為null,輸出為false,而非null |
| SkipTransientField | 如果是true,類中的Get方法對應的Field是transient,序列化時將會被忽略。默認為true |
| SortField | 按字段名稱排序后輸出。默認為false |
到此SpringMvc中的HttpMessageConverter介紹完畢。如果還有不明白的可以留言。
—————————————————————————————————
由于本人水平有限,難免有不足,懇請各位大佬不吝賜教!
總結
以上是生活随笔為你收集整理的一篇文章教你弄懂SpringMvc中的HttpMessageConverter的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring中的Initializing
- 下一篇: 一篇文章教你弄懂 SpringMvc中的