背景:Spring版本4.3.22;微服务A提供接口供其他服务调用,奇葩点:A服务响应封装了统一对象返回,对象如下:
@Getter
@Setter
@NoArgsConstructor
public class Response {
/** 响应状态码 **/
private Integer code;
/** 异常消息 **/
private String message;
/** 请求流水号 **/
private String requestNo;
/** 响应数据 **/
private Object data;
/** 业务码 **/
private String bizCode;
public Response(String requestNo, Integer code, Object data) {
this.code = code;
this.data = data;
this.requestNo = requestNo;
}
public Response(String requestNo, Integer code, String bizCode, Object data) {
this.code = code;
this.data = data;
this.bizCode = bizCode;
this.requestNo = requestNo;
}
}
封装响应对象关键代码:
public class CustomResponseMessageConverter extends MappingJackson2HttpMessageConverter {
@Override
protected void writeInternal(Object object, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
if (object instanceof Response) {
super.writeInternal(object, type, outputMessage);
} else {
checkRequestId();
super.writeInternal(new Response(RequestIdUtils.getRequestId().toString(), HttpStatus.OK.value(), object), type, outputMessage);
}
}
private void checkRequestId() {
UUID uuid = RequestIdUtils.getRequestId();
if (uuid == null) {
RequestIdUtils.generateRequestId();
}
}
}
配置不是本文都重点(省略@configuration配置)
通俗的说:统一封装一个对象(上面的response)响应给调用方,调用方收到对象在解码出(实际用到有效数据为Response对象中的data)觉得多此一举?说实话我也觉得没必要封装这一统一对象,完全没必要。
正常情况
响应对象(自定义对象、Integer、Boolean、int、boolean等)都会封装统一Response对象返回
原因是调用了自定义的CustomResponseMessageConverter进行响应封装,外层调用可以正常解析,不会出现空指针
出现的问题:
微服务提供的接口返回String类型时,其实是没有封装统一Response对象返回,外层调用报空指针
问题出现在哪里?稍微熟悉Spring的都知道Spring封装了很多xxxConverter用来处理转化,
有个例外:当响应类型是String时调用的converter是:org.springframework.http.converter.StringHttpMessageConverter
并不是org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
来看看StringHttpMessageConverter相关源码
@Override
protected void writeInternal(String str, HttpOutputMessage outputMessage) throws IOException {
if (this.writeAcceptCharset) {
outputMessage.getHeaders().setAcceptCharset(getAcceptedCharsets());
}
Charset charset = getContentTypeCharset(outputMessage.getHeaders().getContentType());
StreamUtils.copy(str, charset, outputMessage.getBody());
}
直接将响应的字符串拷贝到“输出流”,并没有调用自定义的CustomResponseMessageConverter,这也就是为什么没有封装统一的response对象返回的原因。
好了问题找到了,给出解决思路:
1.不直接返回String,进行一次封装(封装之后已不是String对象,会调用自定义Converter)【不建议采用】
2.自定义一个Converter继承StringHttpMessageConverter重写writeInternal()方法,【不建议采用,麻烦】
这样做之后你会发现仍然错误,因为spring记录接口返回字符串的长度,最终会进行截取(虽然重写writeInternal()方法封装了Response,但是最终被截取了);因此还需要重写getContentLength()方法,调用方需要重写decode方法
写在最后,强力建议微服务架构中,各个中间服务提供者不要封装统一响应对象,当其中一些微服务没有封装统一响应对象返回(即直接返回响应),一些服务又统一封装响应对象时,处理起来很棘(e)手(xin),深恶痛绝此做法!!!