您好,登錄后才能下訂單哦!
本篇內容介紹了“ResponseBodyAdvice如何使用”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
ResponseBodyAdvice接口可以在將handler方法的返回值寫入response前對返回值進行處理,例如將返回值封裝成一個與客戶端約定好的對象以便于客戶端處理響應數據。
SpringBoot版本:2.4.1
假如已經存在一個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。此時客戶端收到響應之后,針對響應體的處理變得十分不方便,如果增加更多的方法,并且返回值都不相同,那么客戶端將需要根據不同的請求來特定的處理響應體。因此為了方便客戶端處理響應數據,服務器端專門創建了一個返回結果類ReturnResult,并且規定服務器端的所有handler方法執行后往response中寫入的響應體都必須為ReturnResult。在這種情況下,使用ResponseBodyAdvice可以在不修改已有業務代碼的情況下輕松實現上述需求。假設自定義的返回結果類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的響應內容,現在整個ReturnResult為需要寫入response的響應內容,相當于ReturnResult對handler方法的返回值進行了一層封裝。
現在創建一個ReturnResultAdvice類并實現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前被調用。
此時調用DemoController的接口,會發現響應數據結構統一為ReturnResult。
小節:由@ControllerAdvice
注解修飾并實現ResponseBodyAdvice
接口的類所實現的beforeBodyWrite()
方法會在handler方法返回值寫入response前被調用,并且handler方法返回值會作為入參傳入beforeBodyWrite()
,從而可以在返回值寫入response前對返回值進行一些定制操作,例如對返回值進行一層封裝。
首先說明一下為什么第一小節中DemoController的getDefaultDemo1() 方法的返回值類型為ResponseEntity<Demo1>,但是實際往response寫的響應體內容為ResponseEntity中的body。首先所有ResponseBodyAdvice接口的調用是發生在AbstractMessageConverterMethodProcessor的writeWithMessageConverters() 方法中,這個方法的聲明如下所示。
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType, ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException;
其中value就是需要寫入響應體的值,同時也是ResponseBodyAdvice要處理的值。然后如果handler方法的返回值是非ResponseEntity對象且handler方法由@ResponseBody注解修飾,那么writeWithMessageConverters() 方法的調用發生在RequestResponseBodyMethodProcessor#handleReturnValue方法中;
如果handler方法的返回值是ResponseEntity對象,那么writeWithMessageConverters() 方法的調用發生在HttpEntityMethodProcessor#handleReturnValue中,分別看一下在這兩個方法中調用writeWithMessageConverters() 時傳入的參數,就可以解釋之前的疑問了。
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); ...... }
現在正式開始對ResponseBodyAdvice的原理進行分析。
已知所有ResponseBodyAdvice接口的調用是發生在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的調用發生在這里 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() 方法會返回其在構造函數中加載好的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)) { // 執行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() 方法調用了processBody() 方法,processBody() 方法會遍歷所有加載好并且適用于當前handler的ResponseBodyAdvice并執行,至此,所有由@ControllerAdvice注解修飾的ResponseBodyAdvice接口會在這里執行。
小節:由@ControllerAdvice
注解修飾的ResponseBodyAdvice
接口會被SpringMVC框架加載到RequestResponseBodyMethodProcessor
和HttpEntityMethodProcessor
這兩個返回值處理器中,當這兩個返回值處理器將返回值寫入response前,適用于當前handler的ResponseBodyAdvice
接口會被調用,從而可以完成對返回值的定制化改造。
由第二小節可知,正是因為RequestResponseBodyMethodProcessor和HttpEntityMethodProcessor這兩個返回值處理器會將由@ControllerAdvice注解修飾的ResponseBodyAdvice接口加載,才能夠實現將返回值寫入response前調用這些ResponseBodyAdvice接口對返回值進行一些操作。那么本小節將對esponseBodyAdvice接口的加載進行學習。
首先給出結論:ResponseBodyAdvice
的加載發生在RequestMappingHandlerAdapter
的afterPropertiesSet()
方法中。
已知,RequestMappingHandlerAdapter實現了InitializingBean接口,因此RequestMappingHandlerAdapter實現了afterPropertiesSet() 方法。該方法實現如下。
public void afterPropertiesSet() { // 加載ControllerAdviceBean相關內容(同時就會將由@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); } }
上述實現中,initControllerAdviceCache() 方法會加載ControllerAdviceBean相關內容到RequestMappingHandlerAdapter中,這其中就包含由@ControllerAdvice注解修飾的ResponseBodyAdvice接口。然后在getDefaultReturnValueHandlers() 方法中會創建返回值處理器,在創建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實現了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); ...... // 創建并加載HttpEntityMethodProcessor handlers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.contentNegotiationManager, this.requestResponseBodyAdvice)); ... // 創建并加載RequestResponseBodyMethodProcessor handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.contentNegotiationManager, this.requestResponseBodyAdvice)); ...... return handlers; }
根據getDefaultReturnValueHandlers() 方法可知,在創建HttpEntityMethodProcessor或者RequestResponseBodyMethodProcessor時,會將RequestMappingHandlerAdapter加載好的ResponseBodyAdvice傳入構造函數,并且,無論是HttpEntityMethodProcessor還是RequestResponseBodyMethodProcessor,其構造函數最終都會調用到父類AbstractMessageConverterMethodArgumentResolver的構造函數,并在其中初始化一個RequestResponseBodyAdviceChain以完成ResponseBodyAdvice的加載。構造函數源碼如下所示。
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); }
小節:RequestMappingHandlerAdapter
會在其實現的afterPropertiesSet()
方法中加載由@ControllerAdvice
注解修飾的ResponseBodyAdvice
接口,然后會創建并加載返回值處理器,在創建RequestResponseBodyMethodProcessor
和HttpEntityMethodProcessor
這兩個返回值處理器時會傳入加載好的ResponseBodyAdvice
,從而完成了ResponseBodyAdvice的加載。
“ResponseBodyAdvice如何使用”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。