一次Springboot返回值问题排查
问题
一个很简单的springboot的demo。一般我们写springboot的demo的时候为了测试功能是否正常,返回值一般是简单类型比如String,Long等等,这次写demo的时候想写一个稍微复杂一点的demo,因此返回值定义了一个BaseResponse的泛型,如下所示:
public class BaseResponse<T> implements Serializable {
private static final long serialVersionUID = 1L;
private String code;
private String messageInternal;
private String message;
private T data;
public BaseResponse() {
this.code = "200";
this.messageInternal = null;
}
public BaseResponse(T data) {
this.code = "200";
this.messageInternal = null;
this.data = data;
}
}
在返回的时候,将生成的数据set到BaseResponse对象中:
AddressResp resp = getCodeByName(req, response);
BaseResponse baseResponse = new BaseResponse(resp);
return baseResponse;
预期的返回结果应该报告code,messageInternal等字段。但是代码执行之后的返回结果如下:
![image-20200411115051547](/Users/hujun/Library/Application Support/typora-user-images/image-20200411115051547.png)
找不到converter。
解决
-
既然找不到converter,那说明工程中没有配置converter,那就在工程配置一个。springboot默认使用jackson作为json格式转换器,也可以采用fastjson。fastjson配置如下:
@Bean public HttpMessageConverters fastJsonHttpMessageConverters() { //1、定义一个convert转换消息的对象 FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter(); //2、添加fastjson的配置信息 FastJsonConfig fastJsonConfig = new FastJsonConfig(); fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat); //3、在convert中添加配置信息 fastConverter.setFastJsonConfig(fastJsonConfig); //4、将convert添加到converters中 HttpMessageConverter<?> converter = fastConverter; List<MediaType> supportedMediaTypes = new ArrayList<>(); supportedMediaTypes.add(MediaType.APPLICATION_JSON); fastConverter.setSupportedMediaTypes(supportedMediaTypes); //需要配置支持的类型,否则会报错 return new HttpMessageConverters(converter); }
-
配置完成之后继续执行代码,结果返回结果的body是空值。
![image-20200411115522022](/Users/hujun/Library/Application Support/typora-user-images/image-20200411115522022.png)
这个结果非常奇怪,跟进代码之后发现确实set到baseresponse中的对象的值是存在的,但是返回结果确是
{}
。看来只能跟到源码中找答案了。
源码跟踪
-
跟踪源码进入
InvocableHandlerMethod
类的doInvoke
方法,这个类主要是根据传进来的参数调用类的对象的方法,可以认为是一个代理类。 -
继续跟踪代码一直到
HandlerMethodReturnValueHandlerComposite
类的handleReturnValue
方法,这个方法中有一个调用handler.handleReturnValue
,看名称就这知道是处理返回值的。 -
继续跟踪代码,一直跟踪到如下代码段:
if (selectedMediaType != null) { selectedMediaType = selectedMediaType.removeQualityValue(); 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; } } }
跟踪
genericConverter.write
方法,这里的genericConverter实际上是FastJsonHttpMessageConverter
,真正的消息处理就是在这个类中进行的。 -
FastJsonHttpMessageConverter
消息处理在write方法中进行,public final void write(final T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { final HttpHeaders headers = outputMessage.getHeaders(); addDefaultHeaders(headers, t, contentType); if (outputMessage instanceof StreamingHttpOutputMessage) { StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage; streamingOutputMessage.setBody(outputStream -> writeInternal(t, new HttpOutputMessage() { @Override public OutputStream getBody() { return outputStream; } @Override public HttpHeaders getHeaders() { return headers; } })); } else { writeInternal(t, outputMessage); //处理消息 outputMessage.getBody().flush(); } }
-
最后调用到的是
JSONSerializer
的write方法public final void write(Object object) { if (object == null) { out.writeNull(); return; } Class<?> clazz = object.getClass(); ObjectSerializer writer = getObjectWriter(clazz); try { writer.write(this, object, null, null, 0); } catch (IOException e) { throw new JSONException(e.getMessage(), e); } }
这里的writer.write方法将Java对象转换为json string。debug的时候发现这里面的writer是一个ASM构造出来的writer(ASM是一种字节码增强的技术)。FastJson怎么样通过ASM技术来实现对象的转换,还没看明白。
-
代码跟踪到这一步似乎无法进行下去了,突然回头发现是不是BaseResponse中没有属性的set和get方法导致的FastJson转换失败,重新回到原点加上get和set方法。再次执行代码,果然拿到了正确的返回值,猜测fastjson在做对象转换的时候需要调用对象的set/get方法来做转换,具体如何转换需要继续研究。
小结
看似十分简单的问题,分析起来其实并不容易,springboot一方面给我们的web开发带来了极大的便利,另一方面封装太多,给我们分析和定位问题也带来了极大的苦难。如果要对web开发有更深的理解,还是需要自己分析研读源码,只有对springboot的内在实现机制有比较深刻的理解,才能在出现一些疑难杂症的时候知道如何排查!