Spring Boot 中如何統(tǒng)一 API 接口響應格式?

今天又要給大家介紹一個 Spring Boot 中的組件--HandlerMethodReturnValueHandler。

今天要和大家介紹一種更加靈活的方案--HandlerMethodReturnValueHandler,我們一起來看看下。

1.HandlerMethodReturnValueHandler

HandlerMethodReturnValueHandler 的作用是對處理器的處理結果再進行一次二次加工,這個接口里邊有兩個方法:

public interface HandlerMethodReturnValueHandler {
  boolean supportsReturnType(MethodParameter returnType);
  void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
          ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
}
  • supportsReturnType:這個處理器是否支持相應的返回值類型。
  • handleReturnValue:對方法返回值進行處理。

HandlerMethodReturnValueHandler 有很多默認的實現(xiàn)類,我們來看下:

image

接下來我們來把這些實現(xiàn)類的作用捋一捋:

ViewNameMethodReturnValueHandler

這個處理器用來處理返回值為 void 和 String 的情況。如果返回值為 void,則不做任何處理。如果返回值為 String,則將 String 設置給 mavContainer 的 viewName 屬性,同時判斷這個 String 是不是重定向的 String,如果是,則設置 mavContainer 的 redirectModelScenario 屬性為 true,這是處理器返回重定向視圖的標志。

ViewMethodReturnValueHandler

這個處理器用來處理返回值為 View 的情況。如果返回值為 View,則將 View 設置給 mavContainer 的 view 屬性,同時判斷這個 View 是不是重定向的 View,如果是,則設置 mavContainer 的 redirectModelScenario 屬性為 true,這是處理器返回重定向視圖的標志。

MapMethodProcessor

這個處理器用來處理返回值類型為 Map 的情況,具體的處理方案就是將 map 添加到 mavContainer 的 model 屬性中。

StreamingResponseBodyReturnValueHandler

這個用來處理 StreamingResponseBody 或者 ResponseEntity<StreamingResponseBody> 類型的返回值。

DeferredResultMethodReturnValueHandler

這個用來處理 DeferredResult、ListenableFuture 以及 CompletionStage 類型的返回值,用于異步請求。

CallableMethodReturnValueHandler

處理 Callable 類型的返回值,也是用于異步請求。

HttpHeadersReturnValueHandler

這個用來處理 HttpHeaders 類型的返回值,具體處理方式就是將 mavContainer 中的 requestHandled 屬性設置為 true,該屬性是請求是否已經(jīng)處理完成的標志(如果處理完了,就到此為止,后面不會再去找視圖了),然后將 HttpHeaders 添加到響應頭中。

ModelMethodProcessor

這個用來處理返回值類型為 Model 的情況,具體的處理方式就是將 Model 添加到 mavContainer 的 model 上。

ModelAttributeMethodProcessor

這個用來處理添加了 @ModelAttribute 注解的返回值類型,如果 annotaionNotRequired 屬性為 true,也可以用來處理其他非通用類型的返回值。

ServletModelAttributeMethodProcessor

同上,該類只是修改了參數(shù)解析方式。

ResponseBodyEmitterReturnValueHandler

這個用來處理返回值類型為 ResponseBodyEmitter 的情況。

ModelAndViewMethodReturnValueHandler

這個用來處理返回值類型為 ModelAndView 的情況,將返回值中的 Model 和 View 分別設置到 mavContainer 的相應屬性上去。

ModelAndViewResolverMethodReturnValueHandler

這個的 supportsReturnType 方法返回 true,即可以處理所有類型的返回值,這個一般放在最后兜底。

AbstractMessageConverterMethodProcessor

這是一個抽象類,當返回值需要通過 HttpMessageConverter 進行轉(zhuǎn)化的時候會用到它的子類。這個抽象類主要是定義了一些工具方法。

RequestResponseBodyMethodProcessor

這個用來處理添加了 @ResponseBody 注解的返回值類型。

HttpEntityMethodProcessor

這個用來處理返回值類型是 HttpEntity 并且不是 RequestEntity 的情況。

AsyncHandlerMethodReturnValueHandler

這是一個空接口,暫未發(fā)現(xiàn)典型使用場景。

AsyncTaskMethodReturnValueHandler

這個用來處理返回值類型為 WebAsyncTask 的情況。

HandlerMethodReturnValueHandlerComposite

看 Composite 就知道,這是一個組合處理器,沒啥好說的。

這個就是系統(tǒng)默認定義的 HandlerMethodReturnValueHandler。

那么在上面的介紹中,大家看到反復涉及到一個組件 mavContainer,這個我也要和大家介紹一下。

2.ModelAndViewContainer

ModelAndViewContainer 就是一個數(shù)據(jù)穿梭巴士,在整個請求的過程中承擔著數(shù)據(jù)傳送的工作,從它的名字上我們可以看出來它里邊保存著 Model 和 View 兩種類型的數(shù)據(jù),但是實際上可不止兩種,我們來看下 ModelAndViewContainer 的定義:

public class ModelAndViewContainer {
    private boolean ignoreDefaultModelOnRedirect = false;
    @Nullable
    private Object view;
    private final ModelMap defaultModel = new BindingAwareModelMap();
    @Nullable
    private ModelMap redirectModel;
    private boolean redirectModelScenario = false;
    @Nullable
    private HttpStatus status;
    private final Set<String> noBinding = new HashSet<>(4);
    private final Set<String> bindingDisabled = new HashSet<>(4);
    private final SessionStatus sessionStatus = new SimpleSessionStatus();
    private boolean requestHandled = false;
}

把這幾個屬性理解了,基本上也就整明白 ModelAndViewContainer 的作用了:

  • defaultModel:默認使用的 Model。當我們在接口參數(shù)重使用 Model、ModelMap 或者 Map 時,最終使用的實現(xiàn)類都是 BindingAwareModelMap,對應的也都是 defaultModel。
  • redirectModel:重定向時候的 Model,如果我們在接口參數(shù)中使用了 RedirectAttributes 類型的參數(shù),那么最終會傳入 redirectModel。

可以看到,一共有兩個 Model,兩個 Model 到底用哪個呢?這個在 getModel 方法中根據(jù)條件返回合適的 Model:

public ModelMap getModel() {
    if (useDefaultModel()) {
        return this.defaultModel;
    }
    else {
        if (this.redirectModel == null) {
            this.redirectModel = new ModelMap();
        }
        return this.redirectModel;
    }
}
private boolean useDefaultModel() {
    return (!this.redirectModelScenario || (this.redirectModel == null && !this.ignoreDefaultModelOnRedirect));
}

這里 redirectModelScenario 表示處理器是否返回 redirect 視圖;ignoreDefaultModelOnRedirect 表示是否在重定向時忽略 defaultModel,所以這塊的邏輯是這樣:

  1. 如果 redirectModelScenario 為 true,即處理器返回的是一個重定向視圖,那么使用 redirectModel。如果 redirectModelScenario 為 false,即處理器返回的不是一個重定向視圖,那么使用 defaultModel。
  2. 如果 redirectModel 為 null,并且 ignoreDefaultModelOnRedirect 為 false,則使用 redirectModel,否則使用 defaultModel。

接下來還剩下如下一些參數(shù):

@ResponseBody

這個 ModelAndViewContainer 小伙伴們權且做一個了解,松哥在后面的源碼分析中,還會和大家再次聊到這個組件。

接下來我們也來自定義一個 HandlerMethodReturnValueHandler,來感受一下 HandlerMethodReturnValueHandler 的基本用法。

3.API 接口數(shù)據(jù)包裝

假設我有這樣一個需求:我想在原始的返回數(shù)據(jù)外面再包裹一層,舉個簡單例子,本來接口是下面這樣:

@RestController
public class UserController {
    @GetMapping("/user")
    public User getUserByUsername(String username) {
        User user = new User();
        user.setUsername(username);
        user.setAddress("www.javaboy.org");
        return user;
    }
}

返回的數(shù)據(jù)格式是下面這樣:

{"username":"javaboy","address":"www.javaboy.org"}

現(xiàn)在我希望返回的數(shù)據(jù)格式變成下面這樣:

{"status":"ok","data":{"username":"javaboy","address":"www.javaboy.org"}}

就這樣一個簡單需求,我們一起來看下怎么實現(xiàn)。

3.1 RequestResponseBodyMethodProcessor

在開始定義之前,先給大家介紹一下 RequestResponseBodyMethodProcessor,這是 HandlerMethodReturnValueHandler 的實現(xiàn)類之一,這個主要用來處理返回 JSON 的情況。

我們來稍微看下:

@Override
public boolean supportsReturnType(MethodParameter returnType) {
    return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
            returnType.hasMethodAnnotation(ResponseBody.class));
}
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
        ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
        throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
    mavContainer.setRequestHandled(true);
    ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
    ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
    writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
@ResponseBody

有了上面的知識儲備之后,接下來我們就可以自己實現(xiàn)了。

3.2 具體實現(xiàn)

首先自定義一個 HandlerMethodReturnValueHandler:

public class MyHandlerMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
    private HandlerMethodReturnValueHandler handler;

    public MyHandlerMethodReturnValueHandler(HandlerMethodReturnValueHandler handler) {
        this.handler = handler;
    }

    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        return handler.supportsReturnType(returnType);
    }

    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        Map<String, Object> map = new HashMap<>();
        map.put("status", "ok");
        map.put("data", returnValue);
        handler.handleReturnValue(map, returnType, mavContainer, webRequest);
    }
}

由于我們要做的功能其實是在 RequestResponseBodyMethodProcessor 基礎之上實現(xiàn)的,因為支持 @ResponseBody ,輸出 JSON 那些東西都不變,我們只是在輸出之前修改一下數(shù)據(jù)而已。所以我這里直接定義了一個屬性 HandlerMethodReturnValueHandler,這個屬性的實例就是 RequestResponseBodyMethodProcessor,supportsReturnType 方法就按照 RequestResponseBodyMethodProcessor 的要求來,在 handleReturnValue 方法中,我們先對返回值進行一個預處理,然后調(diào)用 RequestResponseBodyMethodProcessor#handleReturnValue 方法繼續(xù)輸出 JSON 即可。

接下來就是配置 MyHandlerMethodReturnValueHandler 使之生效了。由于 SpringMVC 中 HandlerAdapter 在加載的時候已經(jīng)配置了 HandlerMethodReturnValueHandler(這塊松哥以后會和大家分析相關源碼),所以我們可以通過如下方式對已經(jīng)配置好的 RequestMappingHandlerAdapter 進行修改,如下:

@Configuration
public class ReturnValueConfig implements InitializingBean {
    @Autowired
    RequestMappingHandlerAdapter requestMappingHandlerAdapter;
    @Override
    public void afterPropertiesSet() throws Exception {
        List<HandlerMethodReturnValueHandler> originHandlers = requestMappingHandlerAdapter.getReturnValueHandlers();
        List<HandlerMethodReturnValueHandler> newHandlers = new ArrayList<>(originHandlers.size());
        for (HandlerMethodReturnValueHandler originHandler : originHandlers) {
            if (originHandler instanceof RequestResponseBodyMethodProcessor) {
                newHandlers.add(new MyHandlerMethodReturnValueHandler(originHandler));
            }else{
                newHandlers.add(originHandler);
            }
        }
        requestMappingHandlerAdapter.setReturnValueHandlers(newHandlers);
    }
}

自定義 ReturnValueConfig 實現(xiàn) InitializingBean 接口,afterPropertiesSet 方法會被自動調(diào)用,在該方法中,我們將 RequestMappingHandlerAdapter 中已經(jīng)配置好的 HandlerMethodReturnValueHandler 拎出來挨個檢查,如果類型是 RequestResponseBodyMethodProcessor,則重新構建,用我們自定義的 MyHandlerMethodReturnValueHandler 代替它,最后給 requestMappingHandlerAdapter 重新設置 HandlerMethodReturnValueHandler 即可。

最后再提供一個測試接口:

@RestController
public class UserController {
    @GetMapping("/user")
    public User getUserByUsername(String username) {
        User user = new User();
        user.setUsername(username);
        user.setAddress("www.javaboy.org");
        return user;
    }
}
public class User {
    private String username;
    private String address;
    //省略其他
}

配置完成后,就可以啟動項目啦。

項目啟動成功后,訪問 /user 接口,如下:

image

完美。

4.小結

其實統(tǒng)一 API 接口響應格式辦法很多,

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

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

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