文章目录
1. 注解@RequestBody 的作用
@RequestBody
用在方法参数上面,通常在 Controller 中使用,用于将请求携带的参数绑定到 request body中,然后借助HttpMessageConverter
转化为目标 Java Bean。要想了解该注解生效的原理,就需要理清框架对请求的具体处理逻辑。
2. Http 请求的处理流程
2.1 HandlerAdapter 的继承结构
SpringMVC 中请求处理的主要流程可参考SpringMVC 源码分析(1)-请求处理主流程,本文主要分析请求到来时 SpringMVC 对请求的处理细节,入口在DispatcherServlet#doDispatch()
方法中,该方法会先找到合适的 HandlerAdapter 对象来处理当前请求:
// 判断使用哪个adapter,逻辑位于 HandlerAdapter子类的 supports()方法
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
HandlerAdapter
是一个接口,有 4 个直接子类,但是对于@RequestMapping
注解的处理方法的调用会在子类 AbstractHandlerMethodAdapter#handle()
中调用到间接子类RequestMappingHandlerAdapter#handleInternal()
实现
2.2 RequestMappingHandlerAdapter 的处理过程
-
从上文可知代码
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
实际会调用到RequestMappingHandlerAdapter#handleInternal()
。从代码流程很容易看出来,这个方法其实就是完成请求处理并返回 ModeAndView对象,其核心逻辑为invokeHandlerMethod
方法@Override protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ······ // Execute invokeHandlerMethod in synchronized block if required. if (this.synchronizeOnSession) { HttpSession session = request.getSession(false); if (session != null) { Object mutex = WebUtils.getSessionMutex(session); synchronized (mutex) { mav = invokeHandlerMethod(request, response, handlerMethod); } } else { // No HttpSession available -> no mutex necessary mav = invokeHandlerMethod(request, response, handlerMethod); } } else { //真正调用到 Controller 中的请求处理方法 mav = invokeHandlerMethod(request, response, handlerMethod); } ······ return mav; }
-
RequestMappingHandlerAdapter#invokeHandlerMethod()
中需注意在该方法里会设置请求参数的解析器以及返回参数的解析器@Nullable protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ServletWebRequest webRequest = new ServletWebRequest(request, response); try { WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod); // 设置请求参数解析器,argumentResolvers为参数解析器列表,参考本类中getDefaultArgumentResolvers()方法可知其包含了哪些解析器 if (this.argumentResolvers != null) { invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); } // 设置返回参数解析器 if (this.returnValueHandlers != null) { invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers); } ······ //调用具体请求处理方法 invocableMethod.invokeAndHandle(webRequest, mavContainer); if (asyncManager.isConcurrentHandlingStarted()) { return null; } return getModelAndView(mavContainer, modelFactory, webRequest); } finally { webRequest.requestCompleted(); } }
-
ServletInvocableHandlerMethod#invokeAndHandle()
中调用invokeForRequest()
方法完成请求处理并得到返回值,之后调用上一个步骤中设置的返回参数解析器处理返回值public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { // 处理请求 Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); setResponseStatus(webRequest); ······ try { // 返回参数解析器处理返回值 this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest); } catch (Exception ex) { if (logger.isTraceEnabled()) { logger.trace(formatErrorForReturnValue(returnValue), ex); } throw ex; } }
-
InvocableHandlerMethod#invokeForRequest()
方法首先去处理请求中携带的参数,之后将其填入方法执行@Nullable public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { // 获取处理后的请求参数值 Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); if (logger.isTraceEnabled()) { logger.trace("Arguments: " + Arrays.toString(args)); } return doInvoke(args); }
-
InvocableHandlerMethod#getMethodArgumentValues()
处理参数,其实质为根据请求的相关条件选择步骤2中设置的解析器解析参数protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { if (ObjectUtils.isEmpty(getMethodParameters())) { return EMPTY_ARGS; } MethodParameter[] parameters = getMethodParameters(); Object[] args = new Object[parameters.length]; for (int i = 0; i < parameters.length; i++) { MethodParameter parameter = parameters[i]; parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); args[i] = findProvidedArgument(parameter, providedArgs); if (args[i] != null) { continue; } // 遍历参数解析器列表,判断是否有满足此次请求要求的解析器 if (!this.resolvers.supportsParameter(parameter)) { throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver")); } try { // 有则调用其 resolveArgument 方法处理参数 args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory); } catch (Exception ex) { // Leave stack trace for later, exception may actually be resolved and handled.. if (logger.isDebugEnabled()) { String error = ex.getMessage(); if (error != null && !error.contains(parameter.getExecutable().toGenericString())) { logger.debug(formatArgumentError(parameter, error)); } } throw ex; } } return args; }
-
解析器匹配的关键是以上
this.resolvers.supportsParameter(parameter)
代码,这段代码内部具体调用不作具体分析,其功能可归纳为根据参数的相关条件匹配到对应的解析器。对于@RequestBody
修饰的参数,可知其对应的解析器为RequestResponseBodyMethodProcessor
,在该类的supportsParameter()
方法中明确其支持解析@RequestBody
注解修饰的参数@Override public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(RequestBody.class); }
-
参数解析器选择完毕,接下来就是解析参数。该过程最终会调用到
RequestResponseBodyMethodProcessor#resolveArgument()
方法@Override public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { parameter = parameter.nestedIfOptional(); // 转化参数关键方法,使用到转换器 Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType()); String name = Conventions.getVariableNameForParameter(parameter); if (binderFactory != null) { WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name); if (arg != null) { validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { throw new MethodArgumentNotValidException(parameter, binder.getBindingResult()); } } if (mavContainer != null) { mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult()); } } return adaptArgumentIfNecessary(arg, parameter); }
-
进入
readWithMessageConverters()
方法,最终来到AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters()
方法,可以看到其使用HttpMessageConverter
解析参数并返回。而此处的HttpMessageConverter 是在RequestMappingHandlerAdapter 中设置解析器的时候添加到每个解析器中的。protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException { ...... Class<?> contextClass = parameter.getContainingClass(); Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null); if (targetClass == null) { ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter); targetClass = (Class<T>) resolvableType.resolve(); } HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null); Object body = NO_VALUE; EmptyBodyCheckingHttpInputMessage message; try { message = new EmptyBodyCheckingHttpInputMessage(inputMessage); // 遍历解析器中的 converter,如果 converter.canRead() 为真,表明该converter支持当前请求的数据转化,则使用 converter.read()转化数据 for (HttpMessageConverter<?> converter : this.messageConverters) { Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass(); GenericHttpMessageConverter<?> genericConverter = (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null); if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) : (targetClass != null && converter.canRead(targetClass, contentType))) { if (message.hasBody()) { HttpInputMessage msgToUse = getAdvice().beforeBodyRead(message, parameter, targetType, converterType); body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) : ((HttpMessageConverter<T>) converter).read(targetClass, msgToUse)); body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType); } else { body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType); } break; } } } catch (IOException ex) { throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage); } ...... return body; }
3. SpringMVC 自定义协议的解析
通过以上分析我们知道,SpringMVC 中 @RequestBody 生效的原理依赖两个因素:
- RequestResponseBodyMethodProcessor 解析器
- 解析器中装载的转换器转换参数
这样我们就有两种方式来完成自定义协议的解析,Github 项目传送门
3.1 重写转换器方式
3.1.1 实现方式
使用Spring 已有的注解,重新写一个HttpMessageConverter 后加入到 Spring 配置中。这种方式生效的原理是 WebMvcConfigurationSupport#requestMappingHandlerAdapter()
初始化时会调用其函数设置转换器
adapter.setMessageConverters(getMessageConverters())
。代码中的 getMessageConverters 会调用 configureMessageConverters,从而触发自定义配置类的 configureMessageConverters方法,将重写的转换器加载到转换器列表中。这样在初始化解析器的时候,就将转换器添加进去给解析器使用了
-
ExMessageConverter 转换器
public class ExMessageConverter extends AbstractGenericHttpMessageConverter<Object> { private static final MediaType EX_STRING = MediaType.valueOf("application/ex-serialize"); @Override public List<MediaType> getSupportedMediaTypes() { MediaType[] typeArr = {EX_STRING}; // 支持的 MediaType return Arrays.asList(typeArr); } @Override protected void writeInternal(Object o, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { // 该方法在处理返回值时调用 outputMessage.getHeaders().setContentType(EX_STRING); System.out.println("third"); String json = "hello:" + o.toString(); outputMessage.getBody().write(json.getBytes()); } @Override protected Object readInternal(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { // 该方法在读取请求的参数时调用 byte[] bytes = StreamUtils.copyToByteArray(inputMessage.getBody()); String json = new String(bytes); return json + "-hello"; } @Override public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { return readInternal(contextClass, inputMessage); } }
-
WebConfig 配置
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { // 设置优先级为 0,优先使用 converters.set(0, new ExMessageConverter()); } }
3.1.2 使用与测试
-
测试的 Controller 方法:
使用POST方式请求,ContentType 必须设置为转换器自定义的支持类型,此处配置为 produces = {“application/ex-serialize”}@RequestMapping(value = "/get/happy", produces = {"application/ex-serialize"}) @ResponseBody public UserInfo getConverter (@RequestBody String acct) { System.out.println(acct); UserInfo user = new UserInfo(); user.setName("HooKong"); user.setAge("90"); return user; }
-
测试结果
3.1 重写解析器方式
这种方式可使用自定义的新注解,新注解的使用方式与 @RequestBody完全相同。实现思路为继承 AbstractMessageConverterMethodProcessor,仿照 RequestResponseBodyMethodProcessor 重写其抽象方法,之后将自定义的消息转换器设置到解析器中,通过自定义的配置类将解析器添加到Spring配置中
-
@ExRequestBody 注解
@Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface ExRequestBody { }
@ExResponseBody 注解
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface ExResponseBody { }
-
ExMethodProcessor 解析器
public class ExMethodProcessor extends AbstractMessageConverterMethodProcessor { public ExMethodProcessor(List<HttpMessageConverter<?>> converters) { super(converters); } // 关键方法,该解析器触发使用的条件 @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(ExRequestBody.class); } // 关键方法,定义该解析器触发使用的条件 @Override public boolean supportsReturnType(MethodParameter returnType) { return returnType.getMethodAnnotation(ExResponseBody.class) != null; } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { parameter = parameter.nestedIfOptional(); Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType()); String name = Conventions.getVariableNameForParameter(parameter); if (binderFactory != null) { WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name); if (arg != null) { validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { throw new MethodArgumentNotValidException(parameter, binder.getBindingResult()); } } if (mavContainer != null) { mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult()); } } return arg; } @Override public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { mavContainer.setRequestHandled(true); ServletServerHttpRequest inputMessage = createInputMessage(webRequest); ServletServerHttpResponse outputMessage = createOutputMessage(webRequest); // Try even with null return value. ResponseBodyAdvice could get involved. writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage); } }
-
WebConfig 配置
@Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private ExMethodProcessor exMethodProcessor; @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { resolvers.add(exMethodProcessor); } @Override public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) { handlers.add(exMethodProcessor); } // 设置该解析器的转换器 @Bean public ExMethodProcessor newExResolver() { return new ExMethodProcessor(Arrays.asList(new ExMessageConverter())); } }