ResponseBodyAdvice的使用原理源碼解析
前言
ResponseBodyAdvice接口可以在將handler方法的返回值寫入response前對(duì)返回值進(jìn)行處理,例如將返回值封裝成一個(gè)與客戶端約定好的對(duì)象以便于客戶端處理響應(yīng)數(shù)據(jù)。本篇文章將學(xué)習(xí)如何使用ResponseBodyAdvice以及其實(shí)現(xiàn)原理。
SpringBoot版本:2.4.1
正文
一. ResponseBodyAdvice的使用
假如已經(jīng)存在一個(gè)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中有兩個(gè)方法,并且返回值分別為ResponseEntity<Demo1>和Demo2。此時(shí)客戶端收到響應(yīng)之后,針對(duì)響應(yīng)體的處理變得十分不方便,如果增加更多的方法,并且返回值都不相同,那么客戶端將需要根據(jù)不同的請(qǐng)求來(lái)特定的處理響應(yīng)體。因此為了方便客戶端處理響應(yīng)數(shù)據(jù),服務(wù)器端專門創(chuàng)建了一個(gè)返回結(jié)果類ReturnResult,并且規(guī)定服務(wù)器端的所有handler方法執(zhí)行后往response中寫入的響應(yīng)體都必須為ReturnResult。在這種情況下,使用ResponseBodyAdvice可以在不修改已有業(yè)務(wù)代碼的情況下輕松實(shí)現(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)在整個(gè)ReturnResult為需要寫入response的響應(yīng)內(nèi)容,相當(dāng)于ReturnResult對(duì)handler方法的返回值進(jìn)行了一層封裝。
現(xiàn)在創(chuàng)建一個(gè)ReturnResultAdvice類并實(shí)現(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() 方法會(huì)在handler方法返回值寫入response前被調(diào)用。
此時(shí)調(diào)用DemoController的接口,會(huì)發(fā)現(xiàn)響應(yīng)數(shù)據(jù)結(jié)構(gòu)統(tǒng)一為ReturnResult。
小節(jié):由@ControllerAdvice
注解修飾并實(shí)現(xiàn)ResponseBodyAdvice
接口的類所實(shí)現(xiàn)的beforeBodyWrite()
方法會(huì)在handler方法返回值寫入response前被調(diào)用,并且handler方法返回值會(huì)作為入?yún)魅?code>beforeBodyWrite(),從而可以在返回值寫入response前對(duì)返回值進(jìn)行一些定制操作,例如對(duì)返回值進(jìn)行一層封裝。
二. ResponseBodyAdvice的原理
首先說明一下為什么第一小節(jié)中DemoController的getDefaultDemo1() 方法的返回值類型為ResponseEntity<Demo1>,但是實(shí)際往response寫的響應(yīng)體內(nèi)容為ResponseEntity中的body。首先所有ResponseBodyAdvice接口的調(diào)用是發(fā)生在AbstractMessageConverterMethodProcessor的writeWithMessageConverters() 方法中,這個(gè)方法的聲明如下所示。
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType, ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException;
其中value就是需要寫入響應(yīng)體的值,同時(shí)也是ResponseBodyAdvice要處理的值。然后如果handler方法的返回值是非ResponseEntity對(duì)象且handler方法由@ResponseBody注解修飾,那么writeWithMessageConverters() 方法的調(diào)用發(fā)生在RequestResponseBodyMethodProcessor#handleReturnValue方法中;
如果handler方法的返回值是ResponseEntity對(duì)象,那么writeWithMessageConverters() 方法的調(diào)用發(fā)生在HttpEntityMethodProcessor#handleReturnValue中,分別看一下在這兩個(gè)方法中調(diào)用writeWithMessageConverters() 時(shí)傳入的參數(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)在正式開始對(duì)ResponseBodyAdvice的原理進(jìn)行分析。
已知所有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() 方法會(huì)返回其在構(gòu)造函數(shù)中加載好的RequestResponseBodyAdviceChain對(duì)象,下面看一下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中獲取適用于當(dāng)前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是否適用于當(dāng)前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() 方法會(huì)遍歷所有加載好并且適用于當(dāng)前handler的ResponseBodyAdvice并執(zhí)行,至此,所有由@ControllerAdvice注解修飾的ResponseBodyAdvice接口會(huì)在這里執(zhí)行。
小節(jié):由@ControllerAdvice
注解修飾的ResponseBodyAdvice
接口會(huì)被SpringMVC框架加載到RequestResponseBodyMethodProcessor
和HttpEntityMethodProcessor
這兩個(gè)返回值處理器中,當(dāng)這兩個(gè)返回值處理器將返回值寫入response前,適用于當(dāng)前handler的ResponseBodyAdvice
接口會(huì)被調(diào)用,從而可以完成對(duì)返回值的定制化改造。
三. ResponseBodyAdvice的加載
由第二小節(jié)可知,正是因?yàn)镽equestResponseBodyMethodProcessor和HttpEntityMethodProcessor這兩個(gè)返回值處理器會(huì)將由@ControllerAdvice注解修飾的ResponseBodyAdvice接口加載,才能夠?qū)崿F(xiàn)將返回值寫入response前調(diào)用這些ResponseBodyAdvice接口對(duì)返回值進(jìn)行一些操作。那么本小節(jié)將對(duì)esponseBodyAdvice接口的加載進(jìn)行學(xué)習(xí)。
首先給出結(jié)論:ResponseBodyAdvice
的加載發(fā)生在RequestMappingHandlerAdapter
的afterPropertiesSet()
方法中。
已知,RequestMappingHandlerAdapter實(shí)現(xiàn)了InitializingBean接口,因此RequestMappingHandlerAdapter實(shí)現(xiàn)了afterPropertiesSet() 方法。該方法實(shí)現(xiàn)如下。
public void afterPropertiesSet() { // 加載ControllerAdviceBean相關(guān)內(nèi)容(同時(shí)就會(huì)將由@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) { // 獲取返回值處理器,在這里就會(huì)完成RequestResponseBodyMethodProcessor和HttpEntityMethodProcessor的初始化,初始化的同時(shí)就會(huì)完成ResponseBodyAdvice接口的加載 List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers(); this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers); } }
上述實(shí)現(xiàn)中,initControllerAdviceCache() 方法會(huì)加載ControllerAdviceBean相關(guān)內(nèi)容到RequestMappingHandlerAdapter中,這其中就包含由@ControllerAdvice注解修飾的ResponseBodyAdvice接口。然后在getDefaultReturnValueHandlers() 方法中會(huì)創(chuàng)建返回值處理器,在創(chuàng)建RequestResponseBodyMethodProcessor和HttpEntityMethodProcessor時(shí)會(huì)使用加載好的ResponseBodyAdvice接口完成這兩個(gè)返回值處理器的初始化。上述兩個(gè)方法的部分源碼如下所示。
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實(shí)現(xiàn)了ResponseBodyAdvice接口,那么這個(gè)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時(shí),會(huì)將RequestMappingHandlerAdapter加載好的ResponseBodyAdvice傳入構(gòu)造函數(shù),并且,無(wú)論是HttpEntityMethodProcessor還是RequestResponseBodyMethodProcessor,其構(gòu)造函數(shù)最終都會(huì)調(diào)用到父類AbstractMessageConverterMethodArgumentResolver的構(gòu)造函數(shù),并在其中初始化一個(gè)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<HttpMessageConverter<?>> converters, @Nullable ContentNegotiationManager manager, @Nullable List<Object> 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
會(huì)在其實(shí)現(xiàn)的afterPropertiesSet()
方法中加載由@ControllerAdvice
注解修飾的ResponseBodyAdvice
接口,然后會(huì)創(chuàng)建并加載返回值處理器,在創(chuàng)建RequestResponseBodyMethodProcessor
和HttpEntityMethodProcessor
這兩個(gè)返回值處理器時(shí)會(huì)傳入加載好的ResponseBodyAdvice
,從而完成了ResponseBodyAdvice的加載。
總結(jié)
如果需要使用ResponseBodyAdvice來(lái)對(duì)handler方法的返回值做處理,則需要?jiǎng)?chuàng)建一個(gè)類并實(shí)現(xiàn)ResponseBodyAdvice接口,同時(shí)該類還需要被@ControllerAdvice注解修飾,這樣的一個(gè)ResponseBodyAdvice接口會(huì)被RequestMappingHandlerAdapter加載,以及在初始化RequestResponseBodyMethodProcessor和HttpEntityMethodProcessor這兩個(gè)返回值處理器時(shí)被加載。在通過這兩個(gè)返回值處理器將返回值寫入response前,加載好的ResponseBodyAdvice接口的beforeBodyWrite() 方法會(huì)被返回值處理器調(diào)用,完成對(duì)返回值的定制化處理。
以上就是ResponseBodyAdvice的使用原理源碼解析的詳細(xì)內(nèi)容,更多關(guān)于ResponseBodyAdvice原理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
idea新建聚合項(xiàng)目并附上標(biāo)簽的詳細(xì)過程
這篇文章主要介紹了idea新建聚合項(xiàng)目并附上標(biāo)簽的詳細(xì)過程,本文通過實(shí)例圖文相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-08-08intellij idea 2021.2 打包并上傳運(yùn)行spring boot項(xiàng)目的詳細(xì)過程(spring boot 2
這篇文章主要介紹了intellij idea 2021.2 打包并上傳運(yùn)行一個(gè)spring boot項(xiàng)目(spring boot 2.5.4),本文通過圖文并茂的形式給大家介紹的非常詳細(xì),需要的朋友可以參考下2021-09-09Java獲取當(dāng)前時(shí)間方法總結(jié)
本篇文章給大家整理了關(guān)于Java獲取當(dāng)前時(shí)間方法,以及相關(guān)代碼分享,有需要的朋友測(cè)試參考下吧。2018-02-02換了最新的idea如何將原來(lái)舊版本的idea設(shè)置導(dǎo)進(jìn)新的idea中
這篇文章主要介紹了換了最新的idea如何將原來(lái)舊版本的idea設(shè)置導(dǎo)進(jìn)新的idea中,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11Java實(shí)現(xiàn)系統(tǒng)限流的示例代碼
限流是保障系統(tǒng)高可用的方式之一,也是大廠高頻面試題,它在微服務(wù)系統(tǒng)中,緩存、限流、熔斷是保證系統(tǒng)高可用的三板斧,所以本文我們就來(lái)聊聊如何實(shí)現(xiàn)系統(tǒng)限流吧2023-09-09Java continue break制作簡(jiǎn)單聊天室程序
這篇文章主要為大家詳細(xì)介紹了Java continue break制作簡(jiǎn)單聊天室程序,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10解讀java.lang.Character.isLetterOrDigit()的使用方式
這篇文章主要介紹了解讀java.lang.Character.isLetterOrDigit()的使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-06-06