Springboot Controller返回值格式统一处理@RestControllerAdvice扩展点使用和原理分析

返回值格式统一具体做法

在Springboot中如果想对Controller返回值格式或者异常做统处理,可以尝试 @RestControllerAdvice这个扩展点;

其实网上有很多例子,这里推荐 segmentfault 上比较清晰的一篇文章
本文例子也放到了 Gitee

在这里插入图片描述

package com.uu.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.uu.global.StandardResult;
import lombok.SneakyThrows;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

@RestControllerAdvice
public class RestResultAdvice implements ResponseBodyAdvice<Object> {

    private static ObjectMapper objectMapper = new ObjectMapper();

    /**
     * 判断是否要对返回值做处理,这里我们都返回true
     *
     * @param returnType
     * @param converterType
     * @return
     */
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    /**
     * messageConvert在调用write方法前会调用该方法进行message自定义转换
     *
     * @param body                  controller 返回的值
     * @param returnType            具体执行的方法和返回值的封装
     * @param selectedContentType
     * @param selectedConverterType
     * @param request               ServerHttpRequest对象
     * @param response              ServerHttpResponse对象
     * @return
     */
    @SneakyThrows
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {

        if (body instanceof StandardResult) {
            return body;
        }

        Object wrap = StandardResult.builder().code("success").data(body).build();
        if (this.isNative(body)) {
            return objectMapper.writeValueAsString(wrap);
        }
        return wrap;
    }

    /**
     * 对String类型的返回值要做特殊处理,不能进行类型的转换,只能还是返回String类型;
     * 因为在 org.springframework.http.converter.StringHttpMessageConverter#getContentLength(java.lang.String, org.springframework.http.MediaType) 方法会报错
     *
     * @param body
     * @return
     */
    private boolean isNative(Object body) {
        return body instanceof String;
    }
}

内在原理

这个扩展的入口就在 RequestMappingHandlerAdapter这个类,这个类是spring-mvc模块提供的,其最最主要的作用就是根据url去执行controller方法,处理返回值;

Springboot场景下RequestMappingHandlerAdapter被引入时机

RequestMappingHandlerAdapter 这个类是在使用springboot场景下是什么时候被装配进来的呢,这又得从spring.factories说起了(Springboot自动装配原理的同学需要自己补一下相关知识),入口如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
↓
WebMvcAutoConfiguration这个类在 @Configuration 引了 @Import(EnableWebMvcConfiguration.class)注解
↓
@Bean
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.EnableWebMvcConfiguration#requestMappingHandlerAdapter
↓
@Bean
org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#requestMappingHandlerAdapter
↓
new RequestMappingHandlerAdapter(); 返回bean到容器

自定义ResponseBodyAdvice被引入时机

我们首先看看RequestMappingHandlerAdapter类继承图,很明显它是一个InitializingBean,熟悉spring的你应该很敏感的要去看它的afterPropertiesSet方法

在这里插入图片描述
1.构造方法

	public RequestMappingHandlerAdapter() {
    
    
		StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
		stringHttpMessageConverter.setWriteAcceptCharset(false);  // see SPR-7316

		this.messageConverters = new ArrayList<>(4);
		this.messageConverters.add(new ByteArrayHttpMessageConverter());
		this.messageConverters.add(stringHttpMessageConverter);
		this.messageConverters.add(new SourceHttpMessageConverter<>());
		this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
	}

从构造方法可以看出 RequestMappingHandlerAdapter 在实例化的时候就已经初始化了好几种类型返回值的处理器;

2.afterPropertiesSet 方法

@Override
public void afterPropertiesSet() {
    
    
	// Do this first, it may add ResponseBody advice beans
	initControllerAdviceCache(); //@A

	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);
	}
}

重点方法:
//@A

private void initControllerAdviceCache() {
    
    
		if (getApplicationContext() == null) {
    
    
			return;
		}
		if (logger.isInfoEnabled()) {
    
    
			logger.info("Looking for @ControllerAdvice: " + getApplicationContext());
		}

//@A
		List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
		AnnotationAwareOrderComparator.sort(adviceBeans);

		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);
				if (logger.isInfoEnabled()) {
    
    
					logger.info("Detected @ModelAttribute methods in " + adviceBean);
				}
			}
			Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);
			if (!binderMethods.isEmpty()) {
    
    
				this.initBinderAdviceCache.put(adviceBean, binderMethods);
				if (logger.isInfoEnabled()) {
    
    
					logger.info("Detected @InitBinder methods in " + adviceBean);
				}
			}
//@B
			if (RequestBodyAdvice.class.isAssignableFrom(beanType)) {
    
    
				requestResponseBodyAdviceBeans.add(adviceBean);
				if (logger.isInfoEnabled()) {
    
    
					logger.info("Detected RequestBodyAdvice bean in " + adviceBean);
				}
			}
//@C
			if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
    
    
				requestResponseBodyAdviceBeans.add(adviceBean);
				if (logger.isInfoEnabled()) {
    
    
					logger.info("Detected ResponseBodyAdvice bean in " + adviceBean);
				}
			}
		}

		if (!requestResponseBodyAdviceBeans.isEmpty()) {
    
    
			this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
		}
	}

@A:找到所有被 @ControllerAdvice(当然也包括其组合注解@RestControllerAdvice) 标注的类
@B:该类实现了 RequestBodyAdvice 则会被加入集合中待使用
@C:该类实现了 ResponseBodyAdvice 则会被加入集合中待使用
最后将自定义候选类加入到requestResponseBodyAdvice集合中待使用

扫描二维码关注公众号,回复: 13234977 查看本文章

自定义ResponseBodyAdvice被使用时机

通过debug可以发现框架在执行controller对于的method得到返回值后去调用 requestResponseBodyAdvice里的候选者执行对于的过滤,调用栈如下:

org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#writeWithMessageConverters(T, org.springframework.core.MethodParameter, org.springframework.http.server.ServletServerHttpRequest, org.springframework.http.server.ServletServerHttpResponse)
↓
org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver#getAdvice
↓
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyAdviceChain#processBody

具体方法

	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)) {
    
    
// @A
			if (advice.supports(returnType, converterType)) {
    
    
// @B
				body = ((ResponseBodyAdvice<T>) advice).beforeBodyWrite((T) body, returnType,
						contentType, converterType, request, response);
			}
		}
// @C
		return body;
	}

@A:调用 supports 方法
@B:调用 beforeBodyWrite 方法
@C:返回经过处理的body(controller方法返回的值)

over~~~


猜你喜欢

转载自blog.csdn.net/Aqu415/article/details/119079332