我在Spring Boot 統(tǒng)一返回體中踩的坑

Spring Boot項目中我們可以通過RestControllerAdvice配合實現(xiàn)ResponseBodyAdvice<T>接口來保證Spring MVC接口具有統(tǒng)一的返回格式,以保證前端同學(xué)能夠封裝統(tǒng)一的數(shù)據(jù)接收工具顶别。但是很多網(wǎng)上的文章并沒有對實際開發(fā)中的細節(jié)作出更多的講解馋袜。今天胖哥就來分享一下我的采坑經(jīng)歷章鲤,也算作一個總結(jié)烤惊。

控制作用范圍

我記得在前面關(guān)于Swagger3的文章中提過梢为,如果我們不指定范圍將導(dǎo)致Swagger無法識別接口的元信息榴芳。因此如果你使用了Swagger必須指定其范圍罩句,這里你可以通過指定掃描包來指定其作用域:

@RestControllerAdvice("cn.felord.controller")

如果你的Spring MVC控制器有統(tǒng)一的父類控制器的話谍憔,

@RestController
@RequestMapping("/foo")
public class FooController extends BaseController {
 //todo 省略
}    

也可以這樣:

@RestControllerAdvice(assignableTypes = BaseController.class)

白名單

有些接口可能根據(jù)業(yè)務(wù)需要或者協(xié)議需要不能使用統(tǒng)一返回體匪蝙,例如支付的通知應(yīng)答主籍。這就需要一個類似白名單的機制來繞過統(tǒng)一返回體控制器通知類。我們可以借助于ResponseBodyAdvice<T>的下列方法實現(xiàn):

boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);

這個方法如果返回false就表示不執(zhí)行統(tǒng)一返回體的封裝邏輯逛球。這里我推薦注解實現(xiàn)千元。定義一個標記注解,可以定義在類上或者方法上:

@Documented
@Inherited
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface IgnoreRestBody {
}

然后上面的supports方法這樣實現(xiàn):

@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
    return !returnType.hasMethodAnnotation(IgnoreRestBody.class);
}

如果某個Controller下所有的方法都繞過颤绕,就把這個注解標記在控制器類上幸海;如果只想忽略某個方法上就把它標記在該方法上即可。

返回獨立字符串的問題

有些接口我們會返回一個字符串:

@GetMapping("/get")
public String getStr(){
    //返回了一個字符串
    return "felord.cn";
}

我們希望這個字符串被統(tǒng)一返回體處理奥务,類似這樣:

{
    code: 200,
    data: "felord.cn",
    msg: "返回成字符串",
}

但是你會發(fā)現(xiàn)并沒有達到期望的效果物独,會拋出類型轉(zhuǎn)換異常。這是因為當我們的Spring MVC接口返回數(shù)據(jù)時氯葬,會根據(jù)Content-Type來選擇一個HttpMessageConverter來處理挡篓,而字符串在不聲明Content-Type的情況下優(yōu)先使用StringHttpMessageConverter,就導(dǎo)致了轉(zhuǎn)換異常帚称,需要設(shè)定成MappingJackson2HttpMessageConverterJackson來處理官研,Spring MVC的對應(yīng)配置如下:

@Configuration(proxyBeanMethods = false)
public class SpringMvcConfiguration implements WebMvcConfigurer {
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        // 解決 String 統(tǒng)一封裝RestBody的問題
        converters.add(0, new MappingJackson2HttpMessageConverter());
    }
}

嗯,這樣就起效了世杀!你以為這樣就完了阀参?你會發(fā)現(xiàn)你的JSON序列化不按照你設(shè)置的策略執(zhí)行了。因為你new了一個而不是采用系統(tǒng)初始化的那個瞻坝。解決方法為蛛壳,將Spring IoC中的ObjectMapper注入到MappingJackson2HttpMessageConverter中去∷叮或者你使用Debug調(diào)試出系統(tǒng)默認的MappingJackson2HttpMessageConverter的位置衙荐,比如我的索引為7,就可以這樣配置:

@Configuration(proxyBeanMethods = false)
public class SpringMvcConfiguration implements WebMvcConfigurer {
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        // 解決 String 統(tǒng)一封裝RestBody的問題
        HttpMessageConverter<?> httpMessageConverter = converters.get(7);
        if (!(httpMessageConverter instanceof MappingJackson2HttpMessageConverter)) {
            // 確保正確浮创,如果有改動就重新debug
            throw new RuntimeException("MappingJackson2HttpMessageConverter is not here");
        }
        converters.add(0, httpMessageConverter);
    }
}

Data的類型問題

曾經(jīng)一個安卓開發(fā)同學(xué)說忧吟,你這統(tǒng)一結(jié)構(gòu)中的data如果是數(shù)組:

{
    code: 200,
    data: ['a','b'],
    msg: "返回成字符串",
}

后續(xù)如果data添加其它與數(shù)組沒有關(guān)系的屬性就不兼容了,你應(yīng)該保證這個data是個Map斩披。是的溜族,這也是問題,實際中發(fā)現(xiàn)不僅僅是數(shù)組垦沉,如果是int煌抒、long等原始類型或者String類型都面臨這種情況,需要加一個額外的判斷body是不是可能改變data類型的類型:

private boolean checkPrimitive(Object body) {
    Class<?> clazz = body.getClass();
    return clazz.isPrimitive()
            || clazz.isArray()
            || Collection.class.isAssignableFrom(clazz)
            || body instanceof Number
            || body instanceof Boolean
            || body instanceof Character
            || body instanceof String;
}

然后我們在ResponseBodyAdvice<T>實現(xiàn)中增加一個判斷:

      // 增強擴展性
        if (checkPrimitive(body)) {
            return RestBody.okData(Collections.singletonMap("result", body));
        }

就解決問題了厕倍。

總結(jié)

今天對Spring Boot中統(tǒng)一返回體的一些細節(jié)問題進行了分享寡壮,希望能夠幫助你解決一些實際開發(fā)中遇到的同樣問題。多多關(guān)注:碼農(nóng)小胖哥 分享更多有用的編程知識。

關(guān)注公眾號:碼農(nóng)小胖哥况既,獲取更多資訊

個人博客:https://felord.cn

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末这溅,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子棒仍,更是在濱河造成了極大的恐慌悲靴,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件降狠,死亡現(xiàn)場離奇詭異对竣,居然都是意外死亡庇楞,警方通過查閱死者的電腦和手機榜配,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來吕晌,“玉大人蛋褥,你說我怎么就攤上這事【Σ担” “怎么了烙心?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長乏沸。 經(jīng)常有香客問我淫茵,道長,這世上最難降的妖魔是什么蹬跃? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任匙瘪,我火速辦了婚禮,結(jié)果婚禮上蝶缀,老公的妹妹穿的比我還像新娘丹喻。我一直安慰自己,他們只是感情好翁都,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布碍论。 她就那樣靜靜地躺著,像睡著了一般柄慰。 火紅的嫁衣襯著肌膚如雪鳍悠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天坐搔,我揣著相機與錄音藏研,去河邊找鬼。 笑死薯蝎,一個胖子當著我的面吹牛遥倦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼袒哥,長吁一口氣:“原來是場噩夢啊……” “哼缩筛!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起堡称,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤瞎抛,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后却紧,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體桐臊,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年晓殊,在試婚紗的時候發(fā)現(xiàn)自己被綠了断凶。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡巫俺,死狀恐怖认烁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情介汹,我是刑警寧澤却嗡,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站嘹承,受9級特大地震影響窗价,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜叹卷,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一撼港、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧豪娜,春花似錦餐胀、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至鸣奔,卻和暖如春墨技,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背挎狸。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工扣汪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人锨匆。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓崭别,卻偏偏與公主長得像冬筒,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子茅主,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

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