springboot-springmvc响应json与xml原理-详解数据响应与内容协商(长文预警,收藏慢啃)
目錄
一、springmvc響應json
1. web場景自動引入了json場景
2.使用方式
二、springmvc響應json數據原理
1.springmvc請求處理邏輯
2.返回值的處理
3.返回值處理器
4.SpringMVC到底支持哪些返回值
?5.處理@ResponseBody 注解的RequestResponseBodyMethodProcessor
6.內容協商
7.HttpMessageConverter
8.MappingJackson2HttpMessageConverter
9.總結
三、初識內容協商
1.什么是內容協商
2.springboot響應xml報文
四、內容協商的原理
1.關鍵代碼AbstractMessageConverterMethodProcessor
2.各種服務器端支持的Converts
3.獲取客戶端(PostMan、瀏覽器)支持接收的內容類型
4.遍歷服務器所有MessageConverter,看誰支持操作這個對象(Person)
5.服務端支持的10種處理數據類型(MediaType)
6.權重優先匹配原則
7.開啟基于請求參數的內容協商功能
五、自定義MessageConverter
1.功能
2.實現
3.測試
4.使用參數方式
一、springmvc響應json
1. web場景自動引入了json場景
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId> </dependency>引入web場景會自動引入json,json默認是使用jackson解析的。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-json</artifactId><version>2.3.4.RELEASE</version><scope>compile</scope> </dependency>2.使用方式
(1)@ResponseBody
在方法上加@ResponseBody
(2)@RestController
實際上@RestController=@Controller+@ResponseBody
將實體類轉成json格式,給前端自動返回json數據。
二、springmvc響應json數據原理
1.springmvc請求處理邏輯
springmvc處理邏輯參考博文:
springBoot-springMVC請求處理原理_A_art_xiang的博客-CSDN博客
2.返回值的處理
@Nullable protected ModelAndView invokeHandlerMethod(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ServletWebRequest webRequest = new ServletWebRequest(request, response);try {WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);if (this.argumentResolvers != null) { // 設置請求參數解析器invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);}if (this.returnValueHandlers != null) { // 設置響應值處理器invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);}invocableMethod.setDataBinderFactory(binderFactory);invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);ModelAndViewContainer mavContainer = new ModelAndViewContainer();mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));modelFactory.initModel(webRequest, mavContainer, invocableMethod);mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);asyncWebRequest.setTimeout(this.asyncRequestTimeout);WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);asyncManager.setTaskExecutor(this.taskExecutor);asyncManager.setAsyncWebRequest(asyncWebRequest);asyncManager.registerCallableInterceptors(this.callableInterceptors);asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);if (asyncManager.hasConcurrentResult()) {Object result = asyncManager.getConcurrentResult();mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];asyncManager.clearConcurrentResult();LogFormatUtils.traceDebug(logger, traceOn -> {String formatted = LogFormatUtils.formatValue(result, !traceOn);return "Resume with async result [" + formatted + "]";});invocableMethod = invocableMethod.wrapConcurrentResult(result);}invocableMethod.invokeAndHandle(webRequest, mavContainer); // 執行目標方法if (asyncManager.isConcurrentHandlingStarted()) {return null;}return getModelAndView(mavContainer, modelFactory, webRequest);}finally {webRequest.requestCompleted();} } // org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);setResponseStatus(webRequest);if (returnValue == null) {if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {disableContentCachingIfNecessary(webRequest);mavContainer.setRequestHandled(true);return;}}else if (StringUtils.hasText(getResponseStatusReason())) {mavContainer.setRequestHandled(true);return;}mavContainer.setRequestHandled(false);Assert.state(this.returnValueHandlers != null, "No return value handlers");try {// 處理返回值,利用所有的返回值處理器來處理返回值。this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);}catch (Exception ex) {if (logger.isTraceEnabled()) {logger.trace(formatErrorForReturnValue(returnValue), ex);}throw ex;} } // org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite#handleReturnValue @Override public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {// 找能處理返回值的返回值處理器HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);if (handler == null) {throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());}handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);// 返回值處理 } // org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite#selectHandler @Nullable private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {boolean isAsyncValue = isAsyncReturnValue(value, returnType);for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) { // 循環遍歷所有的返回值處理器if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {continue;}if (handler.supportsReturnType(returnType)) { // 如果返回值類型找到對應的返回值處理器,就返回該處理器return handler;}}return null; }3.返回值處理器
有許多返回值處理器
返回值處理器也是一個接口,HandlerMethodReturnValueHandler。
supportsReturnType來判斷是否能處理該返回值類型。
handleReturnValue來真正處理返回值。
?
(1)、返回值處理器判斷是否支持這種類型返回值 supportsReturnType
(2)、返回值處理器調用 handleReturnValue 進行處理
(3)、RequestResponseBodyMethodProcessor 可以處理返回值標了@ResponseBody 注解的。
4.SpringMVC到底支持哪些返回值
ModelAndView Model View // 視圖 ResponseEntity ResponseBodyEmitter StreamingResponseBody // 流式數據 HttpEntity // 不能為RequestEntity HttpHeaders Callable// 支持異步 DeferredResult // 支持異步 ListenableFuture// 支持異步 CompletionStage WebAsyncTask 方法有 @ModelAttribute 且為對象類型的 方法有@ResponseBody 注解 ---> RequestResponseBodyMethodProcessor;(自定義實體類響應json數據,使用此返回值處理器)?5.處理@ResponseBody 注解的RequestResponseBodyMethodProcessor
// 處理返回值 // org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#handleReturnValue @Override public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest)throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {mavContainer.setRequestHandled(true);ServletServerHttpRequest inputMessage = createInputMessage(webRequest);ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);// Try even with null return value. ResponseBodyAdvice could get involved.// 利用 MessageConverters 進行處理 將數據寫為jsonwriteWithMessageConverters(returnValue, returnType, inputMessage, outputMessage); } 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());}// 判斷返回值是否是資源類型(stream流數據)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();boolean isContentTypePreset = contentType != null && contentType.isConcrete();if (isContentTypePreset) {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();// // 循環所有的消息轉換器,SpringMVC會挨個遍歷所有容器底層的 HttpMessageConverter ,看誰能處理for (HttpMessageConverter<?> converter : this.messageConverters) { // 得到MappingJackson2HttpMessageConverter可以將對象寫為json// 利用MappingJackson2HttpMessageConverter將對象轉為json再寫出去。最終 MappingJackson2HttpMessageConverter 把對象轉為JSON(利用底層的jackson的objectMapper轉換的)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) {Set<MediaType> producibleMediaTypes =(Set<MediaType>) inputMessage.getServletRequest().getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);if (isContentTypePreset || !CollectionUtils.isEmpty(producibleMediaTypes)) {throw new HttpMessageNotWritableException("No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'");}throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);} }6.內容協商
內容協商(瀏覽器默認會以請求頭的方式告訴服務器他能接受什么樣的內容類型,請求頭Accept)
?
7.HttpMessageConverter
(1)HttpMessageConverter實際是一個接口
HttpMessageConverter左右: 看是否支持將 此 Class類型的對象,轉為MediaType類型的數據。
例子:Person對象轉為JSON。或者 JSON轉為Person
canRead:是否能讀(媒體類型參數)
canWrite:是否能寫(媒體類型參數)
getSypportedMediaTypes:判斷是否支持該類型的返回值
?(2)默認的MessageConverter
?
0 - 只支持Byte類型的返回值
1 -?只支持String類型的返回值
2 - 只支持String類型的返回值
3 -?只支持Resource類型的返回值
4 - 只支持ResourceRegion類型的返回值
5 -?支持DOMSource.class \ SAXSource.class \ StAXSource.class \StreamSource.class \Source.class類型的返回值
6 -?只支持MultiValueMap類型的返回值
7 -?直接返回true
8 -?直接返回true
9 - 支持注解方式xml處理的
8.MappingJackson2HttpMessageConverter
最終 MappingJackson2HttpMessageConverter 把對象轉為JSON(利用底層的jackson的objectMapper轉換的)
// org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter#writeInternal @Override protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage)throws IOException, HttpMessageNotWritableException {MediaType contentType = outputMessage.getHeaders().getContentType();JsonEncoding encoding = getJsonEncoding(contentType);JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputMessage.getBody(), encoding);try {writePrefix(generator, object);Object value = object;Class<?> serializationView = null;FilterProvider filters = null;JavaType javaType = null;if (object instanceof MappingJacksonValue) {MappingJacksonValue container = (MappingJacksonValue) object;value = container.getValue();serializationView = container.getSerializationView();filters = container.getFilters();}if (type != null && TypeUtils.isAssignable(type, value.getClass())) {javaType = getJavaType(type, null);}ObjectWriter objectWriter = (serializationView != null ?this.objectMapper.writerWithView(serializationView) : this.objectMapper.writer());if (filters != null) {objectWriter = objectWriter.with(filters);}if (javaType != null && javaType.isContainerType()) {objectWriter = objectWriter.forType(javaType);}SerializationConfig config = objectWriter.getConfig();if (contentType != null && contentType.isCompatibleWith(MediaType.TEXT_EVENT_STREAM) &&config.isEnabled(SerializationFeature.INDENT_OUTPUT)) {objectWriter = objectWriter.with(this.ssePrettyPrinter);}objectWriter.writeValue(generator, value);writeSuffix(generator, object);generator.flush();}catch (InvalidDefinitionException ex) {throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex);}catch (JsonProcessingException ex) {throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getOriginalMessage(), ex);} } // org.springframework.http.converter.AbstractGenericHttpMessageConverter#write @Override public final void write(final T t, @Nullable final Type type, @Nullable MediaType contentType,HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {final HttpHeaders headers = outputMessage.getHeaders();addDefaultHeaders(headers, t, contentType);if (outputMessage instanceof StreamingHttpOutputMessage) {StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;streamingOutputMessage.setBody(outputStream -> writeInternal(t, type, new HttpOutputMessage() {@Overridepublic OutputStream getBody() {return outputStream;}@Overridepublic HttpHeaders getHeaders() {return headers;}}));}else {writeInternal(t, type, outputMessage);// 最終 MappingJackson2HttpMessageConverter 把對象轉為JSON(利用底層的jackson的objectMapper轉換的)outputMessage.getBody().flush();} }?
9.總結
標注@ResponseBody ->?使用RequestResponseBodyMethodProcessor處理 ->?查找對應的messageConverter處理返回值。
messageConverter可以處理多種返回值(不止局限于自定義的實體類,但是一定要加上@ResponseBody注解)
三、初識內容協商
1.什么是內容協商
內容協商(瀏覽器默認會以請求頭的方式告訴服務器他能接受什么樣的內容類型,請求頭Accept)。
根據客戶端接收能力不同,返回不同媒體類型的數據。
?
2.springboot響應xml報文
(1)引入支持xml依賴
<dependency><groupId>com.fasterxml.jackson.dataformat</groupId><artifactId>jackson-dataformat-xml</artifactId> </dependency>(2)服務器會按照請求頭來判斷返回的內容格式
只需要改變請求頭中Accept字段。Http協議中規定的,告訴服務器本客戶端可以接收的數據類型。
如果寫application/json會返回json報文,如果寫application/xml就會返回xml報文。
?
四、內容協商的原理
同上,標注@ResponseBody ->?使用RequestResponseBodyMethodProcessor處理 ->?查找對應的messageConverter處理返回值。
1.關鍵代碼AbstractMessageConverterMethodProcessor
// AbstractMessageConverterMethodProcessor的writeWithMessageConverters 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,只要沒加攔截器處理一般情況下都沒有MediaType contentType = outputMessage.getHeaders().getContentType();boolean isContentTypePreset = contentType != null && contentType.isConcrete();if (isContentTypePreset) {if (logger.isDebugEnabled()) {logger.debug("Found 'Content-Type:" + contentType + "' in response");}selectedMediaType = contentType;}else {HttpServletRequest request = inputMessage.getServletRequest();// 獲取客戶端(PostMan、瀏覽器)支持接收的內容類型。(獲取客戶端Accept請求頭字段)【application/xml】// 底層其實就是使用原生request,調用getHeaderValues("Accept")List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);// 遍歷循環所有當前系統的 MessageConverter,看誰支持操作這個對象(Person)// 找到支持操作Person的converter,把converter支持的媒體類型統計出來。(詳見上面)// 導入了jackson處理xml的包,xml的converter就會自動進來List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);// 最終發現,客戶端需要【application/xml】。服務端可以提供10種服務【json、xml】if (body != null && producibleTypes.isEmpty()) {throw new HttpMessageNotWritableException("No converter found for return value of type: " + valueType);}List<MediaType> mediaTypesToUse = new ArrayList<>();// 內容協商,雙重for循環,找出最佳匹配媒體類型。最終將匹配的媒體類型放到mediaTypesToUsefor (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);// 選中的媒體類型只會有一個,有匹配的就break。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();// 找Converter,看誰能將對象轉成需要的媒體類型(xml)// 用 支持 將對象轉為 最佳匹配媒體類型 的converter。調用它進行轉化 。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) {Set<MediaType> producibleMediaTypes =(Set<MediaType>) inputMessage.getServletRequest().getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);if (isContentTypePreset || !CollectionUtils.isEmpty(producibleMediaTypes)) {throw new HttpMessageNotWritableException("No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'");}throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);} }2.各種服務器端支持的Converts
后四個分別支持對象轉json、對象轉xml。
其中對象轉xml的因為導入了jackson的xml支持的包,會自動出現。
?
3.獲取客戶端(PostMan、瀏覽器)支持接收的內容類型
底層其實就是使用原生request,調用getHeaderValues("Accept")
// org.springframework.web.accept.HeaderContentNegotiationStrategy#resolveMediaTypes @Override public List<MediaType> resolveMediaTypes(NativeWebRequest request)throws HttpMediaTypeNotAcceptableException {String[] headerValueArray = request.getHeaderValues(HttpHeaders.ACCEPT);if (headerValueArray == null) {return MEDIA_TYPE_ALL_LIST;}List<String> headerValues = Arrays.asList(headerValueArray);try {List<MediaType> mediaTypes = MediaType.parseMediaTypes(headerValues);MediaType.sortBySpecificityAndQuality(mediaTypes);return !CollectionUtils.isEmpty(mediaTypes) ? mediaTypes : MEDIA_TYPE_ALL_LIST;}catch (InvalidMediaTypeException ex) {throw new HttpMediaTypeNotAcceptableException("Could not parse 'Accept' header " + headerValues + ": " + ex.getMessage());} }4.遍歷服務器所有MessageConverter,看誰支持操作這個對象(Person)
// org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#getProducibleMediaTypes(javax.servlet.http.HttpServletRequest, java.lang.Class<?>, java.lang.reflect.Type) protected List<MediaType> getProducibleMediaTypes(HttpServletRequest request, Class<?> valueClass, @Nullable Type targetType) {Set<MediaType> mediaTypes =(Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);if (!CollectionUtils.isEmpty(mediaTypes)) {return new ArrayList<>(mediaTypes);}else if (!this.allSupportedMediaTypes.isEmpty()) {List<MediaType> result = new ArrayList<>();for (HttpMessageConverter<?> converter : this.messageConverters) {if (converter instanceof GenericHttpMessageConverter && targetType != null) {if (((GenericHttpMessageConverter<?>) converter).canWrite(targetType, valueClass, null)) {result.addAll(converter.getSupportedMediaTypes());}}else if (converter.canWrite(valueClass, null)) {result.addAll(converter.getSupportedMediaTypes());}}return result;}else {return Collections.singletonList(MediaType.ALL);} }5.服務端支持的10種處理數據類型(MediaType)
?
6.權重優先匹配原則
瀏覽器q=0.9,權重為0.9,沒有寫支持json,而*/*權重為0.8,所以優先會返回xml格式。
7.開啟基于請求參數的內容協商功能
(1)使用
瀏覽器內容協商優先支持xml,可以開啟使用請求參數作為內容協商。
spring:mvc:contentnegotiation:favor-parameter: true #開啟請求參數內容協商模式?
(2)原理
開啟配置之后,內容協商管理器會注冊一個使用參數的內容協商策略,參數名稱就是format,format的值就支持xml和json。
?
底層實際上是使用request.getParameter("format");獲取請求參數。
五、自定義MessageConverter
1.功能
實現多協議數據兼容。json、xml、test-測試報文類型
(1)、@ResponseBody 響應數據出去 調用 RequestResponseBodyMethodProcessor 處理
(2)、Processor 處理方法返回值。通過 MessageConverter 處理
(3)、所有 MessageConverter 合起來可以支持各種媒體類型數據的操作(讀、寫)
(4)、內容協商找到最終的 messageConverter;
2.實現
(1)添加自定義的MessageConverter
import com.cxf.model.Person; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotWritableException; import java.io.IOException; import java.io.OutputStream; import java.util.List;/** * 自定義的Converter,操作Person類型的數據 */ public class TestMessageConverter implements HttpMessageConverter<Person> {@Overridepublic boolean canRead(Class<?> clazz, MediaType mediaType) {return false; // 不需要支持讀}@Overridepublic boolean canWrite(Class<?> clazz, MediaType mediaType) {return clazz.isAssignableFrom(Person.class); // 是Person類型就可以寫}/*** 服務器要統計所有MessageConverter都能寫出哪些內容類型* application/test*/@Overridepublic List<MediaType> getSupportedMediaTypes() {// 把字符串解析成媒體類型集合return MediaType.parseMediaTypes("application/test");}@Overridepublic Person read(Class<? extends Person> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {return null;}@Overridepublic void write(Person person, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {//自定義協議數據的寫出String data = person.getUserName()+"|"+person.getAge()+"|"+person.getBirth();//寫出去OutputStream body = outputMessage.getBody();body.write(data.getBytes());} }(2)在配置類中注冊剛寫的Converter.
//WebMvcConfigurer定制化SpringMVC的功能 @Bean public WebMvcConfigurer webMvcConfigurer(){return new WebMvcConfigurer() {// 擴展MessageConverters@Overridepublic void extendMessageConverters(List<HttpMessageConverter<?>> converters) {converters.add(new TestMessageConverter());}}; }3.測試
?
4.使用參數方式
(1)加配置
spring:mvc:contentnegotiation:favor-parameter: true #開啟請求參數內容協商模式(2)配置類加配置
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.web.accept.HeaderContentNegotiationStrategy; import org.springframework.web.accept.ParameterContentNegotiationStrategy; import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map;@Configuration(proxyBeanMethods = false) public class WebConfig{//WebMvcConfigurer定制化SpringMVC的功能@Beanpublic WebMvcConfigurer webMvcConfigurer(){return new WebMvcConfigurer() {/*** 自定義內容協商策略,有可能我們添加的自定義的功能會覆蓋默認很多功能,導致一些默認的功能失效。* @param configurer*/@Overridepublic void configureContentNegotiation(ContentNegotiationConfigurer configurer) {//Map<String, MediaType> mediaTypesMap<String, MediaType> mediaTypes = new HashMap<>();mediaTypes.put("json",MediaType.APPLICATION_JSON);mediaTypes.put("xml",MediaType.APPLICATION_XML);mediaTypes.put("test",MediaType.parseMediaType("application/test")); // 服務端可以產生test類型的數據//指定支持解析哪些參數對應的哪些媒體類型ParameterContentNegotiationStrategy parameterStrategy = new ParameterContentNegotiationStrategy(mediaTypes);parameterStrategy.setParameterName("ff"); // ff=testHeaderContentNegotiationStrategy headeStrategy = new HeaderContentNegotiationStrategy(); // 放一個基于請求頭的,都可以使用configurer.strategies(Arrays.asList(parameterStrategy,headeStrategy));}// 擴展MessageConverters@Overridepublic void extendMessageConverters(List<HttpMessageConverter<?>> converters) {converters.add(new TestMessageConverter());}};} }(3)測試一下吧
總結
以上是生活随笔為你收集整理的springboot-springmvc响应json与xml原理-详解数据响应与内容协商(长文预警,收藏慢啃)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: springboot-静态资源配置原理
- 下一篇: springboot-拦截器的实现、执行