Spring技术内幕(4)Spring MVC与Web环境

本章环境

Spring MVC 概述、Web环境中的SpringMVC、上下文在Web容器中的启动、Spring MVC的设计与实现、Spring MVC视图的呈现

4.1 SpringMVC概述

在使用Spring MVC的时候,需要在web.xml中配置DispatcherServlet,这个DispatcherServlet可以看成是一个前端控制器的具体实现,还需要在Bean定义中配置Web请求和Controller(控制器)的对应关系,以及各种视图的展现方式。在具体使用Controller的时候,会看到ModelAndView数据的生成,还会看到把ModelAndView数据交给相应的View来进行呈现。

4.2 Web环境中的Spring MVC

Spring MVC 是建立在IoC容器基础上的,要了解SpringMVC,首先要了解Spring IoC是如何在Web环境中发挥作用的。

Spring IoC是个独立的模块,并不是直接在Web环境中发挥作用,在web容器启动过程中,将IoC导入,并在web容器中建立起来并初始化,这样才能建立起SpringMVC运行机制,从而响应web容器传递的HTTP请求。

Tomcat 的web.xml对Spring MVC的配置:

在这个配置描述中,首先定义一个Servlet对象,它是Spring MVC的DispatcherServlet。这个DispatcherServlet是MVC中最重要的一个类,起着分发请求的作用。然后,为这个DispatcherServlet定义了对应的URL映射,这些URL映射为这个Servlet指定了需要处理的HTTP请求。context-param参数的配置用来指定Spring IOC容器读取Bean定义的XML文件的路径,在这里,这个配置文件被定义为/WEBINF/applicationContext.xml。DispatcherServlet和ContextLoaderListener提供了在Web容器中对Spring的接口,这些接口与web容器耦合是通过ServletContext来实现的。

4.3 上下文在Web容器中的启动

4.3.1 IoC容器启动的基本过程

IoC容器启动过程就是建立上下文的过程,该上下文与ServletContext相伴而生,由ContextLoaderListener启动的上下文为根上下文。在根上下文的基础上,还有一个与Web MVC相关的上下文用来保存控制器(DispatcherServlet)需要的MVC对象,作为跟上下文的子上下文。

Web容器中启动Spring应用程序的过程如下图:

在web.xml中,已经配置了由Spring提供的实现了ServletContextListener接口的ContextLoaderListener,该监听器类为在Web容器中建立IoC容器提供服务。在Web容器中,建立WebApplicationContext的过程,是在contextInitialized的接口实现中完成的。具体的载入IOC容器的过程是由ContextLoaderListenser交由ContextLoader来完成的,而ContextLoader本身就是ContextLoaderListener的基类,它们之间的类关系为:

 

在ContextLoader中,完成两个IoC容器建立的基本过程,一个是在Web容器中建立起双亲IoC容器,另一个是生成相应的WebApplicationContext并将其初始化。

4.3.2 Web容器中的上下文设计

WebApplicationContext接口的类继承关系图:

 

 

在启动过程中,Spring使用默认的XmlWebApplicationContext 实现作为IoC容器。

 

 

4.3.3 ContextLoader的设计与实现

对于Spring承载的Web应用而言,可以指定在Web应用程序启动时载人IoC容器(或者称为WebAppl icationCon text)。这个功能是由ContextLoaderListener这样的类来完成的,它是在Web容器中配置的监听器。这个ContextLoaderListener通过使用ContextLoader来完成实际的WebApplicationContext,也就是IoC容器的初始化工作。这个ContextLoader就像Spring应用程序在Web容器中的启动器。这个启动过程是在Web容器中发生的,所以需要根据Web容器部署的要求来定义ContextLoader。 

下面分析具体的根上下文的载入过程。在ContextLoaderListener中,实现的是ServletContextListener接口,这个接口里的函数会结合Web容器的生命周期被调用。因为ServletContextListener是ServletContext的监听者,如果ServletContext发生变化.会触发出相应的事件,而监听器一直在对这些事件进行监听,如果接收到了监听的事件,就会做出预先设计好的响应动作。由于ServletContext的变化而触发的监听器的响应具体包括:在服务器启动时,ServletContext被创建。服务器关闭时,ServletContext将被销毁等。对应这些事件及Web容器状态的变化,在监听器中定义了对应的事件响应的回调方法。比如在服务器启动时,ServletContextListener的contextlnitialized()方法被调用,服务器将要关闭时,ServletContextListener的contextDestroyed()方法被调用。

 

 

在初始化这个上下文以后,该上下文会被存储到SevletContext中,这样就建立了一个全局的关于整个应用的上下文。同时,在启动Spring MVC时.我们还会看到这个上下文被以后的DispatcherServlet在进行自己持有的上下文的初始化时,设置为DispatcherServlet自带的上下文的双亲上下文。

4.4 Spring MVC的设计与实现

4.4.1 Spring MVC的应用场景

在前文的分析过程中,了解了 Spring的上下文体系通过ContextLoader和DispatcherServiet建立并初始化的过程。在完成对ContextLoaderListener的初始化以后,Web容器开始初始化DispatcherServlet,这个初始化的启动与在web.xml中对载入次序的定义有关。

DispatcherServiet会建立自己的上下文来持有Spring MVC的Bean对象,在建立这个自己持有的Ioc容器时,会从ServletContext中得到根上下文作为DispatcherServlet持有上下文的双亲上下文。有了这个根上下文,再对自己持有的上下文进行初始化,最后把自己持有的这个上下文保存到ServletContext(Web容器的上下文)中,供以后检索和使用。

4.4.2 Spring MVC设计概念

DispatcherServlet的处理过程,如下图所示:

 

DispatcherServiet的工作大致可以分为两个部分:一个是初始化部分,由initServletBean()启动,通过initWebAppIicationContext()方法最终调用DispatcherServlet的initStrategies方法。在这个方法里,DispatcherServlet对MVC模块的其他部分进行了初始化,比如handlerMapping, ViewResolver等。另一个是对HTTP请求进行响应,作为一个Serviet,Web容器会调用Servlet的doGet()和doPost()方法,在经过FrameworkServlet的processRequest()简单处理后,会调用DispatcherServlet的doService()方法,在这个方法调用中封装了doDispatch(),这个doDispatch()是Dispatcher实现MVC模式的主要部分。

4.4.3 DispatcherServlet的启动和初始化

作为Servlet,DispatcherServlet的启动与Servlet的启动过程是相联系的。在Servlet的初始化过程中,Servlet的init方法会被调用。在HttpServletBean中进行初始化:

FrameworkServlet中的初始化方法:

 

 

 

由于这个根上下文是DispatcherServlet建立的上下文的双亲上下文,所以根上下文中粉理的Bean也是可以被DispatcherServlet的上下文使用的。通过getBean向IoC容器获取Bean时.容器会先到它的双亲IoC容器中获取getBean。除了上面的SpringMVC上下文的的创建之外,还需要启动SpringMVC中的其他一些配置初始化,通过上面的onRefresh调用来完成,这个方法在子类DispatcherServlet中被覆盖了,实际调用了initStrategies进行配置,这里就不细说这些配置了。 源码如下:

对于具体的初始化过程,根据上面的方法名称,很容易理解。以HanderMapping为例来说明这个initHanderMappings过程。这里的Mapping关系的作用是,为HTTP请求找到相应 的Controller控制器,从而利用这些控制器Controller去完成设计好的数据处理工作。HandlerMappings完成对MVC中Controller的定义和配置,只不过在Web这个特定的应用环境中,这些控制器是与具体的HTTP请求相对应的。DispatcherServlet中HandlerMappings初始化过程的具体实现如下。在HandlerMapping初始化的过程中,把在Bean配置文件中配置好的handlerMapping从IoC容器中取得。

经过以上的读取过程,handlerMappings变量就已经获取了在BeanDefinition中配置好的映射关系。其他的初始化过程和handlerMappings比较类似,都是直接从IoC容器中读入配置,所以这里的MVC初始化过程是建立在IoC容器已经初始化完成的基础上的。至于上下文是如何获得的,可以参见前面对IoC容器在Web环境中加载的实现原理的分析。

4.4.4 MVC处理HTTP分发请求

1.HandlerMapping的配置和设计原理

在初始化完成时,在上下文环境中已定义的所有HandlerMapping都已经被加载了,这些加载的handlerMappings被放在一个List中并排序,存储着HTTP请求对应的映射数据。这个List中的每一个元素都对应着一个具体handlerMapping的配置,一般每一个handlerMapping可以持有一系列从URL请求到Controller的映射,而Spring MVC 提供了一系列的HandlerMapping实现。

 

这个HandlerExecutionChain的实现看起来比较简洁,它持有一个拦截器链和一个handler对象,这个handler对象实际上就是HTTP请求对应的Controller,在持有这个handler对象的同时,还在HandlerExecutionChain中设置了拦截器链,通过这个拦截器链中的拦截器,可以为handler对象提供功能的增强。要完成这些工作,需要对拦截器链和handler都进行配置,这些配置都是在这个类的初始化函数中完成的。为了维护这个拦截器和handler, HandlerExecutionChain还提供了一系列与拦截器链维护相关一些操作,比如可以为拦截器链增加拦截器的addInterceptor方法等。

 

 

 

 

HandlerExecutionChain中定义的Handler和Interceptor需要在定义HandlerMapping时配置好,例如对具体的SimpleURLHandlerMapping, 要做的就是根据URL映射的方式,注册Handler和Interceptor,从而维护一个反映这种映射关系的handlerMap。当需要匹配HTTP请求时,需要查询这个handlerMap中信息来得到对应的HandlerExecutionChain。这些信息是什么时候配置好的呢?这里有一个注册过程,这个注册过程在容器对Bean进行依赖注入时发生,它实际上是通过一个Bean的postProcessor来完成的。如果想了解这个过程,可以从依赖注入那里去看看,doCreateBean->initializeBean -> postProcessBeforeInitialization -> setApplicationContext -> initApplicationContext.

以SimpleUrlHandlerMapping为例,需要注意的是,这里用到了对容器的回调,只有SimpleUrlHandlerMapping是AppicationContextAware的子类才能启动这个注册过程。这个注册过程完成的是反映URL和Controller之间的映射关系的handlerMap的建立。具体分析如下:

这个SimpleUrlHandlerMapping注册过程的完成,很大一部分需要它的基类来配合,这个基类就是AbstractUrlHandlerMapping.

 

 

这个配置好URL请求和handler映射数据的handlerMap,为Spring MVC响应HTTP请求准备好了基本的映射数据,根据这个handlerMap以及设置于其中的映射数据,可以方便地由URL请求得到它所对应的handler。有了这些准备工作,Spring MVC就可以等待HTTP请求的到来了。

2  使用HandlerMapping完成请求的映射处理

继续通过SimpleUrlHandlerMapping的实现来分析HandlerMapping的接口方法getHandler,该方法会根据初始化时得到的映射关系来生成DispatcherServlet需要的HandlerExecutionChain,也就是说,这个getHandler方法是实际使用HandlerMapping完成请求的映射处理的地方。

 

取得handler的具体过程在getHandlerInternal方法中实现,这个方法接受HTTP请求作为参数,它的实现在AbstractHandlerMapping的子类AbstractUrlHandlerMapping中,这个实现过程包括从HTTP请求中得到URL,并根据URL到urlMapping中获得handler。代码实现如下:

 

 

 

 

经过这一系列对HTTP请求进行解析和匹配handler的过程,得到了与请求对应的handler处理器。在返回的handler中,已经完成了在HandlerExecutionChain中的封装工作,为handler对HTTP请求的响应做好了准备。然后,在MVC中,还有一个重要的问题:请求是怎样实现分发,从而到达对应的handler的呢?

3.Spring MVC对HTTP请求的分发处理

重新回到DispatcherServlet,这个类不但建立了自己持有的IoC容器,还肩负着请求分发处理的重任。在MVC框架初始化完成以后,对HTTP请求的处理是在doService()方法中完成的,DispatcherServlet也是通过这个方法来响应HTTP的请求。然后,依据Spring MVC的使用,业务逻辑的调用入口是在handler的handler函数中实现的,这里是连接Spring MVC和应用业务逻辑实现的地方。

 

 

经过上面一系列的处理,得到了handler对象,接着就可以开始调用handler对象中的HTTP响应动作了。在handler中封装了应用业务逻辑,由这些逻辑对HTTP请求进行相应的处理,生成各种需要的数据,并把这些数据封装到ModelAndView对象中去,这个ModelAndView的数据封装是Spring MVC框架的要求。对handler来说,这些都是通过调用handler的handlerRequest方法来触发完成的。在得到ModelAndView对象以后,这个ModelAndView对象会被交给MVC模式中的视图类,由视图类对ModelAndView对象中的数据进行呈现。视图呈现的调用入口在DispatcherServlet的doDispatch方法中实现,它的调用入口是render方法。这个方法在下面介绍。

4.5 Spring MVC视图的呈现

4.5.1 DispatcherServlet视图呈现的设计

前面分析了Spring MVC中的M(Model)和 C(Controller)相关的实现,其中的M大致对应成ModelAndView的生成,而C大致对应到DispatcherServlet和用户业务逻辑有关的handler实现。在Spring MVC框架中,DispatcherServlet起到了非常杧的作用,是整个MVC框架的调用枢纽。对于下面关心的视图呈现功能,它的调用入口同样在

DispatcherServlet中的doDispatch方法中实现。具体来说,在DispatcherServlet中,对视图呈现的处理是在render方法调用中完成的,代码如下。为了完成视图的呈现工作,需要从ModelAndViewc对象中取得视图对象,然后调用视图对象的render方法,由这个视图对象来完成特定的视图呈现工作。同时,由于是在Web的环境中,因此视图对象的呈现往往需要完成与HTTP请求和响应相关的处理,这些对象会作为参数传到视图对象的render方法中,供render方法使用

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值