SpringBoot之响应处理

文章目录

  • 前言
  • 一、返回值处理器ReturnValueHandler
    • 流程
      • 关于HttpMessageConverters的初始化
      • ReturnValueHandler与MappingJackson2HttpMessageConverter关联
  • 二、内容协商
    • 内容协商原理
    • 底层源码
  • 三、自定义MessageConverter
  • 总结


前言

包括返回值处理器ReturnValueHandler、内容协商等讲解。


一、返回值处理器ReturnValueHandler

返回值处理器ReturnValueHandler原理:

  1. 返回值处理器判断是否支持这种类型返回值 supportsReturnType
  2. 返回值处理器调用 handleReturnValue 进行处理
  3. RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 注解的。
    • 利用 MessageConverters 进行处理 将数据写为json
      1. 内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)
      2. 服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据,
      3. SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理?
        1. 得到MappingJackson2HttpMessageConverter可以将对象写为json
        2. 利用MappingJackson2HttpMessageConverter将对象转为json再写出去。

流程

首先(returnValueHandlers):

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapterimplements BeanFactoryAware, InitializingBean {...@Nullableprotected ModelAndView invokeHandlerMethod(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ServletWebRequest webRequest = new ServletWebRequest(request, response);try {...ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);if (this.argumentResolvers != null) {invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);}if (this.returnValueHandlers != null) {//<----关注点invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);}...invocableMethod.invokeAndHandle(webRequest, mavContainer);//看下块代码if (asyncManager.isConcurrentHandlingStarted()) {return null;}return getModelAndView(mavContainer, modelFactory, webRequest);}finally {webRequest.requestCompleted();}}

进入invokeAndHandle:

public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);...try {//看下块代码this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);}catch (Exception ex) {...}}

进入handleReturnValue:

public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodReturnValueHandler {...@Overridepublic void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {//selectHandler()实现在下面HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);if (handler == null) {throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());}//开始处理,调用handleReturnValuehandler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);}@Nullableprivate 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)) {//supportsReturnTypereturn handler;}}return null;}

调用handleReturnValue:
处理@ResponseBody 注解,即RequestResponseBodyMethodProcessor,它实现HandlerMethodReturnValueHandler接口。

public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {...@Overridepublic 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.writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);}}

使用消息转换器进行写出操作(writeWithMessageConverters):


//RequestResponseBodyMethodProcessor继承这类
public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolverimplements HandlerMethodReturnValueHandler {...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());}...//内容协商(浏览器默认会以请求头(参数Accept)的方式告诉服务器他能接受什么样的内容类型)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);//选择一个MediaTypefor (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();//HttpMessageConverterfor (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;}}}...}

HTTPMessageConverter接口:

/*** Strategy interface for converting from and to HTTP requests and responses.*/
public interface HttpMessageConverter<T> {/*** Indicates whether the given class can be read by this converter.*/boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);/*** Indicates whether the given class can be written by this converter.*/boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);/*** Return the list of {@link MediaType} objects supported by this converter.*/List<MediaType> getSupportedMediaTypes();/*** Read an object of the given type from the given input message, and returns it.*/T read(Class<? extends T> clazz, HttpInputMessage inputMessage)throws IOException, HttpMessageNotReadableException;/*** Write an given object to the given output message.*/void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)throws IOException, HttpMessageNotWritableException;}

HttpMessageConverter: 看是否支持将 此 Class类型的对象,转为MediaType类型的数据。

例子:Person对象转为JSON,或者 JSON转为Person,这将用到MappingJackson2HttpMessageConverter

public class MappingJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter {...
}

关于HttpMessageConverters的初始化

DispatcherServlet的初始化时会调用initHandlerAdapters(ApplicationContext context)

public class DispatcherServlet extends FrameworkServlet {...private void initHandlerAdapters(ApplicationContext context) {this.handlerAdapters = null;if (this.detectAllHandlerAdapters) {// Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.Map<String, HandlerAdapter> matchingBeans =BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);if (!matchingBeans.isEmpty()) {this.handlerAdapters = new ArrayList<>(matchingBeans.values());// We keep HandlerAdapters in sorted order.AnnotationAwareOrderComparator.sort(this.handlerAdapters);}}...

上述代码会加载ApplicationContext的所有HandlerAdapter,用来处理@RequestMappingRequestMappingHandlerAdapter实现HandlerAdapter接口,RequestMappingHandlerAdapter也被实例化。

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapterimplements BeanFactoryAware, InitializingBean {...private List<HttpMessageConverter<?>> messageConverters;...public RequestMappingHandlerAdapter() {this.messageConverters = new ArrayList<>(4);this.messageConverters.add(new ByteArrayHttpMessageConverter());this.messageConverters.add(new StringHttpMessageConverter());if (!shouldIgnoreXml) {try {this.messageConverters.add(new SourceHttpMessageConverter<>());}catch (Error err) {// Ignore when no TransformerFactory implementation is available}}this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());}

在构造器中看到一堆HttpMessageConverter。接着,重点查看 AllEncompassingFormHttpMessageConverter 类:

public class AllEncompassingFormHttpMessageConverter extends FormHttpMessageConverter {/*** Boolean flag controlled by a {@code spring.xml.ignore} system property that instructs Spring to* ignore XML, i.e. to not initialize the XML-related infrastructure.* <p>The default is "false".*/private static final boolean shouldIgnoreXml = SpringProperties.getFlag("spring.xml.ignore");private static final boolean jaxb2Present;private static final boolean jackson2Present;private static final boolean jackson2XmlPresent;private static final boolean jackson2SmilePresent;private static final boolean gsonPresent;private static final boolean jsonbPresent;private static final boolean kotlinSerializationJsonPresent;static {ClassLoader classLoader = AllEncompassingFormHttpMessageConverter.class.getClassLoader();jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", classLoader);jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", classLoader);kotlinSerializationJsonPresent = ClassUtils.isPresent("kotlinx.serialization.json.Json", classLoader);}public AllEncompassingFormHttpMessageConverter() {if (!shouldIgnoreXml) {try {addPartConverter(new SourceHttpMessageConverter<>());}catch (Error err) {// Ignore when no TransformerFactory implementation is available}if (jaxb2Present && !jackson2XmlPresent) {addPartConverter(new Jaxb2RootElementHttpMessageConverter());}}if (jackson2Present) {addPartConverter(new MappingJackson2HttpMessageConverter());//<----重点看这里}else if (gsonPresent) {addPartConverter(new GsonHttpMessageConverter());}else if (jsonbPresent) {addPartConverter(new JsonbHttpMessageConverter());}else if (kotlinSerializationJsonPresent) {addPartConverter(new KotlinSerializationJsonHttpMessageConverter());}if (jackson2XmlPresent && !shouldIgnoreXml) {addPartConverter(new MappingJackson2XmlHttpMessageConverter());}if (jackson2SmilePresent) {addPartConverter(new MappingJackson2SmileHttpMessageConverter());}}}public class FormHttpMessageConverter implements HttpMessageConverter<MultiValueMap<String, ?>> {...private List<HttpMessageConverter<?>> partConverters = new ArrayList<>();...public void addPartConverter(HttpMessageConverter<?> partConverter) {Assert.notNull(partConverter, "'partConverter' must not be null");this.partConverters.add(partConverter);}...
}

AllEncompassingFormHttpMessageConverter类构造器看到MappingJackson2HttpMessageConverter类的实例化,AllEncompassingFormHttpMessageConverter包含MappingJackson2HttpMessageConverter

ReturnValueHandler是怎么与MappingJackson2HttpMessageConverter关联起来?请看下节。

ReturnValueHandler与MappingJackson2HttpMessageConverter关联

再次回顾RequestMappingHandlerAdapter

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapterimplements BeanFactoryAware, InitializingBean {...@Nullableprivate HandlerMethodReturnValueHandlerComposite returnValueHandlers;//我们关注的returnValueHandlers@Override@Nullable//本方法在AbstractHandlerMethodAdapterpublic final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {return handleInternal(request, response, (HandlerMethod) handler);}@Overrideprotected ModelAndView handleInternal(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ModelAndView mav;...mav = invokeHandlerMethod(request, response, handlerMethod);...return mav;}@Nullableprotected 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) {//<---我们关注的returnValueHandlersinvocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);}...invocableMethod.invokeAndHandle(webRequest, mavContainer);if (asyncManager.isConcurrentHandlingStarted()) {return null;}return getModelAndView(mavContainer, modelFactory, webRequest);}finally {webRequest.requestCompleted();}}@Overridepublic void afterPropertiesSet() {// Do this first, it may add ResponseBody advice beans...if (this.returnValueHandlers == null) {//赋值returnValueHandlersList<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);}}private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>(20);...// Annotation-based return value types//这里就是 ReturnValueHandler与 MappingJackson2HttpMessageConverter关联 的关键点handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),//<---MessageConverters也就传参传进来的this.contentNegotiationManager, this.requestResponseBodyAdvice));//...return handlers;}//------public List<HttpMessageConverter<?>> getMessageConverters() {return this.messageConverters;}//RequestMappingHandlerAdapter构造器已初始化部分messageConverterspublic RequestMappingHandlerAdapter() {this.messageConverters = new ArrayList<>(4);this.messageConverters.add(new ByteArrayHttpMessageConverter());this.messageConverters.add(new StringHttpMessageConverter());if (!shouldIgnoreXml) {try {this.messageConverters.add(new SourceHttpMessageConverter<>());}catch (Error err) {// Ignore when no TransformerFactory implementation is available}}this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());}...}

应用中WebMvcAutoConfiguration(底层是WebMvcConfigurationSupport实现)传入更多messageConverters,其中就包含MappingJackson2HttpMessageConverter。最终 MappingJackson2HttpMessageConverter 把对象转为JSON(利用底层的jackson的objectMapper转换的)

二、内容协商

根据客户端接收能力不同,返回不同媒体类型的数据。

person:

@Data
public class Person {private String userName;private Integer age;private Date birth;
}

引入XML依赖:

 <dependency><groupId>com.fasterxml.jackson.dataformat</groupId><artifactId>jackson-dataformat-xml</artifactId>
</dependency>

可用Postman软件分别测试返回json和xml:只需要改变请求头中Accept字段(application/json、application/xml)。

Http协议中规定的,Accept字段告诉服务器本客户端可以接收的数据类型。

内容协商原理

  1. 判断当前响应头中是否已经有确定的媒体类型MediaType
  2. 获取客户端(PostMan、浏览器)支持接收的内容类型。(获取客户端Accept请求头字段application/xml)(这一步在下一节有详细介绍)
    • contentNegotiationManager 内容协商管理器 默认使用基于请求头的策略
    • HeaderContentNegotiationStrategy 确定客户端可以接收的内容类型
//RequestResponseBodyMethodProcessor继承这类
public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolverimplements HandlerMethodReturnValueHandler {...protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {Object body;Class<?> valueType;Type targetType;...//内容协商(浏览器默认会以请求头(参数Accept)的方式告诉服务器他能接受什么样的内容类型)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);...}//在AbstractMessageConverterMethodArgumentResolver类内private List<MediaType> getAcceptableMediaTypes(HttpServletRequest request)throws HttpMediaTypeNotAcceptableException {//内容协商管理器 默认使用基于请求头的策略return this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request));}}
public class ContentNegotiationManager implements ContentNegotiationStrategy, MediaTypeFileExtensionResolver {...public ContentNegotiationManager() {this(new HeaderContentNegotiationStrategy());//内容协商管理器 默认使用基于请求头的策略}@Overridepublic List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {for (ContentNegotiationStrategy strategy : this.strategies) {List<MediaType> mediaTypes = strategy.resolveMediaTypes(request);if (mediaTypes.equals(MEDIA_TYPE_ALL_LIST)) {continue;}return mediaTypes;}return MEDIA_TYPE_ALL_LIST;}...}

进入HeaderContentNegotiationStrategy查看:

//基于请求头的策略
public class HeaderContentNegotiationStrategy implements ContentNegotiationStrategy {/*** {@inheritDoc}* @throws HttpMediaTypeNotAcceptableException if the 'Accept' header cannot be parsed*/@Overridepublic 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());}}}
  1. 遍历循环所有当前系统的 MessageConverter,看谁支持操作这个对象(Person)

  2. 找到支持操作Person的converter,把converter支持的媒体类型统计出来。

  3. 客户端需要application/xml,服务端有10种MediaType。
    在这里插入图片描述

  4. 进行内容协商的最佳匹配媒体类型

  5. 将对象转为 最佳匹配媒体类型 的converter。调用它进行转化 。
    在这里插入图片描述

开启浏览器参数方式内容协商功能:为了方便内容协商,开启基于请求参数的内容协商功能。

spring:mvc:contentnegotiation:favor-parameter: true  #开启请求参数内容协商模式

然后,浏览器地址输入带format参数的URL:

http://localhost:8080/test/person?format=json
或
http://localhost:8080/test/person?format=xml

自行观察。

底层源码

//RequestResponseBodyMethodProcessor继承这类
public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolverimplements HandlerMethodReturnValueHandler {...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());}...//内容协商(浏览器默认会以请求头(参数Accept)的方式告诉服务器他能接受什么样的内容类型)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);//选择一个MediaTypefor (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();//HttpMessageConverterfor (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;}}}...}

三、自定义MessageConverter

实现多协议数据兼容。json、xml、x-dr(这个是自创的)

  1. @ResponseBody 响应数据出去 调用 RequestResponseBodyMethodProcessor 处理

  2. Processor 处理方法返回值。通过 MessageConverter处理

  3. 所有 MessageConverter 合起来可以支持各种媒体类型数据的操作(读、写)

  4. 内容协商找到最终的 messageConverter

给容器中添加一个 WebMvcConfigurer

@Configuration(proxyBeanMethods = false)
public class WebConfig {@Beanpublic WebMvcConfigurer webMvcConfigurer(){return new WebMvcConfigurer() {@Overridepublic void extendMessageConverters(List<HttpMessageConverter<?>> converters) {converters.add(new GuiguMessageConverter());}/*** 自定义内容协商策略* @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("gg",MediaType.parseMediaType("application/x-dr"));//指定支持解析哪些参数对应的哪些媒体类型ParameterContentNegotiationStrategy parameterStrategy = new ParameterContentNegotiationStrategy(mediaTypes);
//                parameterStrategy.setParameterName("ff");//还需添加请求头处理策略,否则accept:application/json、application/xml则会失效HeaderContentNegotiationStrategy headeStrategy = new HeaderContentNegotiationStrategy();configurer.strategies(Arrays.asList(parameterStrategy, headeStrategy));}}}
}

/*** 自定义的Converter*/
public class DragonMessageConverter implements HttpMessageConverter<Person> {@Overridepublic boolean canRead(Class<?> clazz, MediaType mediaType) {return false;}@Overridepublic boolean canWrite(Class<?> clazz, MediaType mediaType) {return clazz.isAssignableFrom(Person.class);}/*** 服务器要统计所有MessageConverter都能写出哪些内容类型** application/x-dr* @return*/@Overridepublic List<MediaType> getSupportedMediaTypes() {return MediaType.parseMediaTypes("application/x-dr");}@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());}
}
import java.util.Date;@Controller
public class ResponseTestController {/*** 1、浏览器发请求直接返回 xml    [application/xml]        jacksonXmlConverter* 2、如果是ajax请求 返回 json   [application/json]      jacksonJsonConverter* 3、如果硅谷app发请求,返回自定义协议数据  [appliaction/x-dr]   xxxxConverter*          属性值1;属性值2;** 步骤:* 1、添加自定义的MessageConverter进系统底层* 2、系统底层就会统计出所有MessageConverter能操作哪些类型* 3、客户端内容协商 [dr--->dr]** 作业:如何以参数的方式进行内容协商* @return*/@ResponseBody  //利用返回值处理器里面的消息转换器进行处理@GetMapping(value = "/test/person")public Person getPerson(){Person person = new Person();person.setAge(28);person.setBirth(new Date());person.setUserName("zhangsan");return person;}}

在这里插入图片描述
这里贴出json,xml的数据返回:
在这里插入图片描述
在这里插入图片描述


总结

对于底层源码大家理解个大概,验证他是怎样实现的就可以,以上就是本篇文章的讲解。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/140760.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Vue.js】使用Element搭建登入注册界面axios中GET请求与POST请求跨域问题

一&#xff0c;ElementUI是什么&#xff1f; Element UI 是一个基于 Vue.js 的桌面端组件库&#xff0c;它提供了一套丰富的 UI 组件&#xff0c;用于构建用户界面。Element UI 的目标是提供简洁、易用、美观的组件&#xff0c;同时保持灵活性和可定制性 二&#xff0c;Element…

套接字socket编程的基础知识点

目录 前言&#xff08;必读&#xff09; 网络字节序 网络中的大小端问题 为什么网络字节序采用的是大端而不是小端&#xff1f; 网络字节序与主机字节序之间的转换 字符串IP和整数IP 整数IP存在的意义 字符串IP和整数IP相互转换的方式 inet_addr函数&#xff08;会自…

相机One Shot标定

1 原理说明 原理部分网上其他文章[1][2]也已经说的比较明白了&#xff0c;这里不再赘述。 2 总体流程 参考论文作者开源的Matlab代码[3]和github上的C代码[4]进行说明&#xff08;不得不说还是Matlab代码更优雅&#xff09; 论文方法总体分两部&#xff0c;第一部是在画面中找…

封装一个高级查询组件

封装一个高级查询组件 背景一&#xff0c;前端相关代码二&#xff0c;后端相关代码三&#xff0c;呈现效果总结 背景 业务有个按照自定义选择组合查询条件&#xff0c;保存下来每次查询的时候使用的需求。查了一下项目里的代码没有现成的组件可以用&#xff0c;于是封装了一个 …

Python实战实例代码-网络爬虫-数据分析-机器学习-图像处理

Python实战实例代码-网络爬虫-数据分析-机器学习-图像处理 Python实战实例代码1. 网络爬虫1.1 爬取网页数据1.2 爬取图片1.3 爬取动态数据&#xff08;使用Selenium&#xff09; 2. 数据分析2.1 数据清洗2.2 数据变换2.3 数据聚合 3. 机器学习3.1 线性回归3.2 随机森林3.3 K-Me…

【数据结构】C++实现哈希表

闭散列哈希表 哈希表的结构 在闭散列的哈希表中&#xff0c;哈希表每个位置除了存储所给数据之外&#xff0c;还应该存储该位置当前的状态&#xff0c;哈希表中每个位置的可能状态如下&#xff1a; EMPTY&#xff08;无数据的空位置&#xff09;。EXIST&#xff08;已存储数…

Linux Day18 TCP_UDP协议及相关知识

一、网络基础概念 1.1 网络 网络是由若干结点和连接这些结点的链路组成&#xff0c;网络中的结点可以是计算机&#xff0c;交换机、 路由器等设备。 1.2 互联网 把多个网络连接起来就构成了互联网。目前最大的互联网就是因特网。 网络设备有&#xff1a;交换机、路由器、…

图层混合算法(一)

常见混合结果展示 图层混合后变暗 正常模式&#xff08;normal&#xff09; 混合色*不透明度&#xff08;100%-混合色不透明度&#xff09; void layerblend_normal(Mat &base,Mat &blend,Mat &dst,float opacity) {if (base.rows ! blend.rows ||base.cols ! b…

测试C#图像文本识别模块Tesseract的基本用法

微信公众号“dotNET跨平台”的文章《c#实现图片文体提取》&#xff08;参考文献3&#xff09;介绍了C#图像文本识别模块Tesseract&#xff0c;后者是tesseract-ocr&#xff08;参考文献2&#xff09; 的C#封装版本&#xff0c;目前版本为5.2&#xff0c;关于Tesseract的详细介绍…

使用Python+Flask/Moco框架/Fiddler搭建简单的接口Mock服务

一、Mock测试 1、介绍 mock&#xff1a;就是对于一些难以构造的对象&#xff0c;使用虚拟的技术来实现测试的过程mock测试&#xff1a;在测试过程中&#xff0c;对于某些不容易构造或者不容易获取的对象&#xff0c;可以用一个虚拟的对象来代替的测试方法接口mock测试&#x…

多维时序 | MATLAB实现WOA-CNN-BiLSTM-Attention多变量时间序列预测(SE注意力机制)

多维时序 | MATLAB实现WOA-CNN-BiLSTM-Attention多变量时间序列预测&#xff08;SE注意力机制&#xff09; 目录 多维时序 | MATLAB实现WOA-CNN-BiLSTM-Attention多变量时间序列预测&#xff08;SE注意力机制&#xff09;预测效果基本描述模型描述程序设计参考资料 预测效果 基…

stc8H驱动并控制三相无刷电机综合项目技术资料综合篇

stc8H驱动并控制三相无刷电机综合项目技术资料综合篇 🌿相关项目介绍《基于stc8H驱动三相无刷电机开源项目技术专题概要》 🔨停机状态,才能进入设置状态,可以设置调速模式,以及转动方向。 ✨所有的功能基本已经完成调试,目前所想到的功能基本已经都添加和实现。引脚利…

SpringSecurity 认证流程

文章目录 前言认证入口&#xff08;过滤器&#xff09;认证管理器认证器说明默认认证器的实现 总结 前言 通过上文了解SpringSecurity核心组件后&#xff0c;就可以进一步了解其认证的实现流程了。 认证入口&#xff08;过滤器&#xff09; 在SpringSecurity中处理认证逻辑是…

CMU15-445 format\clang-format\clang-tidy 失败

CMU15-445 format\clang-format\clang-tidy 失败 问题修改 问题 -- Setting build type to Debug as none was specified. -- Youre using Clang 14.0.0 CMake Warning at CMakeLists.txt:67 (message):BusTub/main couldnt find clang-format.CMake Warning at CMakeLists.tx…

虚幻4学习笔记(15)读档 和存档 的实现

虚幻4学习笔记 读档存档 B站UP谌嘉诚课程&#xff1a;https://www.bilibili.com/video/BV164411Y732 读档 添加UI蓝图 SaveGame_UMG 添加Scroll Box 修改Scrollbar Thickness滚动条厚度 15 15 勾选 is variable 添加text 读档界面 添加背景模糊 添加UI蓝图 SaveGame_Slot …

Rowset Class

本节介绍 This chapter provides an overview of Rowset class and discusses the following topics: Shortcut considerations. Rowset object declaration. Scope of a Rowset object. Rowset class built-in functions. Rowset class methods. Rowset class propertie…

计算机毕业设计 智慧养老中心管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

golang实现远程控制主机

文章目录 ssh原理使用golang远程下发命令使用golang远程传输文件 ssh原理 说到ssh原理个人觉得解释最全的一张图是这张华为画的 Connection establishment 这一步就是建立tcp连接 version negotiation 这一步是ssh客户端(连接者)和被ssh服务端(连接者)进行协议的交换&#xf…

字符函数和字符串函数(1)

前言 C语言中对字符和字符串的处理很是频繁&#xff0c;但是C语言本身是没有字符串类型的&#xff0c;字符串通常放在 常量字符串 中或者 字符数组 中。 字符串常量 适用于那些对它不做修改的字符串函数. 1.求字符串长度 strlen 1.1 strlen size_t strlen ( const char * s…