Spring MVC @RequestBody @ResponseBody 序列化反序列化实现

前言

Spring MVC 中,标注了 @RequestBody 注解的 Controller 方法参数可以用来接收请求体,标注了 @ResponseBody 注解的 Controller 方法则会将返回值作为响应体。请求体和响应体还可能具有不同的内容类型,例如 json、xml 等等。本篇尝试分析 Spring MVC 如何将请求体反序列化为 Controller 方法参数,以及如何将 Controller 方法返回值序列化为响应体。

Spring MVC @RequestBody 参数解析

Spring MVC 中,Controller 方法参数的解析统一使用 HandlerMethodArgumentResolver,对于 @RequestBody 注解标注的参数同样如此,回顾下这个接口的定义。

public interface HandlerMethodArgumentResolver {

	// 是否支持给定的方法参数
	boolean supportsParameter(MethodParameter parameter);

	// 解析方法参数
	@Nullable
	Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
						   NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;

}

Spring MVC 内部有很多 HandlerMethodArgumentResolver 接口的实现,默认的实现及能够处理的方法参数见下图。
在这里插入图片描述可以看到,处理 @RequestBody 注解标注参数的实现类为 RequestResponseBodyMethodProcessor,简单看下这个类如何将请求体反序列化为 @RequestBody 参数的。

public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {

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

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

		parameter = parameter.nestedIfOptional();
		// 从请求中读取请求体
		Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
		
		... 省略数据绑定、参数校验代码

		return adaptArgumentIfNecessary(arg, parameter);
	}

	@Override
	protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
												   Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

		HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
		Assert.state(servletRequest != null, "No HttpServletRequest");
		ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);
		// 调用父类方法从请求中读取请求体
		Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
		if (arg == null && checkRequired(parameter)) {
			throw new HttpMessageNotReadableException("Required request body is missing: " +
					parameter.getExecutable().toGenericString(), inputMessage);
		}
		return arg;
	}
}

RequestResponseBodyMethodProcessor 反序列化请求体时将 HttpServletRequest 转换为 HttpInputMessage,最终调用了父类中的 #readWithMessageConverters 方法,从方法名也可以看出,实现是从 MessageConverter 读取请求体的,这个 MessageConverter 又是什么呢?先留个悬念,稍后再进行分析。

Spring MVC @ResponseBody 返回值处理

Spring MVC 对 Controller 方法返回值处理的设计思路与对方法参数解析的设计思路类似,提出了一个名为 HandlerMethodReturnValueHandler 的接口,所有的 Controller 方法返回值都由这个接口的实现处理,对于 @ResponseBody 标注方法也是如此,接口定义如下。

public interface HandlerMethodReturnValueHandler {

	// 是否支持返回类型
	boolean supportsReturnType(MethodParameter returnType);

	// 处理返回值
	void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
						   ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;

}

默认的 HandlerMethodReturnValueHandler 实现及能够处理的返回值如下图所示。
在这里插入图片描述Spring 将 @RequestBody 和 @ResponseBody 注解的处理合并到一个类,处理 @ResponseBody 返回值同样使用的 RequestResponseBodyMethodProcessor,与序列化 Controller 方法返回值有关的代码如下。

public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {

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

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

可以看出,RequestResponseBodyMethodProcessor 不仅支持处理方法上存在 @ResponseBody 的注解,还支持类上存在 @ResponseBody 注解或元注解的方法,这也是为何在类上添加 @RestController 就可以在方法上省略 @ResponseBody 注解的原因。

RequestResponseBodyMethodProcessor 处理返回值时,调用了 #writeWithMessageConverters 方法,根据方法名可知,这个方法使用 MessageConverter 写响应。

Spring MVC HttpMessageConverter

不管是将请求体转换为 Controller 方法参数还是将 Controller 方法返回值转换为响应体,都使用到了 MessageConverter,这个 MessageConverter 实际上是一个名为 HttpMessageConverter 的接口,定义如下。

public interface HttpMessageConverter<T> {

	// 是否能将请求体转换为给定的类对象
	boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);

	// 是否能将给定的类对象转换为响应体
	boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);

	// 获取所有支持的内容类型
	List<MediaType> getSupportedMediaTypes();

	// 将响应体转换为类对象
	T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
			throws IOException, HttpMessageNotReadableException;

	// 将类对象转换为响应体
	void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException;

}

HttpMessageConverter 同时支持请求体转换为类对象,以及将类对象转换为响应体,因此 RequestResponseBodyMethodProcessor 不管处理参数还是返回值都用到了它。

看下 RequestResponseBodyMethodProcessor 如何将请求体转换为 Controller 方法参数的,继续跟踪上面没有分析的 #readWithMessageConverters 方法,核心代码如下。
在这里插入图片描述RequestResponseBodyMethodProcessor 的父类 AbstractMessageConverterMethodArgumentResolver 中维护了一个 HttpMessageConverter 列表,如果其中某个 HttpMessageConverter 支持给定内容类型的请求体转换为类对象,就使用它读取请求体转换为类对象。

RequestResponseBodyMethodProcessor 将 Controller 方法返回值转换为响应体的 #writeWithMessageConverters 方法核心代码如下。

在这里插入图片描述同样循环 HttpMessageConverter 列表,如果列表中的某个 HttpMessageConverter 支持将类对象转换为给定内容类型的响应体,则使用它输出响应。

自定义 HttpMessageConverter

基于注解的 Spring MVC 实现 WebMvcConfigurer 接口即可自定义 HttpMessageConverter。

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    }

    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    }
}

#configureMessageConverters 方法将会被先调用,如果没有向列表中添加 HttpMessageConverter,Spring MVC 会使用默认的实现,然后调用 #extendMessageConverters 方法再添加 HttpMessageConverter。

所以如果想要覆盖 Spring MVC 中默认的 HttpMessageConverter,可以重写 #configureMessageConverters 方法,如果只是想在默认列表中添加自定义的 HttpMessageConverter 则可以重写 #extendMessageConverters 方法。

假定我们的项目中引入了 fastjson,如果我们想要配置 fastjson 自带的 HttpMessageConverter 处理 application/json 内容类型,可以使用如下的配置。

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
        converter.setSupportedMediaTypes(Collections.singletonList(MediaType.APPLICATION_JSON));
        converters.add(0, converter);
    }
}
  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
@RequestBody和@ResponseBody都是Spring框架中用于RESTful风格的接口处理中的注解,而且它们的作用是针对请求数据和响应数据的。 @RequestBody的作用是用来指定请求数据的格式,也就是告诉Spring框架如何将HTTP请求Body转化为Java对象。如果在一个Controller中使用了@RequestBody注解,那么当客户端发送请求的时候,请求Body中的数据将会被Spring框架解析并转换为对象,然后传给Controller进行处理。 @ResponseBody的作用则是将Java对象直接转化为HTTP响应Body,并且将其作为响应返回给客户端。如果在一个Controller中使用了@ResponseBody注解,那么Spring框架将会将方法的返回结果序列化成JSON或XML格式的数据,然后发送给客户端。 因此,@RequestBody和@ResponseBody两者的区别如下: 1. @RequestBody注解用于请求数据的处理,@ResponseBody注解用于响应数据的处理。 2. @RequestBody注解将HTTP请求Body转化为Java对象,@ResponseBody注解将Java对象直接转化为HTTP响应Body。 3. @RequestBody注解接收请求参数时,请求参数只能是POST请求方式,@ResponseBody注解通过将Java对象序列化成JSON或XML格式的数据发送给客户端。 总之,@RequestBody注解主要用于将HTTP请求Body转化为Java对象,方便Java代码操作请求参数,而@ResponseBody注解主要用于将Java对象序列化成JSON或XML格式的数据发送给客户端,便于客户端操作响应数据。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大鹏cool

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值