Spring解密之RequestBodyAdvice、ResponseBodyAdvice、HttpMessageConverter

Author: lwyang
SpringBoot:2.1.9.RELEASE

概述

RequestBodyAdvice

Allows customizing the request before its body is read and converted into an Object and also allows for processing of the resulting Object before it is passed into a controller method as an @RequestBody or an HttpEntity method argument.

Implementations of this contract may be registered directly with the RequestMappingHandlerAdapter or more likely annotated with @ControllerAdvice in which case they are auto-detected.

实现RequestBodyAdvice接口可以在请求体被HttpMessageConverter转换成Object前、后注入自己的逻辑,该方法的参数需要是被 @RequestBody修饰或者HttpEntity,实现RequestBodyAdvice接口的类需要使用@ControllerAdvice注册进框架中

ResponseBodyAdvice

Allows customizing the response after the execution of an @ResponseBody or a ResponseEntity controller method but before the body is written with an HttpMessageConverter.

Implementations may be registered directly with RequestMappingHandlerAdapter and ExceptionHandlerExceptionResolver or more likely annotated with @ControllerAdvice in which case they will be auto-detected by both.

实现ResponseBodyAdvice接口可以在方法的返回值被HttpMessageConverter写入之前自定义包装方法返回值,比如进行统一的返回值处理,实现ResponseBodyAdvice的接口同样需要使用@ControllerAdvice注册进框架中

HttpMessageConverter

Strategy interface that specifies a converter that can convert from and to HTTP requests and responses.

HttpMessageConverter可以将请求转换为指定的对象或将指定的对象转换为对应的响应,我们常用的Json格式的转换对应的实现类是MappingJackson2HttpMessageConverter

分析

当请求到达HandlerMethodArgumentResolver后,会通过supportsParameter方法来判断应当使用哪一种HandlerMethodArgumentResolver实现类

// class HandlerMethodArgumentResolverComposite
	@Nullable
	private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
		HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
		if (result == null) {
			for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
				
				// 调用supportsParameter 方法判断使用哪一种实现类
				if (resolver.supportsParameter(parameter)) {
					result = resolver;
					this.argumentResolverCache.put(parameter, result);
					break;
				}
			}
		}
		return result;
	}

如果使用@RequestBody@ResponseBody注解,可以看到在RequestResponseBodyMethodProcessor该实现类的supportsParameter方法里,会去判断该方法参数是否使用了RequestBody 注解

	// class RequestResponseBodyMethodProcessor ... implements HandlerMethodArgumentResolver
	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return parameter.hasParameterAnnotation(RequestBody.class);
	}

通过上述流程就可以找到能够处理有RequestBody注解的类,即为RequestResponseBodyMethodProcessor。同理也是能够处理ResponseBody注解的类

	//class RequestResponseBodyMethodProcessor ... implements HandlerMethodReturnValueHandler
	@Override
	public boolean supportsReturnType(MethodParameter returnType) {
		return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
				returnType.hasMethodAnnotation(ResponseBody.class));
	}

接下来进行参数解析时它会把请求体中Json格式的数据转换成对应的实体类

	class RequestResponseBodyMethodProcessor  ... implements HandlerMethodArgumentResolver
	@Override
	public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

		parameter = parameter.nestedIfOptional();

		//通过HttpMessageConverter对请求体进行解析
		Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
		String name = Conventions.getVariableNameForParameter(parameter);

		if (binderFactory != null) {
			WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
			if (arg != null) {
				
				//这里对参数进行校验,处理@Validated、@Valid注解
				validateIfApplicable(binder, parameter);
				if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
					throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
				}
			}
			if (mavContainer != null) {
				mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
			}
		}

		return adaptArgumentIfNecessary(arg, parameter);
	}

【彩蛋】:在上面这个方法中(RequestResponseBodyMethodProcessor#resolveArgument ),还发现了处理参数检验的方法,就是我们常用的@Validated@Valid注解修饰的参数

readWithMessageConverters方法中,最终会去调用父类的重载方法:

//abstract class AbstractMessageConverterMethodArgumentResolver
	protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
		......
		if (message.hasBody()) {
			HttpInputMessage msgToUse = 
					getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
			body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse)
					 :((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
			body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
		}
	}
	return body;

genericConverter.read()是真正HttpMessageConverter的转换方法,在这个方法前后会去调用beforeBodyReadafterBodyRead,也就是实现RequestBodyAdvice接口重写的方法

getAdvice()方法会返回一个RequestResponseBodyAdviceChain

// class RequestResponseBodyAdviceChain
class RequestResponseBodyAdviceChain implements RequestBodyAdvice, ResponseBodyAdvice<Object> {

	private final List<Object> requestBodyAdvice = new ArrayList<>(4);

	private final List<Object> responseBodyAdvice = new ArrayList<>(4);
......

这个类中的两个List就分别装着实现了requestBodyAdvice 和responseBodyAdvice 接口的Bean,那么问题来了,这两个List中的Bean时什么时候被加进去的呢???

【Question Start】
RequestMappingHandlerAdapter类中有个initControllerAdviceCache方法

	// class RequestMappingHandlerAdapter 
	private void initControllerAdviceCache() {
	
		// 获取所有带有@ControllerAdvice注解的Bean
		List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
		AnnotationAwareOrderComparator.sort(adviceBeans);

		List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();

		for (ControllerAdviceBean adviceBean : adviceBeans) {
			Class<?> beanType = adviceBean.getBeanType();

			// 判断如果该Bean实现了requestBodyAdvice 或者responseBodyAdvice 接口,就存起来
			if (RequestBodyAdvice.class.isAssignableFrom(beanType) || ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
				requestResponseBodyAdviceBeans.add(adviceBean);
			}
		}

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

然后在getDefaultArgumentResolvers方法里会把保存的这个List(requestResponseBodyAdvice)传给RequestResponseBodyMethodProcessor,这样就回到了上面的说的流程中了

    //class RequestMappingHandlerAdapter 
	private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
		List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();

		//把this.requestResponseBodyAdvice 传给RequestResponseBodyMethodProcessor
		resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));		
......

RequestResponseBodyMethodProcessor通过构造函数接收到这个List后会调用父类(AbstractMessageConverterMethodArgumentResolver)的构造函数保存下来

	//class AbstractMessageConverterMethodArgumentResolver
	public AbstractMessageConverterMethodArgumentResolver(List<HttpMessageConverter<?>> converters,
			@Nullable List<Object> requestResponseBodyAdvice) {

		this.advice = new RequestResponseBodyAdviceChain(requestResponseBodyAdvice);
	}

这里就通过RequestResponseBodyAdviceChain的构造函数将这个List传进去后,分别填充刚刚上面说的requestBodyAdvice和responseBodyAdvice两个List

	// class RequestResponseBodyAdviceChain
	public RequestResponseBodyAdviceChain(@Nullable List<Object> requestResponseBodyAdvice) {
		this.requestBodyAdvice.addAll(getAdviceByType(requestResponseBodyAdvice, RequestBodyAdvice.class));
		this.responseBodyAdvice.addAll(getAdviceByType(requestResponseBodyAdvice, ResponseBodyAdvice.class));
	}

这样就回答了刚刚上面的这个问题了!
【Question END】

在请求返回(Json格式)的时候,RequestResponseBodyMethodProcessor类会去调用handleReturnValue方法

	// RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor
	@Override
	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
		ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
		ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

		// Try even with null return value. ResponseBodyAdvice could get involved.
		writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
	}

接下来会去调用父类AbstractMessageConverterMethodProcessorwriteWithMessageConverters方法

	// AbstractMessageConverterMethodProcessor implements HandlerMethodReturnValueHandler
	protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
			ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

			body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
							(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
							inputMessage, outputMessage);	
						
			genericConverter.write(body, targetType, selectedMediaType, outputMessage);
							
......

genericConverter.writeHttpMessageConverter的转换(转Json)方法,在转换之前会先去调用beforeBodyWrite,也就是实现ResponseBodyAdvice接口重写的方法

测试

接下来用代码测试下上述分析是否正确:

@RestController
@ControllerAdvice
@RequestMapping("/")
public class MyTest implements RequestBodyAdvice, ResponseBodyAdvice {

    @GetMapping("test")
    public List adviceOrder(@RequestBody Person person){
        System.out.println("===adviceOrder===");
        System.out.println("name: "+person.getName()+"  age: "+person.getAge());
        ArrayList<String> arrayList = new ArrayList<>();
        arrayList.add(person.getName());
        arrayList.add(person.getAge());
        return arrayList;
    }

    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        System.out.println("RequestBodyAdvice beforeBodyRead");
        return inputMessage;
    }

    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        System.out.println("RequestBodyAdvice afterBodyRead");
        return body;
    }

    @Override
    public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return null;
    }

    @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) {
        System.out.println("ResponseBodyAdvice beforeBodyWrite");
        Map<String, Object> map = new HashMap<>();
        map.put("data", body);
        Result result = new Result();
        result.setCode("200");
        result.setMsg("OK");
        result.setData(map);
        return result;
    }
}

然后自己写个HttpMessageConverter继承MappingJackson2HttpMessageConverter ,用来打印转换的执行过程

@Component
public class MyConverter extends MappingJackson2HttpMessageConverter {

    @Override
    public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        System.out.println("MappingJackson2HttpMessageConverter read");
        return super.read(type, contextClass, inputMessage);
    }

    @Override
    protected void writeInternal(Object object, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        System.out.println("MappingJackson2HttpMessageConverter write");
        super.writeInternal(object, type, outputMessage);
    }

}

【用postman测试结果】

{
    "code": "200",
    "msg": "OK",
    "data": {
        "data": [
            "lwyang",
            "10"
        ]
    }
}

【Consloe打印结果】

RequestBodyAdvice beforeBodyRead
MappingJackson2HttpMessageConverter read
RequestBodyAdvice afterBodyRead
===adviceOrder===
name: lwyang  age: 10
ResponseBodyAdvice beforeBodyWrite
MappingJackson2HttpMessageConverter write

可以看到执行顺序和上面分析的顺序一致
RequestBodyAdvice beforeBodyRead ==》 HttpMessageConverter ==》RequestBodyAdvice afterBodyRead ==》业务Method ==》ResponseBodyAdvice beforeBodyWrite ==》HttpMessageConverter

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值