zoukankan      html  css  js  c++  java
  • SpringCloud请求响应数据转换(一)

    异常现象

    近期做Spring Cloud项目,工程中对Controller添加ResponseBodyAdvice切面,在切片中将返回的结果封装到ResultMessage(自定义结构),但在Controller的方法返回值为字符串,客户端支持的类型为application/json时,出现以下异常:

      java.lang.ClassCastException: com.service.view.ResultMessage cannot be cast to java.lang.String

    即无法将ResultMessage对象转换为String。调试发现,当返回的是String字符串类型时,则会调StringHttpMessageConverter 将数据写入响应流,添加响应头等信息。

    在获取接口数据与写入响应流之间,会将切面处理后的ResultMessage对象交由StringHttpMessageConverter 写入响应流,出现将ResultMessage赋值给一个String对象,从而导致类型转换异常。

    响应数据处理流程

    大致流程(简化请求端)如下:

    源码分析

    工程中自定义ResponseBodyAdvice切面时,对声明@RestController注解的控制层接口,在返回数据的时候会对数据进行转换,转换过程中会调自定义切面对数据处理。具体进行什么转换,会以客户端支持的类型(如application/json或text/plain等)以及控制层返回数据的类型为依据。Spring底层包含几种转换器,如下:

    MVC中,从控制层返回数据到写入响应流,需要通过RequestResponseBodyMethodProcessor类的handleReturnValue方法进行处理,其中会调AbstractMessageConverterMethodProcessor类中方法writeWithMessageConverters,通过消息转换器将数据写入响应流,包含3个关键步骤:

    (1)转换器的确定,该类包含属性List<HttpMessageConverter<?>> messageConverters,其中包含支持的所有转换器,如上图。从前往后依次遍历所有转换器,直到找到支持返回数据类型或媒体类型的转换器。

    (2)切面数据处理,调自定义ResponseBodyAdvice切面(如果存在的话),对返回数据进行处理

    (3)写入响应流,通过消息转换器将数据ServletServerHttpResponse。

    关键方法为writeWithMessageConverters:

      1 /**
      2      * Writes the given return type to the given output message.
      3      * @param value the value to write to the output message
      4      * @param returnType the type of the value
      5      * @param inputMessage the input messages. Used to inspect the {@code Accept} header.
      6      * @param outputMessage the output message to write to
      7      * @throws IOException thrown in case of I/O errors
      8      * @throws HttpMediaTypeNotAcceptableException thrown when the conditions indicated by {@code Accept} header on
      9      * the request cannot be met by the message converters
     10      */
     11     @SuppressWarnings("unchecked")
     12     protected <T> void writeWithMessageConverters(T value, MethodParameter returnType,
     13             ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
     14             throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
     15 
     16         Object outputValue;
     17         Class<?> valueType;
     18         Type declaredType;
     19 //判断控制层返回的value类型,对String进行特殊处理,其他获取对应类型valueType(如java.util.ArrayList)和声明类型declaredType(列表元素具体类型,如java.util.List<com.service.entity.PersonVO>)
     20         if (value instanceof CharSequence) {
     21             outputValue = value.toString();
     22             valueType = String.class;
     23             declaredType = String.class;
     24         }
     25         else {
     26             outputValue = value;
     27             valueType = getReturnValueType(outputValue, returnType);
     28             declaredType = getGenericType(returnType);
     29         }
     30 
     31         HttpServletRequest request = inputMessage.getServletRequest();
     32 //获取浏览器支持的媒体类型,如*/*
     33         List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
     34 //获取控制层指定的返回媒体类型,默认为*/*,如@RequestMapping(value = "/test", produces = MediaType.APPLICATION_JSON_UTF8_VALUE),表示服务响应的格式为application/json格式。
     35         List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);
     36 
     37         if (outputValue != null && producibleMediaTypes.isEmpty()) {
     38             throw new IllegalArgumentException("No converter found for return value of type: " + valueType);
     39         }
     40 //判断浏览器支持的媒体类型是否兼容返回媒体类型
     41         Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
     42         for (MediaType requestedType : requestedMediaTypes) {
     43             for (MediaType producibleType : producibleMediaTypes) {
     44                 if (requestedType.isCompatibleWith(producibleType)) {
     45                     compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));
     46                 }
     47             }
     48         }
     49         if (compatibleMediaTypes.isEmpty()) {
     50             if (outputValue != null) {
     51                 throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
     52             }
     53             return;
     54         }
     55 
     56         List<MediaType> mediaTypes = new ArrayList<MediaType>(compatibleMediaTypes);
     57         MediaType.sortBySpecificityAndQuality(mediaTypes);
     58 
     59         MediaType selectedMediaType = null;
     60         for (MediaType mediaType : mediaTypes) {
     61             if (mediaType.isConcrete()) {
     62                 selectedMediaType = mediaType;
     63                 break;
     64             }
     65             else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
     66                 selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
     67                 break;
     68             }
     69         }
     70 
     71         if (selectedMediaType != null) {
     72             selectedMediaType = selectedMediaType.removeQualityValue();
     73 //遍历所有Http消息转换器,如上图,(1)首先Byte和String等非GenericHttpMessageConverter转换器;
    (2)MappingJackson2HttpMessageConverter转换器继承GenericHttpMessageConverter,会将对象类型转换为json(采用com.fasterxml.jackson)
    74 for (HttpMessageConverter<?> messageConverter : this.messageConverters) { 75 //判断转换器是否为GenericHttpMessageConverter,其中canWrite()方法判断是否能通过该转换器将响应写入响应流,见后续代码 76 if (messageConverter instanceof GenericHttpMessageConverter) { 77 if (((GenericHttpMessageConverter) messageConverter).canWrite( 78 declaredType, valueType, selectedMediaType)) { 79 //获取切片;调切片的beforeBodyWrite方法,处理控制层方法返回值,最终outputValue为处理后的数据,如工程中返回的ResultMessage 80 outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType, 81 (Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(), 82 inputMessage, outputMessage); 83 if (outputValue != null) { 84 addContentDispositionHeader(inputMessage, outputMessage); 85 //将处理后的数据写入响应流,同时添加响应头,并调该转换器的写入方法;如MappingJackson2HttpMessageConverter的writeInternal方法,会将数据写入json中,具体见后续代码 86 ((GenericHttpMessageConverter) messageConverter).write( 87 outputValue, declaredType, selectedMediaType, outputMessage); 88 if (logger.isDebugEnabled()) { 89 logger.debug("Written [" + outputValue + "] as "" + selectedMediaType + 90 "" using [" + messageConverter + "]"); 91 } 92 } 93 return; 94 } 95 } 96 //处理Byte和String等类型的数据 97 else if (messageConverter.canWrite(valueType, selectedMediaType)) { 98 outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType, 99 (Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(), 100 inputMessage, outputMessage); 101 if (outputValue != null) { 102 addContentDispositionHeader(inputMessage, outputMessage); 103 ((HttpMessageConverter) messageConverter).write(outputValue, selectedMediaType, outputMessage); 104 if (logger.isDebugEnabled()) { 105 logger.debug("Written [" + outputValue + "] as "" + selectedMediaType + 106 "" using [" + messageConverter + "]"); 107 } 108 } 109 return; 110 } 111 } 112 } 113 114 if (outputValue != null) { 115 throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes); 116 } 117 }

    (1)确定消息转换器 

    canWrite()方法判断是否能通过该转换器将响应写入响应流,以控制层返回一个自定义对象为例,会调AbstractJackson2HttpMessageConverter,即将数据已json格式返回到前端,其代码如下:

     1 @Override
     2     public boolean canWrite(Class<?> clazz, MediaType mediaType) {
     3         //判断客户端是否支持返回的媒体类型
     4         if (!canWrite(mediaType)) {
     5             return false;
     6         }
     7         if (!logger.isWarnEnabled()) {
     8             return this.objectMapper.canSerialize(clazz);
     9         }
    10         AtomicReference<Throwable> causeRef = new AtomicReference<Throwable>();
    11         //判断是否可以通过ObjectMapper对clazz进行序列化
    12         if (this.objectMapper.canSerialize(clazz, causeRef)) {
    13             return true;
    14         }
    15         logWarningIfNecessary(clazz, causeRef.get());
    16         return false;
    17     }

     其中方法参数,clazz为上文中的valueType,即控制层返回数据类型;mediaType为要写入响应流的媒体类型,可以为null,典型值为请求头Accept(the media type to write, can be null if not specified. Typically the value of an Accept header.)。

    对String或Byte等类型,在对应的转换器中都重写canWrite方法,以StringHttpMessageConverter为例,代码如下:

    1 @Override
    2     public boolean supports(Class<?> clazz) {
    3         return String.class == clazz;
    4     }

     (2)切面数据处理

    beforeBodyWrite:RequestResponseBodyAdviceChain类的beforeBodyWrite方法,会获取到ResponseBodyAdvice子类对应的切面,并调support方法判断是否可以处理某类型数据,调beforeBodyWrite方法进行数据处理

     1 @Override
     2     public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType contentType,
     3             Class<? extends HttpMessageConverter<?>> converterType,
     4             ServerHttpRequest request, ServerHttpResponse response) {
     5 
     6         return processBody(body, returnType, contentType, converterType, request, response);
     7     }
     8 
     9     @SuppressWarnings("unchecked")
    10     private <T> Object processBody(Object body, MethodParameter returnType, MediaType contentType,
    11             Class<? extends HttpMessageConverter<?>> converterType,
    12             ServerHttpRequest request, ServerHttpResponse response) {
    13          //获取并遍历所有与ResponseBodyAdvice匹配的切面,其中returnType包含了请求方法相关信息
    14         for (ResponseBodyAdvice<?> advice : getMatchingAdvice(returnType, ResponseBodyAdvice.class)) {
    15              //调切面的supports方法,判断切面是否支持返回类型和转换类型
    16             if (advice.supports(returnType, converterType)) {
    17                  //调切面的beforeBodyWrite方法,进行数据处理
    18                 body = ((ResponseBodyAdvice<T>) advice).beforeBodyWrite((T) body, returnType,
    19                         contentType, converterType, request, response);
    20             }
    21         }
    22         return body;
    23     }
    24     @SuppressWarnings("unchecked")
    25     private <A> List<A> getMatchingAdvice(MethodParameter parameter, Class<? extends A> adviceType) {
    26          //获取所有切面
    27         List<Object> availableAdvice = getAdvice(adviceType);
    28         if (CollectionUtils.isEmpty(availableAdvice)) {
    29             return Collections.emptyList();
    30         }
    31         List<A> result = new ArrayList<A>(availableAdvice.size());
    32         //遍历所有切面,找到符合adviceType的切面
    33         for (Object advice : availableAdvice) {
    34             if (advice instanceof ControllerAdviceBean) {
    35                 ControllerAdviceBean adviceBean = (ControllerAdviceBean) advice;
    36                 if (!adviceBean.isApplicableToBeanType(parameter.getContainingClass())) {
    37                     continue;
    38                 }
    39                 advice = adviceBean.resolveBean();
    40             }
    41              //判断adviceType 是否为advice.getClass()的父类或父接口等
    42             if (adviceType.isAssignableFrom(advice.getClass())) {
    43                 result.add((A) advice);
    44             }
    45         }
    46         return result;
    47     }

     第16和18行会调自定义ResponseBodyAdvice切面对应的方法,如下,其中还包含对异常情况的处理。

     1 @RestControllerAdvice(annotations = RestController.class)
     2 public class ControllerInterceptor implements ResponseBodyAdvice<Object>{
     3     //异常情况处理
     4     @ExceptionHandler(value = BizException.class)
     5     public String defaultErrorHandler(HttpServletRequest req, BizException e) throws Exception {
     6         ResultMessage rm = new ResultMessage();
     7         ErrorMessage errorMessage = new ErrorMessage(e.getErrCode(), e.getErrMsg());
     8         rm.setErrorMessage(errorMessage);
     9         rm.setSuccess(false);
    10         return JSONUtil.ObjectToString(rm);
    11     }
    12 
    13     //数据处理
    14     @Override
    15     public Object beforeBodyWrite(Object object, MethodParameter methodPram, MediaType mediaType,
    16             Class<? extends HttpMessageConverter<?>> clazz, ServerHttpRequest request, ServerHttpResponse response) {
    17         ResultMessage rm = new ResultMessage();
    18         rm.setSuccess(true);
    19         rm.setData(object);
    20         
    21         Object obj;
    22          //处理控制层返回字符串情况,解决上文说的类型转换异常
    23         if(object != null && object.getClass().equals(String.class)){
    24             obj = JSONObject.fromObject(rm).toString();
    25         }else{
    26             obj = rm;
    27         }
    28         return obj;
    29     }
    30 
    31     //确定是否支持,此处返回true
    32     @Override
    33     public boolean supports(MethodParameter methodPram, Class<? extends HttpMessageConverter<?>> clazz) {
    34         return true;
    35     }
    36 }

       其中,第23行是对控制层返回值为字符串情况的处理,防止出现类型转换异常。

    另外,@RestControllerAdvice支持@ControllerAdvice and @ResponseBody,即为控制层的切面,doc的介绍如下:

      A convenience annotation that is itself annotated with @ControllerAdvice and @ResponseBody.

      Types that carry this annotation are treated as controller advice where @ExceptionHandler methods assume @ResponseBody semantics by default.

    (3)写入响应流

    write方法会将(2)中处理后的数据写入响应流,对String或Byte等类型,会调HttpMessageConverter的write方法;对对象等类型会调GenericHttpMessageConverter的write方法。

    对象类型时,会调GenericHttpMessageConverter父类AbstractGenericHttpMessageConverter的write方法,如下:

     1 /**
     2      * This implementation sets the default headers by calling {@link #addDefaultHeaders},
     3      * and then calls {@link #writeInternal}.
     4      */
     5     public final void write(final T t, final Type type, MediaType contentType, HttpOutputMessage outputMessage)
     6             throws IOException, HttpMessageNotWritableException {
     7 
     8         final HttpHeaders headers = outputMessage.getHeaders();
     9         //添加默认的响应头,包括Content-Type和Content-Length
    10         addDefaultHeaders(headers, t, contentType);
    11 
    12         if (outputMessage instanceof StreamingHttpOutputMessage) {
    13             StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
    14             streamingOutputMessage.setBody(new StreamingHttpOutputMessage.Body() {
    15                 @Override
    16                 public void writeTo(final OutputStream outputStream) throws IOException {
    17                     writeInternal(t, type, new HttpOutputMessage() {
    18                         @Override
    19                         public OutputStream getBody() throws IOException {
    20                             return outputStream;
    21                         }
    22                         @Override
    23                         public HttpHeaders getHeaders() {
    24                             return headers;
    25                         }
    26                     });
    27                 }
    28             });
    29         }
    30         else {
    31             //非StreamingHttpOutputMessage情况下,会调该方法将数据写入响应流
    32             writeInternal(t, type, outputMessage);
    33             outputMessage.getBody().flush();
    34         }
    35     }
    36 /**
    37      * Add default headers to the output message.
    38      * <p>This implementation delegates to {@link #getDefaultContentType(Object)} if a content
    39      * type was not provided, set if necessary the default character set, calls
    40      * {@link #getContentLength}, and sets the corresponding headers.
    41      * @since 4.2
    42      */
    43     protected void addDefaultHeaders(HttpHeaders headers, T t, MediaType contentType) throws IOException{
    44          //设置Content-Type
    45         if (headers.getContentType() == null) {
    46             MediaType contentTypeToUse = contentType;
    47             if (contentType == null || contentType.isWildcardType() || contentType.isWildcardSubtype()) {
    48                 contentTypeToUse = getDefaultContentType(t);
    49             }
    50             else if (MediaType.APPLICATION_OCTET_STREAM.equals(contentType)) {
    51                 MediaType mediaType = getDefaultContentType(t);
    52                 contentTypeToUse = (mediaType != null ? mediaType : contentTypeToUse);
    53             }
    54             if (contentTypeToUse != null) {
    55                 if (contentTypeToUse.getCharset() == null) {
    56                     Charset defaultCharset = getDefaultCharset();
    57                     if (defaultCharset != null) {
    58                         contentTypeToUse = new MediaType(contentTypeToUse, defaultCharset);
    59                     }
    60                 }
    61                 headers.setContentType(contentTypeToUse);
    62             }
    63         }
    64         //设置Content-Length,当t为ArrayList对象时,值为null
    65         if (headers.getContentLength() < 0) {
    66             Long contentLength = getContentLength(t, headers.getContentType());
    67             if (contentLength != null) {
    68                 headers.setContentLength(contentLength);
    69             }
    70         }
    71     }

     第32行会调AbstractJackson2HttpMessageConverter的writeInternal方法。object为经切面处理后的数据,通过com.fasterxml.jackson.databind.ObjectMapper写入json。

     1 @Override
     2     protected void writeInternal(Object object, Type type, HttpOutputMessage outputMessage)
     3             throws IOException, HttpMessageNotWritableException {
     4 
     5         JsonEncoding encoding = getJsonEncoding(outputMessage.getHeaders().getContentType());
     6         JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputMessage.getBody(), encoding);
     7         try {
     8             writePrefix(generator, object);
     9 
    10             Class<?> serializationView = null;
    11             FilterProvider filters = null;
    12             Object value = object;
    13             JavaType javaType = null;
    14             if (object instanceof MappingJacksonValue) {
    15                 MappingJacksonValue container = (MappingJacksonValue) object;
    16                 value = container.getValue();
    17                 serializationView = container.getSerializationView();
    18                 filters = container.getFilters();
    19             }
    20             if (type != null && value != null && TypeUtils.isAssignable(type, value.getClass())) {
    21                 javaType = getJavaType(type, null);
    22             }
    23             ObjectWriter objectWriter;
    24             if (serializationView != null) {
    25                 objectWriter = this.objectMapper.writerWithView(serializationView);
    26             }
    27             else if (filters != null) {
    28                 objectWriter = this.objectMapper.writer(filters);
    29             }
    30             else {
    31                 objectWriter = this.objectMapper.writer();
    32             }
    33             if (javaType != null && javaType.isContainerType()) {
    34                 objectWriter = objectWriter.forType(javaType);
    35             }
    36              //通过ObjectWrite构建json数据结构
    37             objectWriter.writeValue(generator, value);
    38 
    39             writeSuffix(generator, object);
    40             generator.flush();
    41 
    42         }
    43         catch (JsonProcessingException ex) {
    44             throw new HttpMessageNotWritableException("Could not write content: " + ex.getMessage(), ex);
    45         }
    46     }

     String或Byte等类型时,会调HttpMessageConverter的父类AbstractHttpMessageConverter的write方法,代码与上文类似,只是getContentLength和writeInternal方法不同。以String为例,会调StringHttpMessageConverter的writeInternal方法,代码如下:

     1 //返回字符串对应的字节数长度,作为Content-Length,上文中的异常就出现在此处。
     2 @Override
     3     protected Long getContentLength(String str, MediaType contentType) {
     4         Charset charset = getContentTypeCharset(contentType);
     5         try {
     6             return (long) str.getBytes(charset.name()).length;
     7         }
     8         catch (UnsupportedEncodingException ex) {
     9             // should not occur
    10             throw new IllegalStateException(ex);
    11         }
    12     }
    13 
    14 @Override
    15     protected void writeInternal(String str, HttpOutputMessage outputMessage) throws IOException {
    16         if (this.writeAcceptCharset) {
    17             outputMessage.getHeaders().setAcceptCharset(getAcceptedCharsets());
    18         }
    19         Charset charset = getContentTypeCharset(outputMessage.getHeaders().getContentType());
    20         //将字符串数据copy后写入输出流
    21         StreamUtils.copy(str, charset, outputMessage.getBody());
    22     }
    23 StreamUtils类:
    24 /**
    25      * Copy the contents of the given String to the given output OutputStream.
    26      * Leaves the stream open when done.
    27      * @param in the String to copy from
    28      * @param charset the Charset
    29      * @param out the OutputStream to copy to
    30      * @throws IOException in case of I/O errors
    31      */
    32     public static void copy(String in, Charset charset, OutputStream out) throws IOException {
    33         Assert.notNull(in, "No input String specified");
    34         Assert.notNull(charset, "No charset specified");
    35         Assert.notNull(out, "No OutputStream specified");
    36         Writer writer = new OutputStreamWriter(out, charset);
    37         writer.write(in);
    38         writer.flush();
    39     }

     至此,控制层接口返回的数据,经过切面处理后,写入输出流中,返回给前端。

     返回数据处理过程涉及的类

  • 相关阅读:
    hdu 2019 数列有序!
    hdu 2023 求平均成绩
    HDU 5805 NanoApe Loves Sequence (思维题) BestCoder Round #86 1002
    51nod 1264 线段相交
    Gym 100801A Alex Origami Squares (求正方形边长)
    HDU 5512 Pagodas (gcd)
    HDU 5510 Bazinga (字符串匹配)
    UVALive 7269 Snake Carpet (构造)
    UVALive 7270 Osu! Master (阅读理解题)
    UVALive 7267 Mysterious Antiques in Sackler Museum (判断长方形)
  • 原文地址:https://www.cnblogs.com/shuimuzhushui/p/9724583.html
Copyright © 2011-2022 走看看