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

曾幾何時(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è)做更多有趣的事

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末拿诸,一起剝皮案震驚了整個(gè)濱河市扒袖,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌亩码,老刑警劉巖季率,帶你破解...
    沈念sama閱讀 216,692評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異描沟,居然都是意外死亡飒泻,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門吏廉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來泞遗,“玉大人,你說我怎么就攤上這事席覆∈氛蓿” “怎么了?”我有些...
    開封第一講書人閱讀 162,995評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵佩伤,是天一觀的道長聊倔。 經(jīng)常有香客問我,道長生巡,這世上最難降的妖魔是什么耙蔑? 我笑而不...
    開封第一講書人閱讀 58,223評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮障斋,結(jié)果婚禮上纵潦,老公的妹妹穿的比我還像新娘。我一直安慰自己垃环,他們只是感情好邀层,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著遂庄,像睡著了一般寥院。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上涛目,一...
    開封第一講書人閱讀 51,208評(píng)論 1 299
  • 那天秸谢,我揣著相機(jī)與錄音,去河邊找鬼霹肝。 笑死估蹄,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的沫换。 我是一名探鬼主播臭蚁,決...
    沈念sama閱讀 40,091評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了垮兑?” 一聲冷哼從身側(cè)響起冷尉,我...
    開封第一講書人閱讀 38,929評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎系枪,沒想到半個(gè)月后雀哨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,346評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡私爷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評(píng)論 2 333
  • 正文 我和宋清朗相戀三年雾棺,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片衬浑。...
    茶點(diǎn)故事閱讀 39,739評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡垢村,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出嚎卫,到底是詐尸還是另有隱情,我是刑警寧澤宏榕,帶...
    沈念sama閱讀 35,437評(píng)論 5 344
  • 正文 年R本政府宣布拓诸,位于F島的核電站,受9級(jí)特大地震影響麻昼,放射性物質(zhì)發(fā)生泄漏奠支。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評(píng)論 3 326
  • 文/蒙蒙 一抚芦、第九天 我趴在偏房一處隱蔽的房頂上張望倍谜。 院中可真熱鬧,春花似錦叉抡、人聲如沸尔崔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽季春。三九已至,卻和暖如春消返,著一層夾襖步出監(jiān)牢的瞬間载弄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評(píng)論 1 269
  • 我被黑心中介騙來泰國打工撵颊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留宇攻,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,760評(píng)論 2 369
  • 正文 我出身青樓倡勇,卻偏偏與公主長得像逞刷,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容