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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

一篇文章教你弄懂SpringMvc中的HttpMessageConverter

發布時間:2025/1/21 javascript 60 豆豆
生活随笔 收集整理的這篇文章主要介紹了 一篇文章教你弄懂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方法獲得對應的輸入流和輸出流。
public interface HttpMessageConverter<T> {boolean canRead(Class<?> clazz, MediaType mediaType);boolean canWrite(Class<?> clazz, MediaType mediaType);List<MediaType> getSupportedMediaTypes();T read(Class<? extends T> clazz, HttpInputMessage inputMessage)throws IOException, HttpMessageNotReadableException;void write(T t, MediaType contentType, HttpOutputMessage outputMessage)throws IOException, HttpMessageNotWritableException; }

缺省配置
平常我們在發送http請求時,沒有配置任何 MessageConverter,但是數據前后傳遞依舊好用,是因為 SpringMVC 啟動時會自動配置一些HttpMessageConverter,在 WebMvcConfigurationSupport 類中添加了缺省 MessageConverter:

protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();stringConverter.setWriteAcceptCharset(false);messageConverters.add(new ByteArrayHttpMessageConverter());messageConverters.add(stringConverter);messageConverters.add(new ResourceHttpMessageConverter());messageConverters.add(new SourceHttpMessageConverter<Source>());messageConverters.add(new AllEncompassingFormHttpMessageConverter());if (romePresent) {messageConverters.add(new AtomFeedHttpMessageConverter());messageConverters.add(new RssChannelHttpMessageConverter());}if (jackson2XmlPresent) {ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.xml().applicationContext(this.applicationContext).build();messageConverters.add(new MappingJackson2XmlHttpMessageConverter(objectMapper));}else if (jaxb2Present) {messageConverters.add(new Jaxb2RootElementHttpMessageConverter());}if (jackson2Present) {ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().applicationContext(this.applicationContext).build();messageConverters.add(new MappingJackson2HttpMessageConverter(objectMapper));}else if (gsonPresent) {messageConverters.add(new GsonHttpMessageConverter());}}

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依賴

<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.58</version> </dependency>

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應用場景:前端必須需要所有字段
UseISO8601DateFormatDate使用ISO8601格式輸出,默認為false
WriteNullListAsEmptyList字段如果為null,輸出為[],而不是null
WriteNullStringAsEmpty字符類型字段如果為null,輸出為"",而不是null
WriteNullNumberAsZero數值字段如果為null,輸出為0,而非null
WriteNullBooleanAsFalseBoolean字段如果為null,輸出為false,而非null
SkipTransientField如果是true,類中的Get方法對應的Field是transient,序列化時將會被忽略。默認為true
SortField按字段名稱排序后輸出。默認為false

到此SpringMvc中的HttpMessageConverter介紹完畢。如果還有不明白的可以留言。
—————————————————————————————————
由于本人水平有限,難免有不足,懇請各位大佬不吝賜教!

總結

以上是生活随笔為你收集整理的一篇文章教你弄懂SpringMvc中的HttpMessageConverter的全部內容,希望文章能夠幫你解決所遇到的問題。

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