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 将数据写入响应流,会添加响应头等信息。其中计算响应数据长度Content-Length时,会将ResultMessage对象赋值给一个String对象,导致类型转换异常。 响应数据处理流程 大致流程(简化请求端)如下: 源码分析 工程中自定义ResponseBodyAdvice切面时,对声明@RestController注解的控制层接口,在返回数据的时候会对数据进行转换,转换过程中会调自定义切面对数据处理。具体进行什么转换,会以客户端支持的类型(如application/json或text/plain等)以及控制层返回数据的类型为依据。Spring底层包含几种转换器,如下: MVC中,从控制层返回数据到写入响应流,需要通过RequestResponseBodyMethodProcessor类的handleReturnValue方法进行处理,其中会调AbstractMessageConverterMethodProcessor类中方法writeWithMessageConverters,通过消息转换器将数据写入响应流,包含3个关键步骤: (1)转换器的确定,该类包含属性List> 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 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) 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 requestedMediaTypes = getAcceptableMediaTypes(request); 34 //获取控制层指定的返回媒体类型,默认为*/*,如@RequestMapping(value = "/test", produces = MediaType.APPLICATION_JSON_UTF8_VALUE),表示服务响应的格式为application/json格式。 35 List 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 compatibleMediaTypes = new LinkedHashSet(); 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 mediaTypes = new ArrayList(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>) 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>) 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 causeRef = new AtomicReference(); 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> converterType, 4 ServerHttpRequest request, ServerHttpResponse response) { 5 6 return processBody(body, returnType, contentType, converterType, request, response); 7 } 8 9 @SuppressWarnings("unchecked") 10 private Object processBody(Object body, MethodParameter returnType, MediaType contentType, 11 Class> 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) advice).beforeBodyWrite((T) body, returnType, 19 contentType, converterType, request, response); 20 } 21 } 22 return body; 23 } 24 @SuppressWarnings("unchecked") 25 private List getMatchingAdvice(MethodParameter parameter, Class adviceType) { 26 //获取所有切面 27 List availableAdvice = getAdvice(adviceType); 28 if (CollectionUtils.isEmpty(availableAdvice)) { 29 return Collections.emptyList(); 30 } 31 List result = new ArrayList(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{ 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> 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> 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 im
50000+
5万行代码练就真实本领
17年
创办于2008年老牌培训机构
1000+
合作企业
98%
就业率

联系我们

电话咨询

0532-85025005

扫码添加微信