ResponseBodyAdvice的使用原理源碼解析
前言
ResponseBodyAdvice接口可以在將handler方法的返回值寫入response前對返回值進行處理,例如將返回值封裝成一個與客戶端約定好的對象以便于客戶端處理響應(yīng)數(shù)據(jù)。本篇文章將學(xué)習(xí)如何使用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框架加載到RequestResponseBodyMethodProcessor和HttpEntityMethodProcessor這兩個返回值處理器中,當這兩個返回值處理器將返回值寫入response前,適用于當前handler的ResponseBodyAdvice接口會被調(diào)用,從而可以完成對返回值的定制化改造。
三. ResponseBodyAdvice的加載
由第二小節(jié)可知,正是因為RequestResponseBodyMethodProcessor和HttpEntityMethodProcessor這兩個返回值處理器會將由@ControllerAdvice注解修飾的ResponseBodyAdvice接口加載,才能夠?qū)崿F(xiàn)將返回值寫入response前調(diào)用這些ResponseBodyAdvice接口對返回值進行一些操作。那么本小節(jié)將對esponseBodyAdvice接口的加載進行學(xué)習(xí)。
首先給出結(jié)論:ResponseBodyAdvice的加載發(fā)生在RequestMappingHandlerAdapter的afterPropertiesSet()方法中。
已知,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<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會在其實現(xiàn)的afterPropertiesSet()方法中加載由@ControllerAdvice注解修飾的ResponseBodyAdvice接口,然后會創(chuàng)建并加載返回值處理器,在創(chuàng)建RequestResponseBodyMethodProcessor和HttpEntityMethodProcessor這兩個返回值處理器時會傳入加載好的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)文章
intellij idea 2021.2 打包并上傳運行spring boot項目的詳細過程(spring boot 2
這篇文章主要介紹了intellij idea 2021.2 打包并上傳運行一個spring boot項目(spring boot 2.5.4),本文通過圖文并茂的形式給大家介紹的非常詳細,需要的朋友可以參考下2021-09-09
換了最新的idea如何將原來舊版本的idea設(shè)置導(dǎo)進新的idea中
這篇文章主要介紹了換了最新的idea如何將原來舊版本的idea設(shè)置導(dǎo)進新的idea中,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-11-11
解讀java.lang.Character.isLetterOrDigit()的使用方式
這篇文章主要介紹了解讀java.lang.Character.isLetterOrDigit()的使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-06-06

