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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM