亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.430618.com 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢(xún)客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

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

在前面的文章中(如何優(yōu)雅的實(shí)現(xiàn) Spring Boot 接口參數(shù)加密解密?),松哥已經(jīng)和大家介紹過(guò)如何對(duì)請(qǐng)求/響應(yīng)數(shù)據(jù)進(jìn)行預(yù)處理/二次處理,當(dāng)時(shí)我們使用了 ResponseBodyAdvice 和 RequestBodyAdvice。其中 ResponseBodyAdvice 可以實(shí)現(xiàn)對(duì)響應(yīng)數(shù)據(jù)的二次處理,可以在這里對(duì)響應(yīng)數(shù)據(jù)進(jìn)行加密/包裝等等操作。不過(guò)這不是唯一的方案,今天松哥要和大家介紹一種更加靈活的方案--HandlerMethodReturnValueHandler,我們一起來(lái)看看下。

1.HandlerMethodReturnValueHandler

HandlerMethodReturnValueHandler 的作用是對(duì)處理器的處理結(jié)果再進(jìn)行一次二次加工,這個(gè)接口里邊有兩個(gè)方法:

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

HandlerMethodReturnValueHandler 有很多默認(rèn)的實(shí)現(xiàn)類(lèi),我們來(lái)看下:

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

 

接下來(lái)我們來(lái)把這些實(shí)現(xiàn)類(lèi)的作用捋一捋:

ViewNameMethodReturnValueHandler

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

ViewMethodReturnValueHandler

這個(gè)處理器用來(lái)處理返回值為 View 的情況。如果返回值為 View,則將 View 設(shè)置給 mavContainer 的 view 屬性,同時(shí)判斷這個(gè) View 是不是重定向的 View,如果是,則設(shè)置 mavContainer 的 redirectModelScenario 屬性為 true,這是處理器返回重定向視圖的標(biāo)志。

MapMethodProcessor

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

StreamingResponseBodyReturnValueHandler

這個(gè)用來(lái)處理 StreamingResponseBody 或者 ResponseEntity<StreamingResponseBody> 類(lèi)型的返回值。

DeferredResultMethodReturnValueHandler

這個(gè)用來(lái)處理 DeferredResult、ListenableFuture 以及 CompletionStage 類(lèi)型的返回值,用于異步請(qǐng)求。

CallableMethodReturnValueHandler

處理 Callable 類(lèi)型的返回值,也是用于異步請(qǐng)求。

HttpHeadersReturnValueHandler

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

ModelMethodProcessor

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

ModelAttributeMethodProcessor

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

ServletModelAttributeMethodProcessor

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

ResponseBodyEmitterReturnValueHandler

這個(gè)用來(lái)處理返回值類(lèi)型為 ResponseBodyEmitter 的情況。

ModelAndViewMethodReturnValueHandler

這個(gè)用來(lái)處理返回值類(lèi)型為 ModelAndView 的情況,將返回值中的 Model 和 View 分別設(shè)置到 mavContainer 的相應(yīng)屬性上去。

ModelAndViewResolverMethodReturnValueHandler

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

AbstractMessageConverterMethodProcessor

這是一個(gè)抽象類(lèi),當(dāng)返回值需要通過(guò) HttpMessageConverter 進(jìn)行轉(zhuǎn)化的時(shí)候會(huì)用到它的子類(lèi)。這個(gè)抽象類(lèi)主要是定義了一些工具方法。

RequestResponseBodyMethodProcessor

這個(gè)用來(lái)處理添加了 @ResponseBody 注解的返回值類(lèi)型。

HttpEntityMethodProcessor

這個(gè)用來(lái)處理返回值類(lèi)型是 HttpEntity 并且不是 RequestEntity 的情況。

AsyncHandlerMethodReturnValueHandler

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

AsyncTaskMethodReturnValueHandler

這個(gè)用來(lái)處理返回值類(lèi)型為 WebAsyncTask 的情況。

HandlerMethodReturnValueHandlerComposite

看 Composite 就知道,這是一個(gè)組合處理器,沒(méi)啥好說(shuō)的。

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

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

2.ModelAndViewContainer

ModelAndViewContainer 就是一個(gè)數(shù)據(jù)穿梭巴士,在整個(gè)請(qǐng)求的過(guò)程中承擔(dān)著數(shù)據(jù)傳送的工作,從它的名字上我們可以看出來(lái)它里邊保存著 Model 和 View 兩種類(lèi)型的數(shù)據(jù),但是實(shí)際上可不止兩種,我們來(lái)看下 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;
}

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

  • defaultModel:默認(rèn)使用的 Model。當(dāng)我們?cè)诮涌趨?shù)重使用 Model、ModelMap 或者 Map 時(shí),最終使用的實(shí)現(xiàn)類(lèi)都是 BindingAwareModelMap,對(duì)應(yīng)的也都是 defaultModel。
  • redirectModel:重定向時(shí)候的 Model,如果我們?cè)诮涌趨?shù)中使用了 RedirectAttributes 類(lèi)型的參數(shù),那么最終會(huì)傳入 redirectModel。

可以看到,一共有兩個(gè) Model,兩個(gè) Model 到底用哪個(gè)呢?這個(gè)在 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 表示是否在重定向時(shí)忽略 defaultModel,所以這塊的邏輯是這樣:

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

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

  • view:返回的視圖。
  • status:HTTP 狀態(tài)碼。
  • noBinding:是否對(duì) @ModelAttribute(binding=true/false) 聲明的數(shù)據(jù)模型的相應(yīng)屬性進(jìn)行綁定。
  • bindingDisabled:不需要進(jìn)行數(shù)據(jù)綁定的屬性。
  • sessionStatus:SessionAttribute 使用完成的標(biāo)識(shí)。
  • requestHandled:請(qǐng)求處理完成的標(biāo)識(shí)(例如添加了 @ResponseBody 注解的接口,這個(gè)屬性為 true,請(qǐng)求就不會(huì)再去找視圖了)。

?

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

接下來(lái)我們也來(lái)自定義一個(gè) HandlerMethodReturnValueHandler,來(lái)感受一下 HandlerMethodReturnValueHandler 的基本用法。

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

假設(shè)我有這樣一個(gè)需求:我想在原始的返回?cái)?shù)據(jù)外面再包裹一層,舉個(gè)簡(jiǎn)單例子,本來(lái)接口是下面這樣:

@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"}}

就這樣一個(gè)簡(jiǎn)單需求,我們一起來(lái)看下怎么實(shí)現(xiàn)。

3.1 RequestResponseBodyMethodProcessor

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

我們來(lái)稍微看下:

@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);
}
  • supportsReturnType:從這個(gè)方法中可以看到,這里支持有 @ResponseBody 注解的接口。
  • handleReturnValue:這是具體的處理邏輯,首先 mavContainer 中設(shè)置 requestHandled 屬性為 true,表示這里處理完成后就完了,以后不用再去找視圖了,然后分別獲取 inputMessage 和 outputMessage,調(diào)用 writeWithMessageConverters 方法進(jìn)行輸出,writeWithMessageConverters 方法是在父類(lèi)中定義的方法,這個(gè)方法比較長(zhǎng),核心邏輯就是調(diào)用確定輸出數(shù)據(jù)、確定 MediaType,然后通過(guò) HttpMessageConverter 將 JSON 數(shù)據(jù)寫(xiě)出去即可。

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

3.2 具體實(shí)現(xiàn)

首先自定義一個(gè) 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);
    }
}

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

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

@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 實(shí)現(xiàn) InitializingBean 接口,afterPropertiesSet 方法會(huì)被自動(dòng)調(diào)用,在該方法中,我們將 RequestMappingHandlerAdapter 中已經(jīng)配置好的 HandlerMethodReturnValueHandler 拎出來(lái)挨個(gè)檢查,如果類(lèi)型是 RequestResponseBodyMethodProcessor,則重新構(gòu)建,用我們自定義的 MyHandlerMethodReturnValueHandler 代替它,最后給 requestMappingHandlerAdapter 重新設(shè)置 HandlerMethodReturnValueHandler 即可。

最后再提供一個(gè)測(cè)試接口:

@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;
    //省略其他
}

配置完成后,就可以啟動(dòng)項(xiàng)目啦。

項(xiàng)目啟動(dòng)成功后,訪問(wèn) /user 接口,如下:

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

 

完美。

4.小結(jié)

其實(shí)統(tǒng)一 API 接口響應(yīng)格式辦法很多,可以參考松哥之前分享的 如何優(yōu)雅的實(shí)現(xiàn) Spring Boot 接口參數(shù)加密解密?,也可以使用本文中的方案,甚至也可以自定義過(guò)濾器實(shí)現(xiàn)。

本文的內(nèi)容稍微有點(diǎn)多,不知道大家有沒(méi)有發(fā)現(xiàn)松哥最近發(fā)了很多 SpringMVC 源碼相關(guān)的東西,沒(méi)錯(cuò),本文其實(shí)是松哥 SpringMVC 源碼解析的一部分,為了源碼解析不那么枯燥,所以強(qiáng)行加了一個(gè)案例進(jìn)來(lái),祝小伙伴們學(xué)習(xí)愉快~

分享到:
標(biāo)簽:接口 API
用戶(hù)無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定