spring ControllerAdvice源码分析

版本说明

jdk:1.8.0_131
springboot:2.1.6.RELEAS
maven:3.6.1
database:mysql-5.7.14
lombok插件

源码分析

仅仅针对被@ControllerAdvice注解的且实现接口ResponseBodyAdvice的类,进行源码分析,了解一下当controller中被@ResponseBody注解的方法的返回值,是如何被解析成前端需要的值的。 至于RequestBodyAdvice@ExceptionHandler等实现原理是差不多的。

根据Spring自定义ReturnValueHandlers中的分析,我们了解了实际调用controller类中的被@ResponseBody注解方法时,实际使用RequestResponseBodyMethodProcessor处理器去处理。

我们查看下RequestResponseBodyMethodProcessorhandleReturnValue

1
2
3
4
5
6
7
8
9
10
11
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}

writeWithMessageConverters节选片段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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)) {
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;
}
}
}

this.messageConverters的循环调用,其实就是用合适的HttpMessageConverter来解析返回报文,默认情况下我们用的就是SpringBoot内容的MappingJackson2HttpMessageConverter处理器

MappingJackson2HttpMessageConvertercanWrite就是查看MediaType是否满足

1
2
3
4
5
6
7
8
9
10
11
protected boolean canWrite(@Nullable MediaType mediaType) {
if (mediaType == null || MediaType.ALL.equalsTypeAndSubtype(mediaType)) {
return true;
}
for (MediaType supportedMediaType : getSupportedMediaTypes()) {
if (supportedMediaType.isCompatibleWith(mediaType)) {
return true;
}
}
return false;
}

重点的是getAdvice()的加载

1
2
3
RequestResponseBodyAdviceChain getAdvice() {
return this.advice;
}

Debug模式一步步回溯最终发现RequestMappingHandlerAdapter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void afterPropertiesSet() {
//扫描所有@ControllerAdvice
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) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}

节选initControllerAdviceCache

1
2
3
4
5
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
...
if (!requestResponseBodyAdviceBeans.isEmpty()) {
this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
}

节选方法getDefaultArgumentResolvers细节

1
resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));

RequestResponseBodyMethodProcessor构造方法最终指向父类AbstractMessageConverterMethodArgumentResolver,那么我们看到@ControllerAdvice注解的且实现接口ResponseBodyAdvice的类被加载到this.advice

1
2
3
4
5
6
7
8
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);
}

那么我们看下this.advice的类RequestResponseBodyAdviceChain方法beforeBodyWrite细节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Override
@Nullable
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) {

for (ResponseBodyAdvice<?> advice : getMatchingAdvice(returnType, ResponseBodyAdvice.class)) {
//决定自定义@ControllerAdvice是否启用
if (advice.supports(returnType, converterType)) {
//调用我们的返回值处理类
body = ((ResponseBodyAdvice<T>) advice).beforeBodyWrite((T) body, returnType,
contentType, converterType, request, response);
}
}
return body;
}

最后贴下@ControllerAdvice实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.li.springboot.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

@ControllerAdvice
@Slf4j
public class MyControllerAdvice implements ResponseBodyAdvice{
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}

@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
log.debug("MyControllerAdvice beforeBodyWrite");
return body;
}
}