问题
一个很简单的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等字段。但是代码执行之后的返回结果如下:
找不到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是空值。
源码跟踪
- 跟踪源码进入
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
,真正的消息处理就是在这个类中进行的。
4. 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的内在实现机制有比较深刻的理解,才能在出现一些疑难杂症的时候知道如何排查!