SpringBoot之响应处理

简介: SpringBoot之响应处理



前言

包括返回值处理器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 AbstractHandlerMethodAdapter
    implements BeanFactoryAware, InitializingBean {
    ...
  @Nullable
  protected 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 {
    ...
  @Override
  public 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());
    }
        //开始处理,调用handleReturnValue
    handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
  }
    @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)) {//supportsReturnType
        return handler;
      }
    }
    return null;
  }

调用handleReturnValue:

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

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

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

//RequestResponseBodyMethodProcessor继承这类
public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver
    implements 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);
                //选择一个MediaType
                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();
                //HttpMessageConverter
                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;
                    }
                }
            }
      ...
        }

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 AbstractHandlerMethodAdapter
    implements 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 AbstractHandlerMethodAdapter
    implements BeanFactoryAware, InitializingBean {
    ...
    @Nullable
  private HandlerMethodReturnValueHandlerComposite returnValueHandlers;//我们关注的returnValueHandlers
    @Override
  @Nullable//本方法在AbstractHandlerMethodAdapter
  public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
      throws Exception {
    return handleInternal(request, response, (HandlerMethod) handler);
  }
    @Override
  protected ModelAndView handleInternal(HttpServletRequest request,
      HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    ModelAndView mav;
        ...
        mav = invokeHandlerMethod(request, response, handlerMethod);
        ...
    return mav;
  }
    @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) {//<---我们关注的returnValueHandlers
        invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
      }
            ...
      invocableMethod.invokeAndHandle(webRequest, mavContainer);
      if (asyncManager.isConcurrentHandlingStarted()) {
        return null;
      }
      return getModelAndView(mavContainer, modelFactory, webRequest);
    }
    finally {
      webRequest.requestCompleted();
    }
  }
   @Override
  public void afterPropertiesSet() {
    // Do this first, it may add ResponseBody advice beans
        ...
    if (this.returnValueHandlers == null) {//赋值returnValueHandlers
      List<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构造器已初始化部分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());
  }
    ...
}

应用中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 AbstractMessageConverterMethodArgumentResolver
    implements 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());//内容协商管理器 默认使用基于请求头的策略
  }
    @Override
  public 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
   */
  @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());
    }
  }
}
  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 AbstractMessageConverterMethodArgumentResolver
    implements 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);
                //选择一个MediaType
                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();
                //HttpMessageConverter
                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;
                    }
                }
            }
      ...
        }

三、自定义MessageConverter

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

  1. @ResponseBody 响应数据出去 调用 RequestResponseBodyMethodProcessor 处理
  2. Processor 处理方法返回值。通过 MessageConverter处理
  3. 所有 MessageConverter 合起来可以支持各种媒体类型数据的操作(读、写)
  4. 内容协商找到最终的 messageConverter

给容器中添加一个 WebMvcConfigurer

@Configuration(proxyBeanMethods = false)
public class WebConfig {
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
            @Override
            public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
                converters.add(new GuiguMessageConverter());
            }
            /**
             * 自定义内容协商策略
             * @param configurer
             */
            @Override
            public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
                //Map<String, MediaType> mediaTypes
                Map<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> {
    @Override
    public boolean canRead(Class<?> clazz, MediaType mediaType) {
        return false;
    }
    @Override
    public boolean canWrite(Class<?> clazz, MediaType mediaType) {
        return clazz.isAssignableFrom(Person.class);
    }
    /**
     * 服务器要统计所有MessageConverter都能写出哪些内容类型
     *
     * application/x-dr
     * @return
     */
    @Override
    public List<MediaType> getSupportedMediaTypes() {
        return MediaType.parseMediaTypes("application/x-dr");
    }
    @Override
    public Person read(Class<? extends Person> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        return null;
    }
    @Override
    public 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的数据返回:


总结

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

相关文章
|
5天前
|
JSON 缓存 Java
Springboot 之 Filter 实现超大响应 JSON 数据压缩
Springboot 之 Filter 实现超大响应 JSON 数据压缩
37 0
|
5天前
|
JSON Java API
Springboot项目中如何设计一个规范的统一的Restful API 响应框架?
Springboot项目中如何设计一个规范的统一的Restful API 响应框架?
28 1
|
5天前
|
JSON 前端开发 Java
深入解析SpringBoot的请求响应机制
深入解析SpringBoot的请求响应机制
|
5月前
|
JSON Java 数据格式
SpringBoot 统一响应返回格式格式 数组
SpringBoot 统一响应返回格式格式 数组
|
9月前
|
XML 网络架构 数据格式
SpringBoot-22-RESTful统一规范响应数据格式
REST是Representational State Transfer的缩写,是在2000年被Roy Thomas Fielding提出的,Fielding是一个很厉害的人物,他是HTTP协议的主要设计者。REST是他对互联网软件构架的原则。REST是一种针对网络应用设计和软件开发方式,降低了开发的复杂性,提高了系统的可伸缩性。如果想要具体了解一下其含义可以查看一下阮一峰老师对REST理解RESTful架构。
73 0
|
9月前
|
数据采集 监控 Java
Spring Boot拦截器:精细化控制请求与响应
本篇详细介绍了在Spring Boot中使用拦截器的方法。拦截器是一种强大的机制,可用于在请求处理前后进行操作,如鉴权、日志记录等。文章涵盖了创建拦截器类、注册拦截器以及实际应用案例。通过具体的代码示例,读者可以了解如何在项目中配置和使用拦截器,以实现各种功能需求。拦截器为Spring Boot应用增加了更多的灵活性和可扩展性,能够提升应用的安全性和可维护性。
1093 0
Spring Boot拦截器:精细化控制请求与响应
|
10月前
|
JSON NoSQL 前端开发
SpringBoot定义优雅全局统一Restful API 响应框架完结撒花篇封装starter组件
SpringBoot定义优雅全局统一Restful API 响应框架完结撒花篇封装starter组件
SpringBoot定义优雅全局统一Restful API 响应框架完结撒花篇封装starter组件
|
12月前
|
监控 前端开发 安全
Spring Boot 统一RESTful接口响应和统一异常处理
基于Spring Boot 框架开发的应用程序,大部分都是以提供RESTful接口为主要的目的。前端或者移动端开发人员通过调用后端提供的RESTful接口完成数据的交换。 统一的RESTful接口响应数据结构是基本的开发规范。能够减少团队内部不必要的沟通;减轻接口消费者校验数据的负担;降低其他同事接手代码的难度;提高接口的健壮性和可扩展性。 统一的异常处理,是系统完备性的基本象征。通过对全局异常信息的捕获,能够避免将异常信息和系统敏感信息直接抛给客户端;针对特定类型异常捕获之后可以重新对输出数据做编排,提高交互友好度,同时可以记录异常信息以便监控和分析。
304 0
Spring Boot 统一RESTful接口响应和统一异常处理
|
JSON 架构师 Java
SpringBoot封装响应处理
SpringBoot封装响应处理
186 0
|
XML JSON 前端开发
spring boot 设置随机端口和压缩响应
spring boot 设置随机端口和压缩响应
spring boot 设置随机端口和压缩响应