曾幾何時,被所有的業(yè)務(wù)代碼的通用返回數(shù)據(jù)接口格式粗暴而簡單的虐待過之后,不知道大家有沒有想過去改造一下這些controller的接口,定義的接口純粹一些,不需要使用controller 再包裝一層返回結(jié)果
@GetMapping
public ReponseDTO doSomething(String param){
...
Object rtv = service.doSomething(param)
...
ResponseDTO rsp = new ResponseDTO();
rsp.setData(rvt)
return rsp;
}
上面的代碼通常就是業(yè)務(wù)中需要返回給前端的統(tǒng)一格式,基本上全部接口都是以約定好的格式返回給前端渲染。在這里,我們可以改造一下,使得統(tǒng)一的返回格式,交給系統(tǒng)框架統(tǒng)一去處理,不需要在每個方法中做重復(fù)的東西。(程序員都討厭做重復(fù)的東西,對吧)
那么應(yīng)該如何去處理這個問題呢?
筆者一開始對于這個問題的想法是在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;
}
}
但是后來測試發(fā)現(xiàn),并不可行,因為在調(diào)用鏈中,這個方法的返回值必須要跟傳過來的body 類型保持一致。所以上面的代碼會拋出 Cast Exception。
方案
后面想到springMVC 中支持的HttpMessageConverter,這個是可以在請求前和請求結(jié)果返回后 進行動手的地方,于是就開啟下面的代碼發(fā)掘之路了。
第一步,筆者開始看下converter中注入的方法,如果大家熟悉springboot 的話,都應(yīng)該知道,可以在代碼中直接配置一個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;
}
對于有現(xiàn)成的優(yōu)秀產(chǎn)品,我們不需要重復(fù)造輪子,有一個大家經(jīng)常用到的Converter,Jackson2HttpMessageConverter,觀察之后,我們可以繼承這個,然后實現(xiàn)我們需要的功能即可。面向?qū)ο蟮膬?yōu)勢體現(xiàn)出來了 :)
于是筆者就先繼承了這個類
public class MyResponseConverter extends AbstractJackson2HttpMessageConverter
首先這里需要注意的是,對于注冊到Converter List 中的converter, 每次被調(diào)用的時候,spring需要判斷是否這個converter能被使用,所以 看到上面的接口定義就可以看出,有一些地方是我們需要了解的。第一,canRead(),canWrite(), getSupportedMediaTypes()。
對于 AbstractJackson2HttpMessageConverter 的構(gòu)造函數(shù),我們需要指明支持的MediaType,這里默認支持json 格式,可以同時支持多種格式。
protected MyResponseConverter( ObjectMapper objectMapper) {
super(objectMapper, MediaType.APPLICATION_JSON);
}
然后繼續(xù)追蹤代碼,在AbstractHttpMessageConverter 類中,實際執(zhí)行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 = //這里根據(jù)自己的業(yè)務(wù)區(qū)定義是否轉(zhuǎn)換
if (toConvert) {
ResponseDTO rsp = doSomeThingWithBody(body);
super.writeInternal(rsp, ResponseDTO.class, outputMessage);
return;
}
super.writeInternal(originalOutputValue, originalType, outputMessage);
}
寫到這里就差不多,大家可以根據(jù)自己的想法,利用這個做更多有趣的事