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

说明

  • 在千变化万的需求面前,使用 springmvc原生的api进行开发,多数情况是可以满足的,但对于某些特定的场景是无法满足的,这时候就需要对框架进行扩展了或是重写源码组件了。但前提是需要对框架原理流程等掌握透彻,知己知彼,方能动手重构。
  • 本文主要研究下 springmvc如何对http协议中的请求报文,进行反序列化输入和序列化输出的。简单的说,研究下消息转换的输入与输出。
  • 环境说明
    • 操作系统: windows
    • 开发IDE: STS 3.8.release
    • spring&springmvc版本: 4.3.0.RELEASE
    部分源码调试 反序列化(http body--java对象)

    先从以下这个实验入手: 发送一个post类型的http请求, content-type:application/json

    查看下springmvc控制器中

在modifyUserInfo方法里面,入参前面加了个注解@RequestBody,就能将http body中的json报文,反序列化成UserRequest对象了,究竟是如何做到的呢,看下调用栈: InvocableHandlerMethod.doInvoke()

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

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

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

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

继续看方法 getMethodArgumentValues里的实现:

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

真正使用到了哪个类呢,在 methodArgumentResolvers.supportParameter方法里面加个断点继续调试

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

好,我们看到了这个Boolean型的方法,判断方法参数级别上,有没有 RequestBody注解,在一开始的 UserController方法里,确实加入过@RequestBody注解;

继续回到

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

越来越接近目标了

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

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

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

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

  1. protected final List<HttpMessageConverter<?>> messageConverters;

所以,这个messageConverters数组,可以在容器配置文件中配置,有关 HttpMessageConverter的内容,这里略; 至此,大体就能明白,http body反序列化成UserRequest的原理流程;

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

序列化(java对象--http json response)

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

那么这序列化,又是如何做到的呢,继续跟踪源码来发现答案

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

同样的,一开始选择合适的 HandlerMethodReturnValueHandler类返回实例,然后调用具体实例里的handlerReturnValue方法, 我们先看看方法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两个接口。前者是将请求报文绑定到处理方法形参的策略接口,后者则是对处理方法返回值进行处理的策略接口。两个接口的源码如下:
  1. package org.springframework.web.method.support;
  2. import org.springframework.core.MethodParameter;
  3. import org.springframework.web.bind.WebDataBinder;
  4. import org.springframework.web.bind.support.WebDataBinderFactory;
  5. import org.springframework.web.context.request.NativeWebRequest;
  6. public interface HandlerMethodArgumentResolver {
  7. boolean supportsParameter(MethodParameter parameter);
  8. Object resolveArgument(MethodParameter parameter,
  9. ModelAndViewContainer mavContainer,
  10. NativeWebRequest webRequest,
  11. WebDataBinderFactory binderFactory) throws Exception;
  12. }
  13. package org.springframework.web.method.support;
  14. import org.springframework.core.MethodParameter;
  15. import org.springframework.web.context.request.NativeWebRequest;
  16. public interface HandlerMethodReturnValueHandler {
  17. boolean supportsReturnType(MethodParameter returnType);
  18. void handleReturnValue(Object returnValue,
  19. MethodParameter returnType,
  20. ModelAndViewContainer mavContainer,
  21. NativeWebRequest webRequest) throws Exception;
  22. }
  • RequestResponseBodyMethodProcessor这个类,同时充当了方法参数解析和返回值处理两种角色。我们从它的源码中,可以找到上面两个接口的方法实现。

HandlerMethodArgumentResolver接口的实现:

  1. public boolean supportsParameter(MethodParameter parameter) {
  2. return parameter.hasParameterAnnotation(RequestBody.class);
  3. }
  4. public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
  5. NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
  6. Object argument = readWithMessageConverters(webRequest, parameter, parameter.getGenericParameterType());
  7. String name = Conventions.getVariableNameForParameter(parameter);
  8. WebDataBinder binder = binderFactory.createBinder(webRequest, argument, name);
  9. if (argument != null) {
  10. validate(binder, parameter);
  11. }
  12. mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
  13. return argument;
  14. }

HandlerMethodReturnValueHandler接口的实现

  1. public boolean supportsReturnType(MethodParameter returnType) {
  2. return returnType.getMethodAnnotation(ResponseBody.class) != null;
  3. }
  4. public void handleReturnValue(Object returnValue, MethodParameter returnType,
  5. ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
  6. throws IOException, HttpMediaTypeNotAcceptableException {
  7. mavContainer.setRequestHandled(true);
  8. if (returnValue != null) {
  9. writeWithMessageConverters(returnValue, returnType, webRequest);
  10. }
  11. }

框架扩展

看完上面的代码,应该对整个序列化与反序列化流程脉络非常清晰了。因为两个接口的实现,分别以是否有 @RequestBody和 @ResponseBody为条件,然后分别调用 HttpMessageConverter来进行消息的读写。

  • 假使有一天,有个需求是这样的:对于某些机要敏感的数据,客户端发送的http请求是加密的密文,服务端接收到了之后,需要解密;然后处理业务逻辑,最后返回的给客户端的数据也是加密后的密文。对于这种场景,代码应该如何设计?一个比较简单但冗余的方案就是,在每个控制器的每个方法里,进行解密操作,得到明文后,处理业务逻辑,最后再加密返回。这种方案显然是不现实的,缺点就是:业务逻辑和加解密逻辑耦合在一起,同时重复代码量又非常的多。比较优雅的,同时又能体现架构思想的方案,就是写一个类似与RequestResponseBodyMethodProcessor类,只要自定义的类继承自抽象类AbstractMessageConverterMethodProcessor,然后重写里面的四个方法即可:
  • supportsParameter-判断控制器参数级别是否加上了 @DecryptRequestBody注解;
  • resolveArgument-解密http密文,并将相应的信息封装到对象 DecryptRequest<UserRequest>实例中,方便controller中参数接收,这个类中可以灵活的接收http请求中的各种信息,比如http header中的api version、client os、client version等信息,封装到对象实例 DecryptRequest<UserRequest>实例;
  • supportsReturnType-判断控制方法级别是否含有 @EncryptResponseBody注解;
  • handleReturnValue-对controller返回的对象进行加密输出;
  • 比如自定义类: DecryptBodyEncryptReturnValueProcessor,大体实现思路如下:
  1. public class DecryptBodyEncryptReturnValueProcessor extends AbstractMessageConverterMethodProcessor {
  2. protected DecryptBodyEncryptReturnValueProcessor(List<HttpMessageConverter<?>> converters) {
  3. super(converters);
  4. }
  5. @Override
  6. public boolean supportsReturnType(MethodParameter returnType) {
  7. return returnType.hasMethodAnnotation(EncryptResponseBody.class)
  8. && returnType.getParameterType() == GeneralResponse.class;
  9. }
  10. @SuppressWarnings("unchecked")
  11. @Override
  12. public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer,
  13. NativeWebRequest webRequest) throws Exception {
  14. Assert.isInstanceOf(GeneralResponse.class, returnValue, "Return value object type is:" + returnValue.getClass().getName() + ", must be GeneralResponse.");
  15. // 需要设置请求结束标志,否则会走ModelAndView,寻找view流程
  16. // 参考自 RequestResponseBodyMethodProcessor.handleReturnValue 方法
  17. mavContainer.setRequestHandled(true);
  18. HttpServletRequest httpServletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
  19. GeneralResponse<Object> returnValueResponse = (GeneralResponse<Object>) returnValue;
  20. Object payload = returnValueResponse.getData();
  21. // 加密返回对象
  22. Object encryptPayload = encryptPayload(payload, httpServletRequest);
  23. returnValueResponse.setData(encryptPayload);
  24. ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
  25. ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
  26. // Try even with null return value. ResponseBodyAdvice could get involved.
  27. writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
  28. }
  29. private Object encryptPayload(Object payload, HttpServletRequest httpServletRequest) {
  30. // TODO
  31. return null;
  32. }
  33. @Override
  34. public boolean supportsParameter(MethodParameter parameter) {
  35. // 该类只适用于参数上含有@DecryptRequestBody注解的方法
  36. return parameter.hasParameterAnnotation(DecryptRequestBody.class)
  37. && parameter.getParameterType() == DecryptRequest.class;
  38. }
  39. @SuppressWarnings("unchecked")
  40. @Override
  41. public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
  42. NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
  43. HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
  44. ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);
  45. Object arg = readWithMessageConverters(inputMessage, parameter, parameter.getNestedGenericParameterType());
  46. Assert.isInstanceOf(DecryptRequest.class, arg, "Object instance class name:" + arg.getClass().getName() + ", must be DecryptRequest");
  47. DecryptRequest<Object> decryptRequest = (DecryptRequest<Object>) arg;
  48. String encryptReqData = decryptRequest.getEncryptData();
  49. // 对encryptReqData解密逻辑,略...
  50. String decryptReqData = decryptPayload(encryptReqData);
  51. // 取到EncryptRequest里的泛型类型
  52. ResolvableType resolvableType = ResolvableType.forType(parameter.getNestedGenericParameterType());
  53. ResolvableType nestedGenericType = resolvableType.getGenerics()[0];
  54. ObjectMapper objectMapper = new ObjectMapper();
  55. InputStream iss = new ByteArrayInputStream(decryptReqData.getBytes());
  56. TypeFactory tf = objectMapper.getTypeFactory();
  57. JavaType javaType = tf.constructType(nestedGenericType.getType());
  58. Object body = objectMapper.readValue(iss, javaType);
  59. // 最终组装属性
  60. decryptRequest.setDecryptData(decryptReqData);
  61. decryptRequest.setDecryptRequestBody(body);
  62. decryptRequest.setApiVersion(servletRequest.getHeader(SysConstant.API_VERSION));
  63. decryptRequest.setClientOS(servletRequest.getHeader(SysConstant.CLIENT_OS));
  64. decryptRequest.setClientVersion(servletRequest.getHeader(SysConstant.CLIENT_VERSION));
  65. decryptRequest.setChannel(servletRequest.getHeader(SysConstant.CHANNEL));
  66. decryptRequest.setSignKey(servletRequest.getHeader(SysConstant.SIGN_KEY));
  67. return decryptRequest;
  68. }
  69. private String decryptPayload(String encryptReqData) {
  70. // TODO
  71. return null;
  72. }
  73. }

然后可以在控制器中这么用:

  1. @RequestMapping(value = "/decrypt/body/encrypt/return", method = RequestMethod.POST)
  2. @EncryptResponseBody //表示要对返回报文加密
  3. public GeneralResponse<?> decryptBodeAndEncryptReturn(
  4. @DecryptRequestBody DecryptRequest<UserRequest> decryptRequest) { //表示要对http body解密
  5. UserRequest bizRequest = decryptRequest.getDecryptRequestBody();
  6. log.info("解密后的报文:{}", bizRequest);
  7. Map<String, Object> data = new HashMap<String, Object>();
  8. data.put("key1", "value1");
  9. data.put("key2", 2);
  10. return GeneralResponse.createSuccessRes(data);
  11. }
  • 定义了类 DecryptBodyEncryptReturnValueProcessor,那么如何加入spring消息转换组件当中去呢?针对传统xml配置和springboot注解配置,分别如下: xml配置
  1. <bean id="jackson_mc" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" />
  2. <mvc:annotation-driven>
  3. <mvc:argument-resolvers>
  4. <bean class="org.spm.handler.DecryptBodyEncryptReturnValueProcessor">
  5. <constructor-arg name="converters">
  6. <list>
  7. <ref bean="jackson_mc" />
  8. </list>
  9. </constructor-arg>
  10. </bean>
  11. </mvc:argument-resolvers>
  12. <mvc:message-converters>
  13. <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
  14. <property name="supportedMediaTypes">
  15. <list>
  16. <value>application/json</value>
  17. </list>
  18. </property>
  19. <property name="objectMapper" ref="fasterxmlObjectMapper"></property>
  20. </bean>
  21. </mvc:message-converters>
  22. <mvc:return-value-handlers />
  23. </mvc:annotation-driven>
  24. <bean id="fasterxmlObjectMapper" class="com.fasterxml.jackson.databind.ObjectMapper">
  25. <property name="serializationInclusion">
  26. <util:constant
  27. static-field="com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL" />
  28. </property>
  29. </bean>

springboot注解配置返回搜狐,查看更多

  1. import java.util.List;
  2. import org.assertj.core.util.Lists;
  3. import org.spm.handler.DecryptBodyEncryptReturnValueProcessor;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.context.annotation.Configuration;
  6. import org.springframework.http.MediaType;
  7. import org.springframework.http.converter.HttpMessageConverter;
  8. import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
  9. import org.springframework.web.method.support.HandlerMethodArgumentResolver;
  10. import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
  11. import com.fasterxml.jackson.annotation.JsonInclude;
  12. import com.fasterxml.jackson.databind.ObjectMapper;
  13. @Configuration
  14. public class CustomWebMvcConfig extends WebMvcConfigurerAdapter {
  15. @Bean
  16. public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
  17. return new MappingJackson2HttpMessageConverter();
  18. }
  19. @Bean
  20. public ObjectMapper objectMapper() {
  21. ObjectMapper mapper = new ObjectMapper();
  22. mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
  23. return mapper;
  24. }
  25. @Bean
  26. public List<HttpMessageConverter<?>> messageConverters() {
  27. List<HttpMessageConverter<?>> messageConverters = Lists.newArrayList();
  28. MappingJackson2HttpMessageConverter jacksonMessageConverters = mappingJackson2HttpMessageConverter();
  29. jacksonMessageConverters.setSupportedMediaTypes(Lists.newArrayList(MediaType.APPLICATION_JSON_UTF8));
  30. jacksonMessageConverters.setObjectMapper(objectMapper());
  31. return messageConverters;
  32. }
  33. @Override
  34. public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
  35. super.addArgumentResolvers(argumentResolvers);
  36. argumentResolvers.add(new DecryptBodyEncryptReturnValueProcessor(messageConverters()));
  37. }
  38. }

最后说明

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

转载至

    https://www.sohu.com/a/245048777_575744

 

posted @ 2020-11-23 15:02  老子寻道  阅读(4314)  评论(1编辑  收藏  举报