DispatchServlet处理请求核心流程:
1. getHandler(processedRequest) 获取具体要执行的HandlerExecutionChain
2. getHandlerAdapter 获取处理适配器
3. mappedHandler.applyPreHandle 调用前置拦截器
4. 执行controller.method方法
5. mappedHandler.applyPostHandle 调用中置拦截器
6. processDispatchResult 视图的渲染
7. 调用后置拦截器
上一篇讲到getHandler(processedRequest)方法返回了一个HandlerExecutionChain对象,里面包含了HandleMethod对象和拦截器链,接下来DispatchServlet是如何调用我们写在controller中的业务方法的,拦截器的话后续统一讲
主要流程
1. 获取HandlerAdapter对象
2. 在前置拦截器调用完之后
调用controller的方法
2.1. @ModelAttribute方法的调用,返回值存放在mavc对象中
2.2. ServletInvocableHandlerMethod初始化:有所有参数解析器和返回值解析器
2.3. 参数解析
2.4. 反射执行方法
2.5. 返回值处理
2.6. 返回ModelAndView
源码分析:
1.HandlerAdapter的获取
在第二篇文章讲到,HandlerAdapter组件WebMvcConfigurationSupport用@bean的方式注册到了spring容器中,而DispatchServlet在servlet生命周期的init()方法中从spring容器取出HandlerAdapter类型的对象赋给了他的成员变量handlerAdapters。
接下来就是从handlerAdapters中找出匹配当前方法的handlerAdapter
这里有三个HandleAdapter,源码中使用策略模式,在每个HandleAdapter中都定义了自己对HandleMethod的支持策略
比如HttpRequestHandlerAdapter
这样在主线代码中,可以消除大量的if_else类型判断语句,以及契合了开闭原则。后续有需要增加的HandlerAdapter类型,只需要在类中同样的增加supports提供支持策略,而不用修改主线代码.
最终这里支持当前HandleMethod的ha适配器是
RequestMappingHandlerAdapter,并返回
2.controller方法的调用
之前讲到反射执行方法的必需的controller方法对象和controller的bean对象已经存在于HandlerExecutionChain的HandleMethod中了,
接下来就是用ha适配器传入HandlerExecutionChain的HandleMethod进行业务方法的调用
并且同样需要传入request对象,因为需要讲request中的参数与method的参数绑定起来
点到RequestMappingHandlerAdapter对该方法的实现中
2.1. @ModelAttribute方法的调用,返回值存放在mavc对象中
当某一个controller方法上有@ModelAttribute注解时,可以被其他请求方法用@ModelAttribute 调用它一次,并把他的返回值结果传入其他请求方法
- 收集被请求方法@ModelAttribute调用的方法的HandleMethod对象,封装到了ModelFactory对象中
- 调用有@ModelAttribute注解的方法
调用之后把返回值放到ModelAndViewContainer对象中
后续,请求方法,会从这个容器中拿值赋给方法的参数
2.2 SrvletInvocableHandlerMethod初始化:有所有参数解析器和返回值解析器
后续用这个对象去反射method对象
2.3. 参数解析
在执行方法之前,肯定是需要获取对request的参数解析,并映射到方法的参数列表中。
这里会进行方法参数类型的获取
入参的包装类,里面包装了参数类型,参数名称,参数注解等等信息
2.3.1 参数解析器的查找
解析之前会从参数解析器列表里进行匹配,如果找不到对应的解析器会报错
同样的是策略设计模式,每个参数解析器都会提供是否支持解析该参数类型的方法supportsParameter(parameter)
循环所有解析器,这里有26个解析器
分别调用他们的supportsParameter(parameter)方法
如果有一个支持解析该参数的参数解析器,就返回true.
这里几个常用的解析器
RequestParamMethodArgumentResolver 解析@RequestParam注解的参数 PathVariableMethodArgumentResolver 解析@PathVariable 注解的参数 ServletModelAttributeMethodProcessor 解析@ModelAttribute注解的参数 RequestResponseBodyMethodProcessor 解析@RequestBody注解参数,同时返回@ResponseBody返回值也由它解析
2.3.1 循环解析参数
循环每个参数类型,创建对应长度的数组args[],解析完一个就放到args[对应下标]中
我们准备好controller_method
用postman调用一下
分别看下这些类型的参数对应的解析器是如何解析参数的
ServletModelAttributeMethodProcessor @ModelAttribute RequestParamMethodArgumentResolver @RequestParam PathVariableMethodArgumentResolver @PathVariable RequestResponseBodyMethodProcessor @RequestBody
- 第一个参数 :@ModelAttribute("ma") String ma
前面讲到@ModelAttribute方法被调用之后放到了ModelAndViewContainer中
第一个参数对应获取到的解析器是ServletModelAttributeMethodProcessor
ServletModelAttributeMethodProcessor的resolveArgument方法继承父类ModelAttributeMethodProcessor,进入父类的该方法
先获取到方法中定义的参数名称和参数上的ModelAttribute注解,并建立注解和参数名称的绑定关系
首先根据参数名称去mavContainer获取
如果获取不到的话,就根据注解里的值去mavContainer获取
最后获取到了返回
2.第二个参数 @RequestParam String name
如果方法中没有加任何参数注解,那么默认是当成@RequestParam来处理的
果然在查找解析器的结果是RequestParamMethodArgumentResolver 类
进入该类的resolveArgument方法实现至父类AbstractNamedValueMethodArgumentResolver
点到父类的resolveArgument方法
首先获取参数名称
resolveName方法则是从request.getParameter的形式获取值
3.第三个参数 @PathVariable String name
这种类型的参数获取到的解析器是 PathVariableMethodArgumentResolver
2.4. Controller方法调用,重点看看
回到RequestMappingHandlerAdapter.invokeHandlerMethod()
获取到参数之后,反射调用
2.5. Controller方法调用完之后,需要对返回值进行处理
会先把返回值封装成一个ReturnValueMethodParameter对象
2.5.1 获取返回值处理类
这里同样用策略模式,
- 返回值是@ResponseBody,会获取到RequestResponseBodyMethodProcessor解析类
RequestResponseBodyMethodProcessor是判断方法返回值有没有@ResponseBody注解,我们的返回值类型的确是@ResponseBody
2.5.2 处理返回值
进入这个类的handleReturnValue 看到他封装了下request,response对象
后面他会用消息转换器来处理
这里有8个消息转换器,当前方法匹配到的StringHttpMessageConvert方法
最后会用类似于response.write,以流的形式返回出去
如果返回值是一个@Response 的java对象,则默认会匹配到MappingJackson2HttpMessageConverter 消息转换器
他会先把要返回的信息先序列化一下,再输出
- 返回值是单纯的视图的话,就会匹配这个返回值处理器ViewNameMethodReturnValueHandler他的处理方法会把方法的返回值作为视图名称设置到 mavContainer.view当中,由后面的视图渲染来处理
2.6. 返回ModelAndView
处理请求方法最后面会把根据mavContainer创建一个ModelAndView对象,里面装了modelMap以及视图
至此,controller方法处理完毕