亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

ResponseBodyAdvice的使用原理源碼解析

 更新時間:2023年03月13日 14:31:19   作者:半夏之沫  
這篇文章主要為大家介紹了ResponseBodyAdvice的使用原理源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

前言

ResponseBodyAdvice接口可以在將handler方法的返回值寫入response前對返回值進行處理,例如將返回值封裝成一個與客戶端約定好的對象以便于客戶端處理響應(yīng)數(shù)據(jù)。本篇文章將學習如何使用ResponseBodyAdvice以及其實現(xiàn)原理。

SpringBoot版本:2.4.1

正文

一. ResponseBodyAdvice的使用

假如已經(jīng)存在一個Controller,如下所示。

@RestController
public class DemoController {
    @RequestMapping(value = "/api/v1/demo1/getdefault", method = RequestMethod.GET)
    public ResponseEntity<Demo1> getDefaultDemo1() {
        return new ResponseEntity<>(Demo1.defaultDemo1, HttpStatus.INTERNAL_SERVER_ERROR);
    }
    @RequestMapping(value = "/api/v1/demo2/getdefault", method = RequestMethod.GET)
    public Demo2 getDefaultDemo2() {
        return Demo2.defaultDemo2;
    }
}
public class Demo1 {
    private int id;
    private String name;
    public static Demo1 defaultDemo1 = new Demo1(1, "Admin");
    public Demo1() {}
    public Demo1(int id, String name) {		this.id = id;
		this.name = name;
    }
    // 省略getter和setter
}
public class Demo2 {
    private int id;
    private String desc;
    public static Demo2 defaultDemo2 = new Demo2(1, "Root");
    public Demo2() {}
    public Demo2(int id, String desc) {
		this.id = id;
        this.desc = desc;
    }
    // 省略getter和setter
}

上述Controller中有兩個方法,并且返回值分別為ResponseEntity<Demo1>和Demo2。此時客戶端收到響應(yīng)之后,針對響應(yīng)體的處理變得十分不方便,如果增加更多的方法,并且返回值都不相同,那么客戶端將需要根據(jù)不同的請求來特定的處理響應(yīng)體。因此為了方便客戶端處理響應(yīng)數(shù)據(jù),服務(wù)器端專門創(chuàng)建了一個返回結(jié)果類ReturnResult,并且規(guī)定服務(wù)器端的所有handler方法執(zhí)行后往response中寫入的響應(yīng)體都必須為ReturnResult。在這種情況下,使用ResponseBodyAdvice可以在不修改已有業(yè)務(wù)代碼的情況下輕松實現(xiàn)上述需求。假設(shè)自定義的返回結(jié)果類ReturnResult如下所示。

public class ReturnResult<T> {
    private int statusCode;
    private T body;
    public ReturnResult() {}
    public ReturnResult(T body) {
        this.body = body;
    }
    // 省略getter和setter
}

ReturnResult的body就是原本需要寫入response的響應(yīng)內(nèi)容,現(xiàn)在整個ReturnResult為需要寫入response的響應(yīng)內(nèi)容,相當于ReturnResult對handler方法的返回值進行了一層封裝。

現(xiàn)在創(chuàng)建一個ReturnResultAdvice類并實現(xiàn)ResponseBodyAdvice接口,如下所示。

@ControllerAdvice
public class ReturnResultAdvice implements ResponseBodyAdvice<Object> {
    @Override
    public boolean supports(@Nullable MethodParameter returnType, @Nullable Class converterType) {
        return true;
    }
    @Override
    public Object beforeBodyWrite(Object body, @Nullable MethodParameter returnType,
                                  @Nullable MediaType selectedContentType, @Nullable Class selectedConverterType,
                                  @Nullable ServerHttpRequest request, @Nullable ServerHttpResponse response) {
        if (body == null) {
            return null;
        }
        if (body instanceof ReturnResult) {
            return body;
        }
        return new ReturnResult<>(body);
    }
}

ReturnResultAdvice的beforeBodyWrite() 方法會在handler方法返回值寫入response前被調(diào)用。

此時調(diào)用DemoController的接口,會發(fā)現(xiàn)響應(yīng)數(shù)據(jù)結(jié)構(gòu)統(tǒng)一為ReturnResult。

小節(jié):由@ControllerAdvice注解修飾并實現(xiàn)ResponseBodyAdvice接口的類所實現(xiàn)的beforeBodyWrite()方法會在handler方法返回值寫入response前被調(diào)用,并且handler方法返回值會作為入?yún)魅?code>beforeBodyWrite(),從而可以在返回值寫入response前對返回值進行一些定制操作,例如對返回值進行一層封裝。

二. ResponseBodyAdvice的原理

首先說明一下為什么第一小節(jié)中DemoController的getDefaultDemo1() 方法的返回值類型為ResponseEntity<Demo1>,但是實際往response寫的響應(yīng)體內(nèi)容為ResponseEntity中的body。首先所有ResponseBodyAdvice接口的調(diào)用是發(fā)生在AbstractMessageConverterMethodProcessor的writeWithMessageConverters() 方法中,這個方法的聲明如下所示。

protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
            ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
            throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException;

其中value就是需要寫入響應(yīng)體的值,同時也是ResponseBodyAdvice要處理的值。然后如果handler方法的返回值是非ResponseEntity對象且handler方法由@ResponseBody注解修飾,那么writeWithMessageConverters() 方法的調(diào)用發(fā)生在RequestResponseBodyMethodProcessor#handleReturnValue方法中;

如果handler方法的返回值是ResponseEntity對象,那么writeWithMessageConverters() 方法的調(diào)用發(fā)生在HttpEntityMethodProcessor#handleReturnValue中,分別看一下在這兩個方法中調(diào)用writeWithMessageConverters() 時傳入的參數(shù),就可以解釋之前的疑問了。

RequestResponseBodyMethodProcessor#handleReturnValue

public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
        ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
        throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
    ......
    writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}

HttpEntityMethodProcessor#handleReturnValue()

public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
        ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
    ......
    HttpEntity<?> responseEntity = (HttpEntity<?>) returnValue;
    ......
    writeWithMessageConverters(responseEntity.getBody(), returnType, inputMessage, outputMessage);
    ......
}

現(xiàn)在正式開始對ResponseBodyAdvice的原理進行分析。

已知所有ResponseBodyAdvice接口的調(diào)用是發(fā)生在AbstractMessageConverterMethodProcessor的writeWithMessageConverters() 方法中,其部分源碼如下所示。

protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
        ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
        throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
    ......
    if (selectedMediaType != null) {
        selectedMediaType = selectedMediaType.removeQualityValue();
        for (HttpMessageConverter<?> converter : this.messageConverters) {
            GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
                    (GenericHttpMessageConverter<?>) converter : null);
            if (genericConverter != null ?
                    ((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
                    converter.canWrite(valueType, selectedMediaType)) {
                // ResponseBodyAdvice的調(diào)用發(fā)生在這里
                body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
                        (Class<? extends HttpMessageConverter<?>>) converter.getClass(),
                        inputMessage, outputMessage);
                if (body != null) {
                    Object theBody = body;
                    LogFormatUtils.traceDebug(logger, traceOn ->
                            "Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
                    addContentDispositionHeader(inputMessage, outputMessage);
                    if (genericConverter != null) {
                        genericConverter.write(body, targetType, selectedMediaType, outputMessage);
                    }
                    else {
                        ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
                    }
                }
                else {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Nothing to write: null body");
                    }
                }
                return;
            }
        }
    }
    ......
}

AbstractMessageConverterMethodProcessor的getAdvice() 方法會返回其在構(gòu)造函數(shù)中加載好的RequestResponseBodyAdviceChain對象,下面看一下RequestResponseBodyAdviceChain的beforeBodyWrite() 方法。

public Object beforeBodyWrite(@Nullable Object body, MethodParameter returnType, MediaType contentType,
        Class<? extends HttpMessageConverter<?>> converterType,
        ServerHttpRequest request, ServerHttpResponse response) {
    return processBody(body, returnType, contentType, converterType, request, response);
}
private <T> Object processBody(@Nullable Object body, MethodParameter returnType, MediaType contentType,
        Class<? extends HttpMessageConverter<?>> converterType,
        ServerHttpRequest request, ServerHttpResponse response) {
    // 從加載好的ResponseBodyAdvice中獲取適用于當前handler的ResponseBodyAdvice
    for (ResponseBodyAdvice<?> advice : getMatchingAdvice(returnType, ResponseBodyAdvice.class)) {
        if (advice.supports(returnType, converterType)) {
            // 執(zhí)行ResponseBodyAdvice的beforeBodyWrite()方法以處理handler方法返回值
            body = ((ResponseBodyAdvice<T>) advice).beforeBodyWrite((T) body, returnType,
                    contentType, converterType, request, response);
        }
    }
    return body;
}
private <A> List<A> getMatchingAdvice(MethodParameter parameter, Class<? extends A> adviceType) {
    // 獲取ResponseBodyAdvice集合
    List<Object> availableAdvice = getAdvice(adviceType);
    if (CollectionUtils.isEmpty(availableAdvice)) {
        return Collections.emptyList();
    }
    List<A> result = new ArrayList<>(availableAdvice.size());
    for (Object advice : availableAdvice) {
        // 判斷ResponseBodyAdvice是否由@ControllerAdvice注解修飾
        if (advice instanceof ControllerAdviceBean) {
            ControllerAdviceBean adviceBean = (ControllerAdviceBean) advice;
            // 判斷ResponseBodyAdvice是否適用于當前handler
            if (!adviceBean.isApplicableToBeanType(parameter.getContainingClass())) {
                continue;
            }
            advice = adviceBean.resolveBean();
        }
        if (adviceType.isAssignableFrom(advice.getClass())) {
            result.add((A) advice);
        }
    }
    return result;
}

在RequestResponseBodyAdviceChain中,beforeBodyWrite() 方法調(diào)用了processBody() 方法,processBody() 方法會遍歷所有加載好并且適用于當前handler的ResponseBodyAdvice并執(zhí)行,至此,所有由@ControllerAdvice注解修飾的ResponseBodyAdvice接口會在這里執(zhí)行。

小節(jié):由@ControllerAdvice注解修飾的ResponseBodyAdvice接口會被SpringMVC框架加載到RequestResponseBodyMethodProcessorHttpEntityMethodProcessor這兩個返回值處理器中,當這兩個返回值處理器將返回值寫入response前,適用于當前handler的ResponseBodyAdvice接口會被調(diào)用,從而可以完成對返回值的定制化改造。

三. ResponseBodyAdvice的加載

由第二小節(jié)可知,正是因為RequestResponseBodyMethodProcessor和HttpEntityMethodProcessor這兩個返回值處理器會將由@ControllerAdvice注解修飾的ResponseBodyAdvice接口加載,才能夠?qū)崿F(xiàn)將返回值寫入response前調(diào)用這些ResponseBodyAdvice接口對返回值進行一些操作。那么本小節(jié)將對esponseBodyAdvice接口的加載進行學習。

首先給出結(jié)論:ResponseBodyAdvice的加載發(fā)生在RequestMappingHandlerAdapterafterPropertiesSet()方法中。

已知,RequestMappingHandlerAdapter實現(xiàn)了InitializingBean接口,因此RequestMappingHandlerAdapter實現(xiàn)了afterPropertiesSet() 方法。該方法實現(xiàn)如下。

public void afterPropertiesSet() {
    // 加載ControllerAdviceBean相關(guān)內(nèi)容(同時就會將由@ControllerAdvice注解修飾的ResponseBodyAdvice接口加載)
    initControllerAdviceCache();
    if (this.argumentResolvers == null) {
        List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
        this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    }
    if (this.initBinderArgumentResolvers == null) {
        List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
        this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    }
    if (this.returnValueHandlers == null) {
        // 獲取返回值處理器,在這里就會完成RequestResponseBodyMethodProcessor和HttpEntityMethodProcessor的初始化,初始化的同時就會完成ResponseBodyAdvice接口的加載
        List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
        this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
    }
}

上述實現(xiàn)中,initControllerAdviceCache() 方法會加載ControllerAdviceBean相關(guān)內(nèi)容到RequestMappingHandlerAdapter中,這其中就包含由@ControllerAdvice注解修飾的ResponseBodyAdvice接口。然后在getDefaultReturnValueHandlers() 方法中會創(chuàng)建返回值處理器,在創(chuàng)建RequestResponseBodyMethodProcessor和HttpEntityMethodProcessor時會使用加載好的ResponseBodyAdvice接口完成這兩個返回值處理器的初始化。上述兩個方法的部分源碼如下所示。

initControllerAdviceCache()

private void initControllerAdviceCache() {
    if (getApplicationContext() == null) {
        return;
    }
    // 獲取由@ControllerAdvice注解修飾的bean
    List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
    List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();
    for (ControllerAdviceBean adviceBean : adviceBeans) {
        Class<?> beanType = adviceBean.getBeanType();
        if (beanType == null) {
            throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
        }
        Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);
        if (!attrMethods.isEmpty()) {
            this.modelAttributeAdviceCache.put(adviceBean, attrMethods);
        }
        Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);
        if (!binderMethods.isEmpty()) {
            this.initBinderAdviceCache.put(adviceBean, binderMethods);
        }
        // 如果ControllerAdviceBean實現(xiàn)了ResponseBodyAdvice接口,那么這個ControllerAdviceBean需要加載到requestResponseBodyAdvice中
        if (RequestBodyAdvice.class.isAssignableFrom(beanType) || ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
            requestResponseBodyAdviceBeans.add(adviceBean);
        }
    }
    if (!requestResponseBodyAdviceBeans.isEmpty()) {
        this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
    }
    ......
}

getDefaultReturnValueHandlers()

private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
    List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>(20);
    ......
    // 創(chuàng)建并加載HttpEntityMethodProcessor
    handlers.add(new HttpEntityMethodProcessor(getMessageConverters(),
            this.contentNegotiationManager, this.requestResponseBodyAdvice));
    ...
    // 創(chuàng)建并加載RequestResponseBodyMethodProcessor
    handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),
            this.contentNegotiationManager, this.requestResponseBodyAdvice));
    ......
    return handlers;
}

根據(jù)getDefaultReturnValueHandlers() 方法可知,在創(chuàng)建HttpEntityMethodProcessor或者RequestResponseBodyMethodProcessor時,會將RequestMappingHandlerAdapter加載好的ResponseBodyAdvice傳入構(gòu)造函數(shù),并且,無論是HttpEntityMethodProcessor還是RequestResponseBodyMethodProcessor,其構(gòu)造函數(shù)最終都會調(diào)用到父類AbstractMessageConverterMethodArgumentResolver的構(gòu)造函數(shù),并在其中初始化一個RequestResponseBodyAdviceChain以完成ResponseBodyAdvice的加載。構(gòu)造函數(shù)源碼如下所示。

HttpEntityMethodProcessor#HttpEntityMethodProcessor

public HttpEntityMethodProcessor(List<HttpMessageConverter<?>> converters,
        @Nullable ContentNegotiationManager manager, List<Object> requestResponseBodyAdvice) {
    super(converters, manager, requestResponseBodyAdvice);
}

AbstractMessageConverterMethodProcessor#AbstractMessageConverterMethodProcessor

protected AbstractMessageConverterMethodProcessor(List&lt;HttpMessageConverter&lt;?&gt;&gt; converters,
        @Nullable ContentNegotiationManager manager, @Nullable List&lt;Object&gt; requestResponseBodyAdvice) {
    super(converters, requestResponseBodyAdvice);
    this.contentNegotiationManager = (manager != null ? manager : new ContentNegotiationManager());
    this.safeExtensions.addAll(this.contentNegotiationManager.getAllFileExtensions());
    this.safeExtensions.addAll(SAFE_EXTENSIONS);
}

AbstractMessageConverterMethodArgumentResolver#AbstractMessageConverterMethodArgumentResolver

public AbstractMessageConverterMethodArgumentResolver(List<HttpMessageConverter<?>> converters,
        @Nullable List<Object> requestResponseBodyAdvice) {
    Assert.notEmpty(converters, "'messageConverters' must not be empty");
    this.messageConverters = converters;
    this.allSupportedMediaTypes = getAllSupportedMediaTypes(converters);
    this.advice = new RequestResponseBodyAdviceChain(requestResponseBodyAdvice);
}

小節(jié):RequestMappingHandlerAdapter會在其實現(xiàn)的afterPropertiesSet()方法中加載由@ControllerAdvice注解修飾的ResponseBodyAdvice接口,然后會創(chuàng)建并加載返回值處理器,在創(chuàng)建RequestResponseBodyMethodProcessorHttpEntityMethodProcessor這兩個返回值處理器時會傳入加載好的ResponseBodyAdvice,從而完成了ResponseBodyAdvice的加載。

總結(jié)

如果需要使用ResponseBodyAdvice來對handler方法的返回值做處理,則需要創(chuàng)建一個類并實現(xiàn)ResponseBodyAdvice接口,同時該類還需要被@ControllerAdvice注解修飾,這樣的一個ResponseBodyAdvice接口會被RequestMappingHandlerAdapter加載,以及在初始化RequestResponseBodyMethodProcessor和HttpEntityMethodProcessor這兩個返回值處理器時被加載。在通過這兩個返回值處理器將返回值寫入response前,加載好的ResponseBodyAdvice接口的beforeBodyWrite() 方法會被返回值處理器調(diào)用,完成對返回值的定制化處理。

以上就是ResponseBodyAdvice的使用原理源碼解析的詳細內(nèi)容,更多關(guān)于ResponseBodyAdvice原理的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • idea新建聚合項目并附上標簽的詳細過程

    idea新建聚合項目并附上標簽的詳細過程

    這篇文章主要介紹了idea新建聚合項目并附上標簽的詳細過程,本文通過實例圖文相結(jié)合給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-08-08
  • intellij idea 2021.2 打包并上傳運行spring boot項目的詳細過程(spring boot 2.5.4)

    intellij idea 2021.2 打包并上傳運行spring boot項目的詳細過程(spring boot 2

    這篇文章主要介紹了intellij idea 2021.2 打包并上傳運行一個spring boot項目(spring boot 2.5.4),本文通過圖文并茂的形式給大家介紹的非常詳細,需要的朋友可以參考下
    2021-09-09
  • Java獲取當前時間方法總結(jié)

    Java獲取當前時間方法總結(jié)

    本篇文章給大家整理了關(guān)于Java獲取當前時間方法,以及相關(guān)代碼分享,有需要的朋友測試參考下吧。
    2018-02-02
  • Java輸出打印工具類封裝的實例

    Java輸出打印工具類封裝的實例

    下面小編就為大家?guī)硪黄狫ava輸出打印工具類封裝的實例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-10-10
  • 換了最新的idea如何將原來舊版本的idea設(shè)置導進新的idea中

    換了最新的idea如何將原來舊版本的idea設(shè)置導進新的idea中

    這篇文章主要介紹了換了最新的idea如何將原來舊版本的idea設(shè)置導進新的idea中,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-11-11
  • Java實現(xiàn)系統(tǒng)限流的示例代碼

    Java實現(xiàn)系統(tǒng)限流的示例代碼

    限流是保障系統(tǒng)高可用的方式之一,也是大廠高頻面試題,它在微服務(wù)系統(tǒng)中,緩存、限流、熔斷是保證系統(tǒng)高可用的三板斧,所以本文我們就來聊聊如何實現(xiàn)系統(tǒng)限流吧
    2023-09-09
  • SpringBoot 整合Jest實例代碼講解

    SpringBoot 整合Jest實例代碼講解

    本文通過實例代碼給大家介紹了SpringBoot 整合Jest的相關(guān)知識,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下
    2018-08-08
  • Java由淺入深刨析繼承

    Java由淺入深刨析繼承

    繼承就是子類繼承父類的特征和行為,使得子類對象(實例)具有父類的實例域和方法,或子類從父類繼承方法,使得子類具有父類相同的行為
    2022-03-03
  • Java continue break制作簡單聊天室程序

    Java continue break制作簡單聊天室程序

    這篇文章主要為大家詳細介紹了Java continue break制作簡單聊天室程序,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-10-10
  • 解讀java.lang.Character.isLetterOrDigit()的使用方式

    解讀java.lang.Character.isLetterOrDigit()的使用方式

    這篇文章主要介紹了解讀java.lang.Character.isLetterOrDigit()的使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-06-06

最新評論