曾幾何時(shí)疚漆,被所有的業(yè)務(wù)代碼的通用返回?cái)?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)一去處理伸蚯,不需要在每個(gè)方法中做重復(fù)的東西摩渺。(程序員都討厭做重復(fù)的東西,對(duì)吧)
那么應(yīng)該如何去處理這個(gè)問題呢剂邮?
筆者一開始對(duì)于這個(gè)問題的想法是在AOP 上面去動(dòng)手摇幻,例如寫如下的代碼:
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;
}
}
但是后來測(cè)試發(fā)現(xiàn),并不可行,因?yàn)樵谡{(diào)用鏈中绰姻,這個(gè)方法的返回值必須要跟傳過來的body 類型保持一致枉侧。所以上面的代碼會(huì)拋出 Cast Exception。
方案
后面想到springMVC 中支持的HttpMessageConverter龙宏,這個(gè)是可以在請(qǐng)求前和請(qǐng)求結(jié)果返回后 進(jìn)行動(dòng)手的地方棵逊,于是就開啟下面的代碼發(fā)掘之路了。
第一步银酗,筆者開始看下converter中注入的方法,如果大家熟悉springboot 的話徒像,都應(yīng)該知道黍特,可以在代碼中直接配置一個(gè)converter bean,然后springboot就會(huì)自動(dòng)加入到HttpMessageConverter List中了锯蛀。
第二步灭衷,下面的問題是,我們?cè)趺炊x自己需要的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;
}
對(duì)于有現(xiàn)成的優(yōu)秀產(chǎn)品旁涤,我們不需要重復(fù)造輪子翔曲,有一個(gè)大家經(jīng)常用到的Converter,Jackson2HttpMessageConverter劈愚,觀察之后瞳遍,我們可以繼承這個(gè),然后實(shí)現(xiàn)我們需要的功能即可菌羽。面向?qū)ο蟮膬?yōu)勢(shì)體現(xiàn)出來了 :)
于是筆者就先繼承了這個(gè)類
public class MyResponseConverter extends AbstractJackson2HttpMessageConverter
首先這里需要注意的是掠械,對(duì)于注冊(cè)到Converter List 中的converter, 每次被調(diào)用的時(shí)候注祖,spring需要判斷是否這個(gè)converter能被使用猾蒂,所以 看到上面的接口定義就可以看出,有一些地方是我們需要了解的是晨。第一肚菠,canRead(),canWrite(), getSupportedMediaTypes()。
對(duì)于 AbstractJackson2HttpMessageConverter 的構(gòu)造函數(shù)罩缴,我們需要指明支持的MediaType,這里默認(rèn)支持json 格式蚊逢,可以同時(shí)支持多種格式。
protected MyResponseConverter( ObjectMapper objectMapper) {
super(objectMapper, MediaType.APPLICATION_JSON);
}
然后繼續(xù)追蹤代碼靴庆,在AbstractHttpMessageConverter 類中时捌,實(shí)際執(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();
}
}
所以我們只要重寫這個(gè)方法,就可以偷偷干掉原來的放回值了哈哈炉抒,不說了奢讨,直接看代碼
@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ù)自己的想法,利用這個(gè)做更多有趣的事