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 里面具体方法调用逻辑一模一样