說明
- 在千變化萬的需求面前,使用 springmvc原生的api進行開發,多數情況是可以滿足的,但對於某些特定的場景是無法滿足的,這時候就需要對框架進行擴展了或是重寫源碼組件了。但前提是需要對框架原理流程等掌握透徹,知己知彼,方能動手重構。
- 本文主要研究下 springmvc如何對http協議中的請求報文,進行反序列化輸入和序列化輸出的。簡單的說,研究下消息轉換的輸入與輸出。
- 環境說明
- 操作系統: windows
- 開發IDE: STS 3.8.release
- spring&springmvc版本: 4.3.0.RELEASE
先從以下這個實驗入手: 發送一個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已經被反序列化過了。很好,快接近目標了,我們繼續往上看,可以看到
- 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是個全局的容器,
- 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兩個接口。前者是將請求報文綁定到處理方法形參的策略接口,后者則是對處理方法返回值進行處理的策略接口。兩個接口的源碼如下:
- 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請求是加密的密文,服務端接收到了之后,需要解密;然后處理業務邏輯,最后返回的給客戶端的數據也是加密后的密文。對於這種場景,代碼應該如何設計?一個比較簡單但冗余的方案就是,在每個控制器的每個方法里,進行解密操作,得到明文后,處理業務邏輯,最后再加密返回。這種方案顯然是不現實的,缺點就是:業務邏輯和加解密邏輯耦合在一起,同時重復代碼量又非常的多。比較優雅的,同時又能體現架構思想的方案,就是寫一個類似與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,大體實現思路如下:
- 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配置和springboot注解配置,分別如下: 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>
springboot注解配置返回搜狐,查看更多
- import java.util.List;
- 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 com.fasterxml.jackson.annotation.JsonInclude;
- import com.fasterxml.jackson.databind.ObjectMapper;
- @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()));
- }
- }
最后說明
- 以上展示了在實際開發過程中,在使用級別上,思考框架如何實現序列化與反序列化,並帶着這個問題,一步一步的跟蹤調試源碼。希望對讀者有所啟示。
轉載至
https://www.sohu.com/a/245048777_575744