曾几何时,被所有的业务代码的通用返回数据接口格式粗暴而简单的虐待过之后,不知道大家有没有想过去改造一下这些controller的接口,定义的接口纯粹一些,不需要使用controller 再包装一层返回结果
@GetMapping
public ReponseDTO doSomething(String param){
...
Object rtv = service.doSomething(param)
...
ResponseDTO rsp = new ResponseDTO();
rsp.setData(rvt)
return rsp;
}
上面的代码通常就是业务中需要返回给前端的统一格式,基本上全部接口都是以约定好的格式返回给前端渲染。在这里,我们可以改造一下,使得统一的返回格式,交给系统框架统一去处理,不需要在每个方法中做重复的东西。(程序员都讨厌做重复的东西,对吧)
那么应该如何去处理这个问题呢?
笔者一开始对于这个问题的想法是在AOP 上面去动手,例如写如下的代码:
public class MyBodyAdvice implements ResponseBodyAdvice {
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
....
ResponseDTO rsp = doSomeThingWithBody(body);
return rsp;
}
}
但是后来测试发现,并不可行,因为在调用链中,这个方法的返回值必须要跟传过来的body 类型保持一致。所以上面的代码会抛出 Cast Exception。
方案
后面想到springMVC 中支持的HttpMessageConverter,这个是可以在请求前和请求结果返回后 进行动手的地方,于是就开启下面的代码发掘之路了。
第一步,笔者开始看下converter中注入的方法,如果大家熟悉springboot 的话,都应该知道,可以在代码中直接配置一个converter bean,然后springboot就会自动加入到HttpMessageConverter List中了。
第二步,下面的问题是,我们怎么定义自己需要的HttpMessageConverter?
下面是接口的定义:
public interface HttpMessageConverter<T> {
boolean canRead(Class<?> clazz, MediaType mediaType);
boolean canWrite(Class<?> clazz, MediaType mediaType);
List<MediaType> getSupportedMediaTypes();
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
对于有现成的优秀产品,我们不需要重复造轮子,有一个大家经常用到的Converter,Jackson2HttpMessageConverter,观察之后,我们可以继承这个,然后实现我们需要的功能即可。面向对象的优势体现出来了 :)
于是笔者就先继承了这个类
public class MyResponseConverter extends AbstractJackson2HttpMessageConverter
首先这里需要注意的是,对于注册到Converter List 中的converter, 每次被调用的时候,spring需要判断是否这个converter能被使用,所以 看到上面的接口定义就可以看出,有一些地方是我们需要了解的。第一,canRead(),canWrite(), getSupportedMediaTypes()。
对于 AbstractJackson2HttpMessageConverter 的构造函数,我们需要指明支持的MediaType,这里默认支持json 格式,可以同时支持多种格式。
protected MyResponseConverter( ObjectMapper objectMapper) {
super(objectMapper, MediaType.APPLICATION_JSON);
}
然后继续追踪代码,在AbstractHttpMessageConverter 类中,实际执行write 操作的是 writeInternal
@Override
public final void write(final T t, 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(new StreamingHttpOutputMessage.Body() {
@Override
public void writeTo(final OutputStream outputStream) throws IOException {
writeInternal(t, new HttpOutputMessage() {
@Override
public OutputStream getBody() throws IOException {
return outputStream;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
});
}
});
}
else {
writeInternal(t, outputMessage);
outputMessage.getBody().flush();
}
}
所以我们只要重写这个方法,就可以偷偷干掉原来的放回值了哈哈,不说了,直接看代码
@Override
protected void writeInternal(Object originalOutputValue, Type originalType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
boolean toConvert = //这里根据自己的业务区定义是否转换
if (toConvert) {
ResponseDTO rsp = doSomeThingWithBody(body);
super.writeInternal(rsp, ResponseDTO.class, outputMessage);
return;
}
super.writeInternal(originalOutputValue, originalType, outputMessage);
}
写到这里就差不多,大家可以根据自己的想法,利用这个做更多有趣的事