说明
- 在千变化万的需求面前,使用 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