在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è)定成MappingJackson2HttpMessageConverter
用Jackson來處理官研,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)小胖哥况既,獲取更多資訊