springmvc请求源码流程解析(二)
Spring官网的MVC模块介绍:Spring Web MVC是基于Servlet API构建的原始Web框架,从一开始就已包含在Spring框架中。正式名称“Spring Web MVC”来自其源模块的名称(spring-webmvc),但它通常被称为SpringMVC。
从Servlet到SpringMVC:
最典型的MVC就是JSP + servlet + javabean的模式。传统Servlet:

传统servlet的弊端:
1.xml下配置servlet的映射非常麻烦开发效率低
2.必须要继承父类、重写方法侵入性强
3.参数解析麻烦:单个参数(转换类型)--->pojo对象,Json文本--->pojo对象
4.数据响应麻烦:pojo对象--->json
5.跳转页面麻烦, 对path的控制、设置编码麻烦...等等...
所以SpringMVC 就是在Servlet的基础上进行了封装,帮我把这些麻烦事都给我们做了。
SpringMVC的具体执行流程:
Spring MVC 是围绕前端控制器模式设计的,其中中央 Servlet DispatcherServlet 为
请求处理流程提供统一调度,实际工作则交给可配置组件执行。

具体组件作用:
DispatcherServlet:前端调度器,负责将请求拦截下来分发到各控制器方法中。
HandlerMapping: 负责根据请求的URL和配置@RequestMapping映射去匹配, 匹配到会返回Handler(具体控制器的方法)。
HandlerAdaper: 负责调用Handler具体的方法然后返回视图的名字,Handler将它封装到ModelAndView(封装视图名,request域的数据)。
ViewReslover: 根据ModelAndView里面的视图名地址去找到具体的jsp封装在View对象中。
View:进行视图渲染(将jsp转换成html内容,这是Servlet容器的事情了) ,最终response返回客户端。
具体执行流程
1. 用户发送请求至前端控制器DispatcherServlet。
2. DispatcherServlet收到请求调用处理器映射器HandlerMapping。处理器映射器根据请求url找到具体的处理器,生成处理器执行链HandlerExecutionChain(包括处理器对象和处理器拦截器)一并返回给DispatcherServlet。
3. DispatcherServlet根据处理器Handler获取处理器适配器HandlerAdapter,执行HandlerAdapter处理一系列的操作,如:参数封装,数据格式转换,数据验证等操作。
4. 执行处理器Handler(Controller,也叫页面控制器)。Handler执行完成返回ModelAndView,HandlerAdapter将Handler执行结果ModelAndView返回到DispatcherServlet。
5. DispatcherServlet将ModelAndView传给ViewReslover视图解析器,ViewReslover解析后返回具体View。
6. DispatcherServlet对View进行渲染视图(即将模型数据model填充至视图中)。
7. DispatcherServlet响应用户。
整个调用过程其实都在doDispatch中体现了,用户发送请求至前端控制器DispatcherServlet,由于它是个Servlet所以tomcat接收到请求后,会先调用它的service方法-->doGet/doPost-->processRequestdoService--->doDispatch。




1、mappedHandler = getHandler(processedRequest)
DispatcherServlet收到请求调用处理器映射器HandlerMapping。处理器映射器根据请求url找到具体的处理器,生成处理器执行链HandlerExecutionChain (包括处理器对象和处理器拦截器)一并返回给DispatcherServlet。
2、HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler())
DispatcherServlet根据处理器Handler获取处理器适配器HandlerAdapter。
3、mappedHandler.applyPreHandle(processedRequest, response))
前置拦截器
4、ha.handle(processedRequest, response, mappedHandler.getHandler())
执行HandlerAdapter处理一系列的操作,如:参数封装,数据格式转换,数据验证等操作。执行处理器Handler(Controller,也叫页面控制器),Handler执行完成返回ModelAndView,HandlerAdapter将Handler执行结果ModelAndView返回到DispatcherServlet。
5、applyDefaultViewName(processedRequest, mv)
如果没有视图,给你设置默认视图。
6、mappedHandler.applyPostHandle(processedRequest, response, mv)
后置拦截器
HandlerMapping
在整个过程中,涉及到非常多的组件,每个组件解析各个环节,其中HandlerMapping最为重要它是用来映射请求的,我们就着重介绍下HandlerMapping的解析过程和请求映射过程。 HandlerMapping在请求之前会建立映射关系,在HandlerMapping类实例化的时候就会完成url 和method的映射关系,要根据一个请求能够唯一找到一个类和一个方法。
由于实际开发过程中RequestMappingHandlerMapping的使用最多,所以我们先看它的实例化,在其父类AbstractHandlerMethodMapping 中实现了InitializingBean 接口,所以在
RequestMappingHandlerMapping 实例化完成以后就会调用到afterPropertiesSet 方法,在这个方法里面完成了映射关系的建立。



这里判断类上面是否有@Controller注解或@RequestMapping注解,只有这种类才需要建立映射关系,如果类上面有这两个注解,就在detectHandlerMethods方法中建立uri和method的映射关系,这个方法很重要。





1、ReflectionUtils.doWithMethods中循环Controller类里面的所有method方法,执行MethodFilter.doWith方法。
2、那么每个MethodFilter.doWith方法调用都会执行到外面的方法体,T result = metadataLookup.inspect(specificMethod),然后这行方法又会执行到外面的方法体,也就是会执行到getMappingForMethod(method, userType)方法。它会收集方法上面的@RequestMapping注解,把注解里面的配置信息封装到类里面,该类就是RequestMappingInfo类,并且跟类上面的@RequestMapping注解封装类RequestMappingInfo 合并,比如类上面是/common,方法上面是/queryUser。这两者合并后就是/common/queryUser,这样的url 才是我们需要的,合并完就是这样的url。

3、然后建立method 对象和对应的RequestMappingInfo 的映射关系,把关系存放到map 中。

4、然后回对map进行处理,执行registerHandlerMethod方法

5、createHandlerMethod这里会创建HandlerMethod 对象,该类型封装了method、beanName、bean、方法类型等信息。然后this.mappingLookup.put(mapping, handlerMethod)建立RequestMappingInfo 和HandlerMethod 的映射关系。this.urlLookup.add(url, mapping)建立url 和RequestMappingInfo 对象的映射关系。这样映射关系就已经建立好,这样根据请求url 我们就可以唯一的找到一个HandlerMethod 对象了,注意这个对象中还不能进行反射调用,还缺少参数数组。


dispatcherServlet 处理请求
当请求过来时,首先会调用到service 方法,最终会调用到dispatcherServlet 中的doDispatch 方法。根据请求url 获取HandlerExecutionChain 对象,寻找HandlerMethod 的过程由于前面映射关系已经建立好了,现在就是只需要从request 对象中获取请求url,然后从映射关系中获取HandlerMethod 对象就可以了,先从urlLookup中获取RequestMappingInfo 对象,然后再根据RequestMappingInfo对象获取到HandlerMethod。

获取到HandlerMethod 对象后,就把HandlerMethod 对象封装到HandlerExecutionChain 对象中了,这个对象,其实就是封装了HandlerMethod 和一个拦截器数组而已。
![]()

拿到HandlerExecutionChain 对象进行过滤器的调用,调用了前置过滤器preHandle 方法,只要这个方法返回为false,则后续请求就不会继续。

然后是HandlerAdapter调用handle方法,进行具体Controller 中方法的调用这个调用过程,关键点就在于参数的解析,其他都没什么技术含量。

首先获取方法的参数列表,并且把参数封装成MethodParameter 对象,这个对象记录了
参数在参数列表中的索引,参数类型,参数上面的注解数组等等信息。然后循环参数列表,一个个参数来处理,这里是一个典型的策略模式的运用,根据参数获取一个处理该参数的类,把参数一个个处理完成后,放到一个参数数组中了Object[] args。


处理参数的解析类有26 个,如图:

接下来就是反射调用了,有方法method 对象,有类对象,有参数数组就可以进行反射调用了。

返回值处理
当反射调用成功后,有可能方法会有返回值,而返回值处理也是一个比较重要的事情,根据
什么样的方式把返回值响应回去,返回值响应时有可能是数据有可能是界面,而如果返回数据的
话,要把返回值解析成对应的格式,例如如果返回值是一个list 对象,就需要解析这个list对象把list 对象解析成json 格式。返回值解析讨论跟入参解析基本上类似。
1、把返回值封装成对象,对象跟MethodParameter 对象差不多,里面包括参数名称、类型、参数注解等等信息。
2、根据返回值类型用策略模式找到一个解析类,然后用这个解析类解析。
4、一个是带@ResponseBody 注解的,一个是直接返回字符串响应一个界面的,里面涉及到一个ModelAndViewContainer 容器,这个容器会把视图名称设置到里面,而且会把响应到界面的数据也会放到这个容器中。
![]()
后置过滤器的调用时序,是当ha.handle 掉完以后,也就是Controller 里面具体方法调用完以后才轮到后置过滤器调用。
视图渲染
其实就是响应界面,如果返回值没有加@ResponseBody 注解时,这时候是需要响应一个界面给前端的,视图渲染借助了servlet 中的api,如下图这样servlet 就可以响应一个界面给前端,而我们spring 也是差不多的处理方式。

异常解析
Controller 调用过程中的异常解析使用,如图:

类上面加上@ControllerAdvice("com.dsk")注解,这个包定义就是只对这个包里面的Controller 生效。然后类里面的方法加上@ExceptionHandler({ArrayIndexOutOfBoundsException.class})
@ExceptionHandler({NullPointerException.class})表示这个方法当调用过程中出现注解里面定义的异常时会被调用到,这些方法就是对异常处理的方法。
源码的核心思想差不多
1、收集注解包装成类
2、建立@ExceptionHandler 中异常和Method 的映射关系
3、根据出现的异常从映射关系中找到对应的Method 对象
4、反射调用,这个调用逻辑跟Controller 里面具体方法调用逻辑一模一样
