SpringMVC 中反序列化与序列化的原理

原文: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

img

查看 Spring MVC 控制器:

img

modifyUserInfo方法里,入参前面加了注解@RequestBody,就能将 HTTP Body 中的 JSON 报文反序列化成UserRequest对象了。这究竟是如何做到的呢?我们来看下调用栈:InvocableHandlerMethod.doInvoke()

img

依次从上往下看调用栈,看看InvocableHandlerMethod.doInvoke()方法:

img

InvocableHandlerMethod.doInvoke()方法里,getBean()返回的就是被代理的类UserController。这里使用到了 JDK 反射,调用UserController里的modifyUserInfo方法。到目前为止,看不到什么与反序列化有关的线索,继续看上一个调用栈:InvocableHandlerMethod.invokeForRequest

img

可以看到Object类型的变量args已经被反序列化了。很好,快接近目标了。我们继续往上看,可以看到这行代码:

Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);

继续看getMethodArgumentValues方法里的实现:

img
img

可以看到,这个argumentResolvers数组里面,装载了 26 个HandlerMethodArgumentResolver接口的实现类(我们先不用关心HandlerMethodArgumentResolver接口是干什么的)。

img

那么,真正用到的是哪个类呢?我们在methodArgumentResolvers.supportsParameter方法里加个断点继续调试。

img

可以看到methodArgumentResolverRequestResponseBodyMethodProcessor类的实例,那么这个supportsParameter方法里面是如何实现的呢?

img

好,我们看到了这个布尔类型的方法,它判断方法参数上有没有@RequestBody注解。在一开始的UserController方法里,我们确实加了@RequestBody注解。

继续回到这里:

img

现在可以知道resolver变量是RequestResponseBodyMethodProcessor类的实例,继续看resolveArgument方法的实现。

img

越来越接近目标了。

img

再看readWithMessageConverters(inputMessage, methodParam, paramType)的实现:

img
img

关键的代码就是这行body = genericConverter.read(targetType, contextClass, inputMessage),这行代码就是将 HTTP Body 中的字符串反序列化成targetType(即UserRequest)对象的。

看到这个 for 循环messageConverters数组没有?这个messageConverters是什么东西?继续看下messageConverters数组里加载的内容:

img

看下这个messageConverters数组是怎么定义的。原来,这个messageConverters是个全局的容器:

protected final List<HttpMessageConverter<?>> messageConverters;

所以,这个messageConverters数组可以在容器的配置文件中配置。有关HttpMessageConverter的内容,这里不赘述。至此,我们大体就能明白 HTTP Body 反序列化成UserRequest的原理和流程了。

到目前为止,对于序列化流程可能还是有点模糊,我们来画个时序图,帮助理清类与方法的调用顺序与关系:

img

序列化(Java 对象 -> HTTP JSON Response)

接着再看UserController,我们打算在modifyUserInfo方法中返回一个GeneralResponse类型的对象。可以看到 Postman 中返回的 Body 报文:

img
img

那么这个序列化又是如何做到的呢?我们继续跟踪源码来寻找答案。

img

可以看到,在调用完ServletInvocableHandlerMethod里的invokeForRequest方法(反序列化)之后,又调用了returnValueHandlers.handleReturnValue方法。从方法名上看,这个方法是用来处理返回值的。我们进去看看它的实现。

img

同样,一开始要选择合适的HandlerMethodReturnValueHandler实现类,然后调用该实例的handleReturnValue方法。我们先看看selectHandler方法是如何适配到合适的HandlerMethodReturnValueHandler实现类的。

img

和刚才类似,这里也是通过遍历returnValueHandlers数组,不断地去判断返回方法是否满足supportsReturnType方法。我们打上断点继续调试。

img

可以发现,满足supportsReturnType方法的实现类是RequestResponseBodyMethodProcessor。我们进入该类,查看supportsReturnType方法的具体实现。

img

这下清楚了,原来是判断方法上是否含有@ResponseBody注解。在modifyUserInfo方法上,我们也确实加了@ResponseBody注解。好,我们找到了具体的处理类RequestResponseBodyMethodProcessor之后,再看下它是如何处理返回值的。

img
img

看到了writeWithMessageConverters方法,大概能猜到这个方法应该就是用于处理输出的。

img
img

这里,会根据返回的 Content-Type 类型,调用canWrite方法,路由到合适的HttpMessageConverter实现类。本例中,实现类是MappingJackson2HttpMessageConverter,最终的序列化是通过调用其write()方法来实现的。

img

好了,我们借用时序图来辅助理清序列化流程:

img

接口研究

上面两节比较详细地调试跟踪了序列化与反序列化的代码,我们发现最终都会进入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这个类的层次关系。

这个类同时实现了HandlerMethodArgumentResolverHandlerMethodReturnValueHandler两个接口。前者是将请求报文绑定到处理方法形参的策略接口,后者是对处理方法返回值进行处理的策略接口。两个接口的源码如下:

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

    }

}

最后说明

以上展示了在实际开发过程中,在使用级别上,思考框架如何实现序列化与反序列化,并带着这个问题,一步一步地跟踪调试源码。希望对读者有所启示。

posted @ 2025-10-25 15:14  Higurashi-kagome  阅读(20)  评论(0)    收藏  举报