spring mvc源码分析(整个流程)

写在前面

      正本讲述了spring mvc整个初始化过程以及应用时请求分发的整个过程。
      本文参考了《spring技术内幕》和spring 4.0.5源码以及 http://jinnianshilongnian.iteye.com/blog/1594806 。如有什么不对的地方请指出,谢谢。

spring mvc

      先简要描述一下应用时整个流程,再具体分析初始化和加载情况。以dispatcherServlet为例。

DispatcherServlet流程

       spring mvc源码分析(整个流程)_第1张图片

      1、DispatcherServlet作为统一访问点,进行全局的流程控制;

      2、DispatcherServlet——>HandlerMapping, HandlerMapping将会把请求映射为HandlerExecutionChain对象(包含一个Handler处理器(页面控制器)对象、多个HandlerInterceptor拦截器)对象,通过这种策略模式,很容易添加新的映射策略;

      3、DispatcherServlet——>HandlerAdapter,HandlerAdapter将会把处理器包装为适配器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;

      4、HandlerAdapter——>处理器功能处理方法的调用,HandlerAdapter将会根据适配的结果调用真正的处理器的功能处理方法,完成功能处理;并返回一个ModelAndView对象(包含模型数据、逻辑视图名);

      5、ModelAndView的逻辑视图名——> ViewResolver, ViewResolver将把逻辑视图名解析为具体的View,通过这种策略模式,很容易更换其他视图技术;

      6、View——>渲染,View会根据传进来的Model模型数据进行渲染,此处的Model实际是一个Map数据结构,因此很容易支持其他视图技术;

      7、返回控制权DispatcherServlet,由DispatcherServlet返回响应给用户,到此一个流程结束。

流程分析(初始化后处理请求)

容器初始化

      在WEB容器如tomcat启动时会去读web.xml里的相关属性,然后容器创建一个ServletContext作为根上下文,将转化为键值对,并交给ServletContext(其中有contextConfigLocation也就是配置文件的路径等),创建监听。在web.xml里配置ContextLoaderListener(继承了ServletContextListener监听服务器启动情况)来启动spring容器,服务器启动时会回调监听器的contextInitialized。具体的工作是ContextLoader类完成IOC容器加载(这里是根容器加载,最终还是容器的refresh【refresh详情见spring ioc和aop的解析】)。

      加载完ContextLoaderListener后对标签中的进行按顺序加载()。如DispatcherServlet 加载。DispatcherServlet(继承自FrameworkServlet->HttpServletBean)是HttpServlet的实现也就是说他的初始化方式和servlet类似,初始化时会调用init()方法。把ContextLoaderListener加载的容器设置为双亲容器,也是refresh容器(容器以servlet来命名,ioc容器是WebApplicationContext)。

MVC初始化

MVC的初始化是在servlet的init方法中进行的,其中initWebApplicationContext方法中configureAndRefreshWebApplicationContext方法是初始化ioc容器的(refresh方法所在),在这个之后进行了onRefresh方法调用。方法中初始化了mvc(有以下几个部分)。
spring mvc源码分析(整个流程)_第2张图片

  • MultipartResolver文件上传的分解器
  • 国际化LocaleResolver
  • 其中initHandlerMappings初始化handlermapping(在spring配置文件里配置的),实际上就是赋值dispatcherServlet的一个handlerMappings属性。默认查找所有的handlermappings(在当前容器以及双亲容器中查找HandlerMapping类型的bean,也是用getBean获取,这里注册了handlerMap)构成集合默认使用OrderComparator排序。如果自动查找所有handlermappings被关闭则从容器getBean名字为handlerMapping的bean作为集合的单个元素。最后检查集合如果为空(没有handlermapping)就使用默认的BeanNameUrlHandlerMapping(表示将请求的URL和Bean名字映射,如URL为 “上下文/hello”,则Spring配置文件必须有一个名字为“/hello”的Bean,上下文默认忽略。) 

注意:这里的获取是从之前ioc容器加载完成的基础上获取的(不用加载BeanDefinition)

处理请求

spring mvc源码分析(整个流程)_第3张图片
      dispatcherServlet的基类FrameworkServlet重写了HttpServlet的service以及doGet等方法其中调用了doService方法。对于include请求为request的attribute保存快照,然后分发http请求(doDispatch)。

      doDispatch方法当中先检查请求是否是multipart(如文件上传),如果是将通过MultipartResolver解析,再获得HandlerExecutionChain,获得第一个支持它的handlerAdapter(配置文件里配置的,如SimpleControllerHandlerAdapter实现是如果是Controller的子类就支持),进行http头部last-modified的处理(如果没修改直接返回不处理),interceptor预处理后adapter调用adapter的handler处理(适配后调用具体的handler的处理方法,可以自己实现adapter定义适配的内容)返回ModelAndView(如果没view使用默认view)后再interceptor后处理。如果ModelAndView中的view是名字引用则用viewResolver(也是配置文件里配置的)解析viewName成view(如果有现成的view则直接使用,常见的InternalResourceViewResolver就是用viewClass通过反射方式获得View实例(BeanUtils.instantiateClass)然后填充View类的url(pre+name+suf),attributeMap等属性),调用view的render ,将static attribute,pathVariables或model(也是attribute)以及requestContextAttribute合到一个map里,设置http一些头部信息,然后根据具体的view来进行具体的处理。如JSP的view是将map里的所有属性都加到request里(servletContext),再向request添加一些helper(FMT_TIME_ZONE,FMT_LOCALIZATION_CONTEXT等),获得view中之前设置的url封装成RequestDispatcher转发(forward)到内部定义好的资源上(如JSP页面,JSP页面的加载是web容器负责的,view只是起转发作用)。

获得HandlerExecutionChain

      获得HandlerExecutionChain:dispatcherServlet的handlerMapping属性的getHandler方法根据参数request返回HandlerExecutionChain(一个handler和一个interceptor列表),如URL的方式是从request中获得url,在handlerMap(url对应handler的map)中查找handler(也就是controller),如果找到就(如果获得的handler是beanName就getBean获得实例)进行验证后封装返回,否则遍历handlerMap的keyset也就是路径集合用matcher来匹配当前request的url,如有多个匹配就按默认排序后取第一个,同样处理返回。如果没有匹配的尝试root和default,如果成功同样验证封装返回。

      这个handlerMap的注册是在查找handlerMappings时getBean中bean的postProcessor来完成的。在getBean中populateBean依赖注入之后的initializeBean中beanPostProcessor(ApplicationContextAwareProcessor)初始化ApplicationContext时调用registerHandlers注册handler。

      注意:HandlerExecutionChain中实际上包装了handler和一个interceptor数组和list,这个数组好像一个最新加入的interceptor缓存一样,在chain中addInterceptor时要先把数组中的元素加到list中再把参数中的interceptor加到list中,getInterceptor获取interceptor时也是返回数组(如果数组里没有把list所有的都放在数组里再返回)。

View的渲染

 spring mvc源码分析(整个流程)_第4张图片
      View的种类,jstl就是jsp标签库,还有pdf,excel等实现。Jsp的实现之前介绍过了。

      其中excel的实现是用POI (Apache POI)来实现的,POI的功能是提供API给Java程序对Microsoft Office格式档案读和写。Excel是用HSSFWorkbook作为excel的一个引用,如果指定的url是个excel就把这个excel加载进来(用resourceLoader封装成Resource然后读取其中的inputStream来创建新的HSSFWorkbook),否则新建一个空的HSSFWorkbook对象,其中buildExcelDocument方法让子类实现,提供具体的写入数据的方式。POI可以对sheet,row,col进行读取和写入。

      Pdf的实现是用IText来实现的,IText是基于java的pdf文件生成类库。IText是用Document来作为Pdf的一个抽象,document可以add一个paragraph类增加文本。PdfWriter可以用来将一个现有的pdf文件加载到Document中,也可以设置pdf的一些属性等。

你可能感兴趣的:(技术分享)