ResponseBodyAdvice如何使用
前言
ResponseBodyAdvice接口可以在将handler方法的返回值写入response前对返回值进行处理,例如将返回值封装成一个与客户端约定好的对象以便于客户端处理响应数据。
SpringBoot版本:2.4.1
一. ResponseBodyAdvice的使用
假如已经存在一个Controller,如下所示。
@RestController
public class DemoController {
@RequestMapping(value = "/api/v1/demo1/getdefault", method = RequestMethod.GET)
public ResponseEntity 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
public class ReturnResult{ 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
ReturnResultAdvice的beforeBodyWrite() 方法会在handler方法返回值写入response前被调用。
此时调用DemoController的接口,会发现响应数据结构统一为ReturnResult。
小节:由@ControllerAdvice注解修饰并实现ResponseBodyAdvice接口的类所实现的beforeBodyWrite()方法会在handler方法返回值写入response前被调用,并且handler方法返回值会作为入参传入beforeBodyWrite(),从而可以在返回值写入response前对返回值进行一些定制操作,例如对返回值进行一层封装。
二. ResponseBodyAdvice的原理
首先说明一下为什么第一小节中DemoController的getDefaultDemo1() 方法的返回值类型为ResponseEntity
protectedvoid 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() 方法中,其部分源码如下所示。
protectedvoid 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 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) advice).beforeBodyWrite((T) body, returnType,
contentType, converterType, request, response);
}
}
return body;
}
private List getMatchingAdvice(MethodParameter parameter, Class extends A> adviceType) {
// 获取ResponseBodyAdvice集合
List 在RequestResponseBodyAdviceChain中,beforeBodyWrite() 方法调用了processBody() 方法,processBody() 方法会遍历所有加载好并且适用于当前handler的ResponseBodyAdvice并执行,至此,所有由@ControllerAdvice注解修饰的ResponseBodyAdvice接口会在这里执行。
小节:由@ControllerAdvice注解修饰的ResponseBodyAdvice接口会被SpringMVC框架加载到RequestResponseBodyMethodProcessor和HttpEntityMethodProcessor这两个返回值处理器中,当这两个返回值处理器将返回值写入response前,适用于当前handler的ResponseBodyAdvice接口会被调用,从而可以完成对返回值的定制化改造。
三. 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 resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.initBinderArgumentResolvers == null) {
List resolvers = getDefaultInitBinderArgumentResolvers();
this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.returnValueHandlers == null) {
// 获取返回值处理器,在这里就会完成RequestResponseBodyMethodProcessor和HttpEntityMethodProcessor的初始化,初始化的同时就会完成ResponseBodyAdvice接口的加载
List 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 adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
List getDefaultReturnValueHandlers()
private ListgetDefaultReturnValueHandlers() { List 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> converters, @Nullable ContentNegotiationManager manager, List
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> converters, @Nullable List
小节:RequestMappingHandlerAdapter会在其实现的afterPropertiesSet()方法中加载由@ControllerAdvice注解修饰的ResponseBodyAdvice接口,然后会创建并加载返回值处理器,在创建RequestResponseBodyMethodProcessor和HttpEntityMethodProcessor这两个返回值处理器时会传入加载好的ResponseBodyAdvice,从而完成了ResponseBodyAdvice的加载。