使用spring HttpMessageConverter 構(gòu)造一個通用返回結(jié)構(gòu)數(shù)據(jù)結(jié)構(gòu)全局配置

曾幾何時,被所有的業(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ù)自己的想法,利用這個做更多有趣的事

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容