一次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等字段。但是代码执行之后的返回结果如下:
在这里插入图片描述
找不到converter。

解决

  1. 既然找不到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);
	}
  1. 配置完成之后继续执行代码,结果返回结果的body是空值。
    在这里插入图片描述

源码跟踪

  1. 跟踪源码进入InvocableHandlerMethod类的doInvoke方法,这个类主要是根据传进来的参数调用类的对象的方法,可以认为是一个代理类。

  2. 继续跟踪代码一直到HandlerMethodReturnValueHandlerComposite类的handleReturnValue方法,这个方法中有一个调用handler.handleReturnValue,看名称就这知道是处理返回值的。

  3. 继续跟踪代码,一直跟踪到如下代码段:

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();
		}
	}
  1. 最后调用到的是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技术来实现对象的转换,还没看明白。

  1. 代码跟踪到这一步似乎无法进行下去了,突然回头发现是不是BaseResponse中没有属性的set和get方法导致的FastJson转换失败,重新回到原点加上get和set方法。再次执行代码,果然拿到了正确的返回值,猜测fastjson在做对象转换的时候需要调用对象的set/get方法来做转换,具体如何转换需要继续研究。

小结

​ 看似十分简单的问题,分析起来其实并不容易,springboot一方面给我们的web开发带来了极大的便利,另一方面封装太多,给我们分析和定位问题也带来了极大的苦难。如果要对web开发有更深的理解,还是需要自己分析研读源码,只有对springboot的内在实现机制有比较深刻的理解,才能在出现一些疑难杂症的时候知道如何排查!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值