• 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

  • 相关阅读:
    hdu 1018
    hdu 1005
    hdu 1222
    hdu 1297
    hdu 1568
    WCF入门, 到创建一个简单的WCF应用程序
    BarTender 通过ZPL命令操作打印机打印条码, 操作RFID标签
    WCF入门的了解准备工作
    C# Bartender模板打印 条码,二维码, 文字, 及操作RFID标签等。
    Qt configure脚本说明
  • 原文地址:https://www.cnblogs.com/lujiquan/p/14024513.html
Copyright © 2020-2023  润新知