SpringMVC 中反序列化与序列化的原理
说明
- 在千变万化的需求面前,使用 Spring MVC 原生的 API 进行开发,多数情况是可以满足的,但对于某些特定的场景是无法满足的,这时候就需要对框架进行扩展或是重写源码组件。但前提是需要对框架原理、流程等掌握透彻,知己知彼,方能动手重构。
- 本文主要研究下 Spring MVC 如何对 HTTP 协议中的请求报文,进行反序列化输入和序列化输出。简单地说,就是研究消息转换的输入与输出。
- 环境说明
- 操作系统:Windows
- 开发 IDE:STS 3.8.RELEASE
- Spring & Spring MVC 版本:4.3.0.RELEASE
部分源码调试:反序列化(HTTP Body -> Java 对象)
先从以下这个实验入手:发送一个 POST 类型的 HTTP 请求,Content-Type: application/json
![]() |
查看 Spring MVC 控制器:
![]() |
在modifyUserInfo方法里,入参前面加了注解@RequestBody,就能将 HTTP Body 中的 JSON 报文反序列化成UserRequest对象了。这究竟是如何做到的呢?我们来看下调用栈:InvocableHandlerMethod.doInvoke()
![]() |
依次从上往下看调用栈,看看InvocableHandlerMethod.doInvoke()方法:
![]() |
在InvocableHandlerMethod.doInvoke()方法里,getBean()返回的就是被代理的类UserController。这里使用到了 JDK 反射,调用UserController里的modifyUserInfo方法。到目前为止,看不到什么与反序列化有关的线索,继续看上一个调用栈:InvocableHandlerMethod.invokeForRequest。
![]() |
可以看到Object类型的变量args已经被反序列化了。很好,快接近目标了。我们继续往上看,可以看到这行代码:
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
继续看getMethodArgumentValues方法里的实现:
![]() |
![]() |
可以看到,这个argumentResolvers数组里面,装载了 26 个HandlerMethodArgumentResolver接口的实现类(我们先不用关心HandlerMethodArgumentResolver接口是干什么的)。
![]() |
那么,真正用到的是哪个类呢?我们在methodArgumentResolvers.supportsParameter方法里加个断点继续调试。
![]() |
可以看到methodArgumentResolver是RequestResponseBodyMethodProcessor类的实例,那么这个supportsParameter方法里面是如何实现的呢?
![]() |
好,我们看到了这个布尔类型的方法,它判断方法参数上有没有@RequestBody注解。在一开始的UserController方法里,我们确实加了@RequestBody注解。
继续回到这里:
![]() |
现在可以知道resolver变量是RequestResponseBodyMethodProcessor类的实例,继续看resolveArgument方法的实现。
![]() |
越来越接近目标了。
![]() |
再看readWithMessageConverters(inputMessage, methodParam, paramType)的实现:
![]() |
![]() |
关键的代码就是这行body = genericConverter.read(targetType, contextClass, inputMessage),这行代码就是将 HTTP Body 中的字符串反序列化成targetType(即UserRequest)对象的。
看到这个 for 循环messageConverters数组没有?这个messageConverters是什么东西?继续看下messageConverters数组里加载的内容:
![]() |
看下这个messageConverters数组是怎么定义的。原来,这个messageConverters是个全局的容器:
protected final List<HttpMessageConverter<?>> messageConverters;
所以,这个messageConverters数组可以在容器的配置文件中配置。有关HttpMessageConverter的内容,这里不赘述。至此,我们大体就能明白 HTTP Body 反序列化成UserRequest的原理和流程了。
到目前为止,对于序列化流程可能还是有点模糊,我们来画个时序图,帮助理清类与方法的调用顺序与关系:
![]() |
序列化(Java 对象 -> HTTP JSON Response)
接着再看UserController,我们打算在modifyUserInfo方法中返回一个GeneralResponse类型的对象。可以看到 Postman 中返回的 Body 报文:
![]() |
![]() |
那么这个序列化又是如何做到的呢?我们继续跟踪源码来寻找答案。
![]() |
可以看到,在调用完ServletInvocableHandlerMethod里的invokeForRequest方法(反序列化)之后,又调用了returnValueHandlers.handleReturnValue方法。从方法名上看,这个方法是用来处理返回值的。我们进去看看它的实现。
![]() |
同样,一开始要选择合适的HandlerMethodReturnValueHandler实现类,然后调用该实例的handleReturnValue方法。我们先看看selectHandler方法是如何适配到合适的HandlerMethodReturnValueHandler实现类的。
![]() |
和刚才类似,这里也是通过遍历returnValueHandlers数组,不断地去判断返回方法是否满足supportsReturnType方法。我们打上断点继续调试。
![]() |
可以发现,满足supportsReturnType方法的实现类是RequestResponseBodyMethodProcessor。我们进入该类,查看supportsReturnType方法的具体实现。
![]() |
这下清楚了,原来是判断方法上是否含有@ResponseBody注解。在modifyUserInfo方法上,我们也确实加了@ResponseBody注解。好,我们找到了具体的处理类RequestResponseBodyMethodProcessor之后,再看下它是如何处理返回值的。
![]() |
![]() |
看到了writeWithMessageConverters方法,大概能猜到这个方法应该就是用于处理输出的。
![]() |
![]() |
这里,会根据返回的 Content-Type 类型,调用canWrite方法,路由到合适的HttpMessageConverter实现类。本例中,实现类是MappingJackson2HttpMessageConverter,最终的序列化是通过调用其write()方法来实现的。
![]() |
好了,我们借用时序图来辅助理清序列化流程:
![]() |
接口研究
上面两节比较详细地调试跟踪了序列化与反序列化的代码,我们发现最终都会进入RequestResponseBodyMethodProcessor类的实例来进行输入与输出。看下这个类的说明:
Resolves method arguments annotated with @RequestBody and handles return values from methods annotated with @ResponseBody by reading and writing to the body of the request or response with an HttpMessageConverter.
可以得知,这个类就是专门用于解析处理方法参数上标注了@RequestBody注解和方法上标注了@ResponseBody注解的解析器。这个解析器使用了HttpMessageConverter类的实例来进行输入与输出。我们重点看下RequestResponseBodyMethodProcessor这个类的层次关系。
这个类同时实现了HandlerMethodArgumentResolver和HandlerMethodReturnValueHandler两个接口。前者是将请求报文绑定到处理方法形参的策略接口,后者是对处理方法返回值进行处理的策略接口。两个接口的源码如下:
package org.springframework.web.method.support;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
public interface HandlerMethodArgumentResolver {
boolean supportsParameter(MethodParameter parameter);
Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception;
}
package org.springframework.web.method.support;
import org.springframework.core.MethodParameter;
import org.springframework.web.context.request.NativeWebRequest;
public interface HandlerMethodReturnValueHandler {
boolean supportsReturnType(MethodParameter returnType);
void handleReturnValue(Object returnValue,
MethodParameter returnType,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest) throws Exception;
}
RequestResponseBodyMethodProcessor这个类同时充当了方法参数解析和返回值处理两种角色。我们从它的源码中可以找到上面两个接口的方法实现。
对HandlerMethodArgumentResolver接口的实现:
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(RequestBody.class);
}
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Object argument = readWithMessageConverters(webRequest, parameter, parameter.getGenericParameterType());
String name = Conventions.getVariableNameForParameter(parameter);
WebDataBinder binder = binderFactory.createBinder(webRequest, argument, name);
if (argument != null) {
validate(binder, parameter);
}
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
return argument;
}
对HandlerMethodReturnValueHandler接口的实现:
public boolean supportsReturnType(MethodParameter returnType) {
return returnType.getMethodAnnotation(ResponseBody.class) != null;
}
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException {
mavContainer.setRequestHandled(true);
if (returnValue != null) {
writeWithMessageConverters(returnValue, returnType, webRequest);
}
}
框架扩展
看完上面的代码,应该对整个序列化与反序列化流程的脉络非常清晰了。因为两个接口的实现,分别以是否有@RequestBody和@ResponseBody为条件,然后调用HttpMessageConverter来进行消息的读写。
假如有一天,有个需求是这样的:对于某些机要敏感的数据,客户端发送的 HTTP 请求是加密的密文,服务端接收到之后需要解密;然后处理业务逻辑,最后返回给客户端的数据也是加密后的密文。对于这种场景,代码应该如何设计?一个比较简单但冗余的方案是,在每个 Controller 的每个方法里进行解密操作,得到明文后处理业务逻辑,最后再加密返回。这种方案显然是不现实的,缺点是:业务逻辑和加解密逻辑耦合在一起,同时重复代码量非常多。
比较优雅、同时又能体现架构思想的方案,就是写一个类似于RequestResponseBodyMethodProcessor的类。只要自定义的类继承自抽象类AbstractMessageConverterMethodProcessor,然后重写里面的四个方法即可:
supportsParameter:判断 Controller 参数上是否加了自定义的@DecryptRequestBody注解;resolveArgument:解密 HTTP 密文,并将相应的信息封装到DecryptRequest<UserRequest>实例中,方便 Controller 中的参数接收。这个类中可以灵活地接收 HTTP 请求中的各种信息,比如 HTTP Header 中的 api version、client os、client version 等信息,并封装到DecryptRequest<UserRequest>实例中;supportsReturnType:判断 Controller 方法上是否含有自定义的@EncryptResponseBody注解;handleReturnValue:对 Controller 返回的对象进行加密输出;
比如,可以自定义一个DecryptBodyEncryptReturnValueProcessor类,大体实现思路如下:
public class DecryptBodyEncryptReturnValueProcessor extends AbstractMessageConverterMethodProcessor {
protected DecryptBodyEncryptReturnValueProcessor(List<HttpMessageConverter<?>> converters) {
super(converters);
}
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return returnType.hasMethodAnnotation(EncryptResponseBody.class)
&& returnType.getParameterType() == GeneralResponse.class;
}
@SuppressWarnings("unchecked")
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest) throws Exception {
Assert.isInstanceOf(GeneralResponse.class, returnValue, "Return value object type is:" + returnValue.getClass().getName() + ", must be GeneralResponse.");
// 需要设置请求结束标志,否则会走 ModelAndView,寻找 view 流程
// 参考自 RequestResponseBodyMethodProcessor.handleReturnValue 方法
mavContainer.setRequestHandled(true);
HttpServletRequest httpServletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
GeneralResponse<Object> returnValueResponse = (GeneralResponse<Object>) returnValue;
Object payload = returnValueResponse.getData();
// 加密返回对象
Object encryptPayload = encryptPayload(payload, httpServletRequest);
returnValueResponse.setData(encryptPayload);
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
// Try even with null return value. ResponseBodyAdvice could get involved.
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
private Object encryptPayload(Object payload, HttpServletRequest httpServletRequest) {
// TODO
return null;
}
@Override
public boolean supportsParameter(MethodParameter parameter) {
// 该类只适用于参数上含有 @DecryptRequestBody 注解的方法
return parameter.hasParameterAnnotation(DecryptRequestBody.class)
&& parameter.getParameterType() == DecryptRequest.class;
}
@SuppressWarnings("unchecked")
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);
Object arg = readWithMessageConverters(inputMessage, parameter, parameter.getNestedGenericParameterType());
Assert.isInstanceOf(DecryptRequest.class, arg, "Object instance class name:" + arg.getClass().getName() + ", must be DecryptRequest");
DecryptRequest<Object> decryptRequest = (DecryptRequest<Object>) arg;
String encryptReqData = decryptRequest.getEncryptData();
// 对 encryptReqData 解密逻辑,略...
String decryptReqData = decryptPayload(encryptReqData);
// 取到 EncryptRequest 里的泛型类型
ResolvableType resolvableType = ResolvableType.forType(parameter.getNestedGenericParameterType());
ResolvableType nestedGenericType = resolvableType.getGenerics()[0];
ObjectMapper objectMapper = new ObjectMapper();
InputStream iss = new ByteArrayInputStream(decryptReqData.getBytes());
TypeFactory tf = objectMapper.getTypeFactory();
JavaType javaType = tf.constructType(nestedGenericType.getType());
Object body = objectMapper.readValue(iss, javaType);
// 最终组装属性
decryptRequest.setDecryptData(decryptReqData);
decryptRequest.setDecryptRequestBody(body);
decryptRequest.setApiVersion(servletRequest.getHeader(SysConstant.API_VERSION));
decryptRequest.setClientOS(servletRequest.getHeader(SysConstant.CLIENT_OS));
decryptRequest.setClientVersion(servletRequest.getHeader(SysConstant.CLIENT_VERSION));
decryptRequest.setChannel(servletRequest.getHeader(SysConstant.CHANNEL));
decryptRequest.setSignKey(servletRequest.getHeader(SysConstant.SIGN_KEY));
return decryptRequest;
}
private String decryptPayload(String encryptReqData) {
// TODO
return null;
}
}
然后可以在控制器中这么用:
@RequestMapping(value = "/decrypt/body/encrypt/return", method = RequestMethod.POST)
@EncryptResponseBody //表示要对返回报文加密
public GeneralResponse<?> decryptBodeAndEncryptReturn(
@DecryptRequestBody DecryptRequest<UserRequest> decryptRequest) { //表示要对 http body 解密
UserRequest bizRequest = decryptRequest.getDecryptRequestBody();
log.info("解密后的报文:{}", bizRequest);
Map<String, Object> data = new HashMap<String, Object>();
data.put("key1", "value1");
data.put("key2", 2);
return GeneralResponse.createSuccessRes(data);
}
定义了类DecryptBodyEncryptReturnValueProcessor,那么如何加入 Spring 消息转换组件当中去呢?针对传统 XML 配置和 Spring Boot 注解配置,分别如下:
XML 配置
<bean id="jackson_mc" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="org.spm.handler.DecryptBodyEncryptReturnValueProcessor">
<constructor-arg name="converters">
<list>
<ref bean="jackson_mc"/>
</list>
</constructor-arg>
</bean>
</mvc:argument-resolvers>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>application/json</value>
</list>
</property>
<property name="objectMapper" ref="fasterxmlObjectMapper"></property>
</bean>
</mvc:message-converters>
<mvc:return-value-handlers/>
</mvc:annotation-driven>
<bean id="fasterxmlObjectMapper" class="com.fasterxml.jackson.databind.ObjectMapper">
<property name="serializationInclusion">
<util:constant
static-field="com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL"/>
</property>
</bean>
Spring Boot 注解配置
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.assertj.core.util.Lists;
import org.spm.handler.DecryptBodyEncryptReturnValueProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import java.util.List;
@Configuration
public class CustomWebMvcConfig extends WebMvcConfigurerAdapter {
@Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
return new MappingJackson2HttpMessageConverter();
}
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return mapper;
}
@Bean
public List<HttpMessageConverter<?>> messageConverters() {
List<HttpMessageConverter<?>> messageConverters = Lists.newArrayList();
MappingJackson2HttpMessageConverter jacksonMessageConverters = mappingJackson2HttpMessageConverter();
jacksonMessageConverters.setSupportedMediaTypes(Lists.newArrayList(MediaType.APPLICATION_JSON_UTF8));
jacksonMessageConverters.setObjectMapper(objectMapper());
return messageConverters;
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
super.addArgumentResolvers(argumentResolvers);
argumentResolvers.add(new DecryptBodyEncryptReturnValueProcessor(messageConverters()));
}
}
最后说明
以上展示了在实际开发过程中,在使用级别上,思考框架如何实现序列化与反序列化,并带着这个问题,一步一步地跟踪调试源码。希望对读者有所启示。






























浙公网安备 33010602011771号