SpringCloud请求响应数据转换(一)
2021-06-16 13:05
标签:obj produces ack linked not elf resolve 分享图片 ppi 近期做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 (2)切面数据处理,调自定义ResponseBodyAdvice切面(如果存在的话),对返回数据进行处理 (3)写入响应流,通过消息转换器将数据ServletServerHttpResponse。 关键方法为writeWithMessageConverters: (1)确定消息转换器 canWrite()方法判断是否能通过该转换器将响应写入响应流,以控制层返回一个自定义对象为例,会调AbstractJackson2HttpMessageConverter,即将数据已json格式返回到前端,其代码如下: 其中方法参数,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为例,代码如下: (2)切面数据处理 beforeBodyWrite:RequestResponseBodyAdviceChain类的beforeBodyWrite方法,会获取到ResponseBodyAdvice子类对应的切面,并调support方法判断是否可以处理某类型数据,调beforeBodyWrite方法进行数据处理 第16和18行会调自定义ResponseBodyAdvice切面对应的方法,如下,其中还包含对异常情况的处理。 其中,第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方法,如下: This implementation delegates to { 第32行会调AbstractJackson2HttpMessageConverter的writeInternal方法。object为经切面处理后的数据,通过com.fasterxml.jackson.databind.ObjectMapper写入json。 String或Byte等类型时,会调HttpMessageConverter的父类AbstractHttpMessageConverter的write方法,代码与上文类似,只是getContentLength和writeInternal方法不同。以String为例,会调StringHttpMessageConverter的writeInternal方法,代码如下: 至此,控制层接口返回的数据,经过切面处理后,写入输出流中,返回给前端。 SpringCloud请求响应数据转换(一) 标签:obj produces ack linked not elf resolve 分享图片 ppi 原文地址:https://www.cnblogs.com/shuimuzhushui/p/9724583.html异常现象
响应数据处理流程
源码分析
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
(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 @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
1 @Override
2 public boolean supports(Class> clazz) {
3 return String.class == clazz;
4 }
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
1 @RestControllerAdvice(annotations = RestController.class)
2 public class ControllerInterceptor implements ResponseBodyAdvice
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 *
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 }
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 }
返回数据处理过程涉及的类
文章标题:SpringCloud请求响应数据转换(一)
文章链接:http://soscw.com/index.php/essay/94584.html