Spring/Boot/Cloud系列知识:SpringMVC进行HTTP信息接收和发送的过程
Posted 说好不能打脸
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring/Boot/Cloud系列知识:SpringMVC进行HTTP信息接收和发送的过程相关的知识,希望对你有一定的参考价值。
接上文《Spring/Boot/Cloud系列知识:SpringMVC进行HTTP信息接收和发送的过程(2)》
3、多种HandlerAdapter执行适配器
上一篇文章中我们介绍了Spring MVC组件提供的多种映射处理器(HandlerMapping),但是映射处理器只是负责基于URL信息匹配合适的处理方式并不负责进行具体的处理,所以Spring MVC组件要完整完成为URL信息匹配合理的处理方式的步骤,就还应该为合适的处理方式找到匹配的执行方式,也就是要找到匹配的执行适配器(HandlerAdapter)。Spring MVC组件提供了多种执行适配器,这里本文首先进行介绍:
3.1、SimpleControllerHandlerAdapter执行适配器
如果URL信息通过SimpleUrlHandlerMapping映射处理器、BeanNameUrlHandlerMapping映射处理器匹配到合适的处理方式,那么正式执行这个处理方式的执行适配器,就是SimpleControllerHandlerAdapter。DispatcherServlet将通过doDispatch()方法中的以下逻辑,来调用HandlerAdapter执行适配器的以下代码正式执行处理过程:
public class DispatcherServlet extends FrameworkServlet
// ......
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception
// ......
// 以下位置获取到执行适配器
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// ......
// 在这里调用HandlerAdapter执行适配器,正式执行处理过程:
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// ......
// ......
由于通过SimpleUrlHandlerMapping映射处理器、BeanNameUrlHandlerMapping映射处理器匹配的Controller层方法,都需要实现Spring MVC组件提供的Controller接口(org.springframework.web.servlet.mvc.Controller)中的handleRequest(HttpServletRequest , HttpServletResponse)方法。所以SimpleControllerHandlerAdapter执行适配器的正式执行方法就是直接对Controller.handleRequest(…)方法的调用,源代码如下:
public class SimpleControllerHandlerAdapter implements HandlerAdapter
// ......
// SimpleControllerHandlerAdapter中的handle方法
@Override
@Nullable
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
// 这里的handler(真实的执行者)一定实现了Controller接口
// 那么就调用Controller接口的handleRequest(...)方法
return ((Controller) handler).handleRequest(request, response);
// ......
SimpleControllerHandlerAdapter执行适配器的处理逻辑非常简单,这是因为使用该执行适配器正式执行的Controller层方法的类一定实现了Spring MVC组件提供的Controller接口,匹配的处理场景相对简单。而HttpRequestHandlerAdapter执行适配器是Spring MVC提供的另一种执行适配器,他们的处理场景都相对简单,也不是本专题重点关注的介绍主线,所以这里大致介绍到此即可。
3.2、RequestMappingHandlerAdapter执行适配器
RequestMappingHandlerAdapter执行适配器是Spring MVC组件中另外一种典型的执行适配器,如果一个URL请求基于RequestMappingHandlerMapping映射处理器成功匹配了真实的执行者,那么正式驱动这次执行的适配器就是RequestMappingHandlerAdapter执行适配器。
RequestMappingHandlerAdapter执行适配器也是本专题内容主线上的执行适配器,他基本覆盖了使用@RequestMapping注解(和相关注解)定义的Controller层方法的真实执行场景。这里面当然还涉及正确的HandlerExecutionChain执行管理器。下图描述了这几种关键对象的关系(这些关系实际上在本专题的上一篇文章中已经进行了详细介绍):
这里应该特别关注HandlerExecutionChain执行管理器中记录的真实执行者HandlerMethod类的实例对象。该类中的主要属性的值概括为:
// 这里借助对TestProjectController类中
// @GetMapping("/findParams") public void findParams(String param1 , String param2)controller层方法的调用
// 进行HandlerMethod对象中主要属性的说明
public class HandlerMethod
// ......
// 该属性的值就是真实要调用的Controller方法所属的bean对象,例如XXXXX.TestProjectController类的对象
private final Object bean;
// bean工厂,没有特别情况就是DefaultListableBeanFactory
private final BeanFactory beanFactory;
// bean对象的类型为XXXXX.controller.TestProjectController
private final Class<?> beanType;
// 因为这个实际的method并不是java中的桥接方法
// 所以method属性和bridgedMethod属性一致:XXXXX.TestProjectController.findParams(java.lang.String,java.lang.String)
private final Method method;
private final Method bridgedMethod;
// 由于实际调用的方法有两个参数所以这里就是一个长度为2的数组,每一个都是HandlerMethodParameter类的实例
private final MethodParameter[] parameters;
// 在实际方法没有执行前,responseStatus属性和responseStatusReason属性都没有值
@Nullable
private HttpStatus responseStatus;
@Nullable
private String responseStatusReason;
// ......
后续章节的内容,本文将继续按照RequestMappingHandlerAdapter执行适配器所处理的场景介绍下去,原因之一是其它诸如SimpleControllerHandlerAdapter执行适配器所匹配的处理场景太过简单,没有详细介绍下去的意义;另一个重要原因是,RequestMappingHandlerAdapter执行适配器所代表的处理场景最具代表性,也是读者理解Spring MVC进行HTTP请求处理过程的最佳学习路径。
4、参数解析器
RequestMappingHandlerAdapter执行适配器将借助自己的handleInternal(HttpServletRequest , HttpServletResponse , HandlerMethod) 方法正式开始执行方法的调用。代码片段如下所示:
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response,
HandlerMethod handlerMethod) throws Exception
ModelAndView mav;
checkRequest(request);
// session不是线程安全的,如果系统有保证session线程安全性的要求则执行该分支
// 该分支不在本文内容的主线上,故没有贴出此部分代码
if (this.synchronizeOnSession)
// ......
else
// No synchronization on session demanded at all...
// 执行invokeHandlerMethod方法,该方法内部主要是构造一个ServletInvocableHandlerMethod对象
// 并作相关处理
mav = invokeHandlerMethod(request, response, handlerMethod);
// ......
return mav;
如果说Spring MVC组件进行HTTP信息接收和处理的第一步,是通过映射处理器(HandlerMapping)、执行适配器(HandlerAdapter)和执行管理器(HandlerExecutionChain)找到当次请求是否应该由某个Controller层方法进行处理,以及记录那个最终映射的Controller层方法各方面的描述。
那么Spring MVC组件进行HTTP信息接收和处理的第二步,就是正式调用这个方法。这个步骤涉及到通过参数解析器HandlerMethodArgumentResolver将HTTP请求中的各种信息解析成Controller层方法对应的入参对象,这个步骤还涉及正式进行最终Controller层方法的调用。
注意,因为在Spring MVC组件进行HTTP信息接收和处理的第一步,涉及很多种执行适配器(HandlerAdapter),所以为了专注于对处理主线的描述,本文仅讨论由RequestMappingHandlerAdapter执行适配器进行处理的HTTP请求场景,且执行管理器中的handler属性是一个HandlerMethod对象。
RequestMappingHandlerAdapter执行适配器是针对Controller层方法专门设计的执行适配器,更精确的说法是该执行适配器专门针对@RequestMapping注解处理设计的执行适配器。但是由本专题上一篇文章的描述可知,Controller层方法涉及的定义场景非常复杂,请看如下的示例:
@PostMapping("")
doSomeOtherthing(@RequestBody YourBusiness yourBusiness ,
@PathVariable("param") String param ,
@RequestParam("key") String value ,
@RequestAttribute("org.springframework.session.SessionRepository.CURRENT_SESSION") HttpSession session)
// ......
// 这里是逻辑代码
// ......
可以看到以上示例的controller层方法定义中,第一个参数采用@RequestBody注解,表示使用HTTP请求的Body部分数据进行参数映射;第二个参数采用@PathVariable注解,表示使用URL地址通配符进行参数映射;第三个参数采用@RequestParam注解,表示使用URL的Query部分进行参数映射;第四个参数采用@RequestAttribute注解,表示使用Servlet上下文中的信息进行参数映射。
那么RequestMappingHandlerAdapter执行适配器内部是怎么应对这些复杂场景的呢?我们来看RequestMappingHandlerAdapter执行适配器中最主要的invokeHandlerMethod(HttpServletRequest , HttpServletResponse , HandlerMethod)方法是如何进行处理的:
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try
// ......
// 首先创建一个ServletInvocableHandlerMethod类的对象invocableMethod,它是HandlerMethod类的子类
// 所以读者可以看成,在这个步骤创建了一个handlerMethod对象的副本
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
// 为invocableMethod对象设定系统中已有的参数解析器argumentResolvers。
// 需要注意argumentResolvers是一个全局变量,它是HandlerMethodArgumentResolverComposite类的实例
// 后者是一个典型的组合模式,里面包括了当前系统中所有已注册的参数解析器
if (this.argumentResolvers != null)
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
// 为invocableMethod对象设定系统中已注册的返回值处理器
// 需要注意returnValueHandlers是一个全局变量,它是HandlerMethodReturnValueHandlerComposite类的实例
// 后者也是一个典型的组合模式,其中包括了当前系统中所有已注册的返回值处理器
if (this.returnValueHandlers != null)
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
// 为invocableMethod对象设定其它属性,这些属性在本篇文章中暂时不涉及,后续内容中会进行讲解
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
// ......
// 这里还有一段代码涉及到异步请求处理、Spring MVC中ModelAndView容器的管理
// 但这和本文当前的主旨没有太大关系,所以暂时忽略这段代码
// ......
// 调用invocableMethod对象的invokeAndHandle方法,正式进入Spring MVC组件对HTTP请求处理的第二大步骤
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted())
return null;
return getModelAndView(mavContainer, modelFactory, webRequest);
finally
webRequest.requestCompleted();
由以上代码可知RequestMappingHandlerAdapter执行适配器的invokeHandlerMethod方法构建了一个ServletInvocableHandlerMethod类的对象invocableMethod,然后再为invocableMethod对象设定可支持的参数解析器、返回值处理器等信息,最后调用invocableMethod对象的invokeAndHandle方法正式开始Spring MVC组件处理HTTP请求的第二步。
再次强调,本文这里描述的第二步场景,是基于第一步已经匹配到RequestMappingHandlerAdapter执行适配器,且HandlerExecutionChain执行管理器中的handler属性是一个handlerMethod对象的情况。那么invocableMethod对象的invokeAndHandle方法内部到底进行了一些什么处理呢?我们继续进行源代码执行过程跟踪:
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try
// ......
// 首先创建一个ServletInvocableHandlerMethod类的对象invocableMethod,它是HandlerMethod类的子类
// 所以读者可以看成,在这个步骤创建了一个handlerMethod对象的副本
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
// 为invocableMethod对象设定系统中已有的参数解析器argumentResolvers。
// 需要注意argumentResolvers是一个全局变量,它是HandlerMethodArgumentResolverComposite类的实例
// 后者是一个典型的组合模式(后文会重点讲解),里面包括了当前系统中所有已注册的参数解析器
if (this.argumentResolvers != null)
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
// 为invocableMethod对象设定系统中已注册的返回值处理器
// 需要注意returnValueHandlers是一个全局变量,它是HandlerMethodReturnValueHandlerComposite类的实例
// 后者也是一个典型的组合模式,其中包括了当前系统中所有已注册的返回值处理器
if (this.returnValueHandlers != null)
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
// 为invocableMethod对象设定其它属性,这些属性在本篇文章中暂时不涉及,后续内容中会进行讲解
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
// ......
// 这里还有一段代码涉及到异步请求处理、Spring MVC中ModelAndView容器的管理
// 但这和本文当前的主旨没有太大关系,所以暂时忽略这段代码
// ......
// 调用invocableMethod对象的invokeAndHandle方法,正式进入Spring MVC组件对HTTP请求处理的第二大步骤
invocableMethod.invokeAndHandle(webRequest, mavContainer);
// ......
return getModelAndView(mavContainer, modelFactory, webRequest);
finally
webRequest.requestCompleted();
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception
// 该方法的执行过程主要是两步:
// 第一步是调用invokeForRequest方法正式执行Controller层方法(也是本文重点说明的步骤)
// 第二步是根据返回值情况,进行返回信息的组装
// ======= 以下是第一步
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
// ======= 以下是第二步,由于不是本文的重点,所以暂时省略
// ......
try
this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
catch (Exception ex)
// ......
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception
// invokeForRequest该方法内部主要执行了两个步骤:
// 第一个步骤,就是使用getMethodArgumentValues方法,为将要执行的Controller层方法的每一个参数
// 进行值信息的转换,换句话说就是将HTTP请求中的各种信息,按照Controller层方法上使用的不同参数注解转换成特定的入参值
// 第二个步骤,就是正式将这些得到的参数值,带入到具体的Controller层方法中进行执行,并得到返回值
// ====== 这是第一个步骤(那么这个方法是如何工作的,请看后续的代码内容)
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled())
logger.trace("Arguments: " + Arrays.toString(args));
// ======= 这是第二个步骤
return doInvoke(args);
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception
// 取得将要正式调用的Controller层方法的入参列表
// 如果将要正式调用的Controller层方法无需传入任何入参,则返回一个EMPTY_ARGS常量
// 这个EMPTY_ARGS常量是个长度为0的数组。
MethodParameter[] parameters = getMethodParameters();
if (ObjectUtils.isEmpty(parameters))
return EMPTY_ARGS;
// 以下循环,依次为每一个入参寻找匹配的参数解析器,并进行参数值的绑定
// 请注意以下使用的resolvers全局变量,这个全局变量是一个HandlerMethodArgumentResolverComposite类的对象
// 这是一种典型的组合模式(设计模式中的一种),后文将详细进行讲解
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++)
// ...... 省略了一些本文不进行介绍的代码
// 首先判定当前的参数是否有任何的参数解析器支持参数值的转换
// 如果没有任何参数解析器支持则抛出异常
// 如果存在,HandlerMethodArgumentResolverComposite将记录这个参数所使用的参数解析器
if (!this.resolvers.supportsParameter(parameter))
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
// 这里正是进行入参值的转换,并得到转换结果
try
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
catch (Exception ex)
// ...... 省略了一些无关紧要的代码
throw ex;
// 在完成所有入参值的转换后,向调用者返回一个数组
return args;
// 正式进行Controller层方法的调用
@Nullable
protected Object doInvoke(Object... args) throws Exception
ReflectionUtils.makeAccessible(getBridgedMethod());
try
return getBridgedMethod().invoke(getBean(), args);
catch (IllegalArgumentException ex)
// ..... 省略了一些无关紧要的代码
catch (InvocationTargetException ex)
// ..... 省略了一些无关紧要的代码
以上的代码过程读者无需全部阅读,只需要从以上代码片段中知晓两件事情即可,就是invocableMethod对象的invokeAndHandle方法和其相关方法主要完成了两件事情,第一件事情就是为匹配的Controller层方法转换各个入参,这个过程说得再详细一点就是为指定的入参对象MethodParameter匹配正确的参数解析器,并基于参数解析器将HTTP请求中特定的信息(可能是HTTP的Header部分的某个属性,可能是HTTP信息Query部分的某个传参,还可能是HTTP信息Body部分的信息)转换成入参对象值;第二件事情则是正式调用Controller层方法本身(这个过程相对简单,只需要一笔带过即可)。如下图所示:
后文本专题就来具体介绍多个Spring MVC组件中的重要组成部分——参数解析器(接后文)。
以上是关于Spring/Boot/Cloud系列知识:SpringMVC进行HTTP信息接收和发送的过程的主要内容,如果未能解决你的问题,请参考以下文章
Spring/Boot/Cloud系列知识:SpringMVC 传参详解(下)
Spring/Boot/Cloud系列知识:SpringMVC 传参详解(下)
Spring/Boot/Cloud系列知识:SpringMVC 传参详解(下)
Spring/Boot/Cloud系列知识:SpringMVC进行HTTP信息接收和发送的过程