上篇介绍了SpringMVC相关的基础类,下面分析一下各个类是如何工作的。
首先从web.xml这个配置文件开始,在介绍Servlet的时候,提到过:
ApplicationContext是Spring的核心,在介绍Spring的时候知道它是一个Bean容器,在SpringMVC中,会用到的是WebApplicationContext,它继承自ApplicationContext。
WebApplicationContext的初始化方式和以前介绍的BeanFactory、ApplicationContext容器有所区别,因为WebApplicationContext需要ServletContext实例,也就是说它必须在拥有Web容器的前提下才能完成启动的工作。
在web.xml中配置自启动的Servlet或定义Web容器监听器(ServletContextListener),借助着两者中的任何一个,我们就可以启动Spring Web应用上下文的工作。
下面就来介绍一下这个web监听容器ServletContextListener。
一、ContextLoaderListener
这两个名字非常相似,首先ServletContextListener是一个接口,使用这个接口,它的位置是package javax.servlet,与Spring无关,使用这个接口,开发者能够在为客户端请求提供服务之前向ServletContext中添加任意的对象。
而ContextLoaderListener就是Spring用来实现这一接口的类,它向ServletContext中添加的对象就是一个WebApplicationContext对象。
1 | package org.springframework.web.context; |
ContextLoaderListener实现了ServletContextListener接口,在Servlet容器启动的时候,会初始化一个WebApplicationContext的实现类,并将其作为ServletContext的一个属性设置到Servlet环境中,具体实现代码在父类ContextLoader中:
1 | servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); |
ContextLoaderListener所初始化的这个Spring容器上下文,被称为根上下文。
下面会介绍到,Spring在DispatcherServlet的初始化过程中,同样会初始化一个WebApplicationContext的实现类,作为自己独有的上下文,这个独有的上下文,会将上面的根上下文作为自己的父上下文,来存放SpringMVC的配置元素,然后同样作为ServletContext的一个属性,被设置到ServletContext中,只不过它的key就稍微有点不同,key和具体的DispatcherServlet注册在web.xml文件中的名字有关,从这一点也决定了,我们可以在web.xml文件中注册多个DispatcherServlet,因为Servlet容器中注册的Servlet名字肯定不一样,设置到Servlet环境中的key也肯定不同。
由于在Spring容器中,子上下文可以访问到所有父上下文中的信息,而父上下文访问不到子上下文的信息,这个根上下文,就很适合作为多个子上下文配置的集中点。以官方文档中的图来说明:
还有一张图比较直观:
从图中可以看出:
- ContextLoaderListener初始化的上下文加载的Bean是对于整个应用程序共享的,不管是使用什么表现层技术,一般如DAO层、Service层Bean;
- DispatcherServlet初始化的上下文加载的Bean是只对Spring Web MVC有效的Bean,如Controller、HandlerMapping、HandlerAdapter等等,该初始化上下文应该只加载Web相关组件。
下面具体介绍一下Servlet接口的实现类,也是SpringMVC的核心,DispatcherServlet。
二、DispatcherServlet
1、类继承关系
再来看一下它的继承关系。
它最上面继承自HttpServlet,下面主要有三个具体的类:
- HttpServletBean
- FrameworkServlet
- DispatcherServlet
通过这三个不同层次的类,来抽象不同的职责。
而因为它继承自HttpServlet,所以先简单回顾一下Servlet的生命周期:
初始化阶段 init()方法
容器调用 init() 方法(一生只调一次,并且是在第一次调用Servlet的时候执行)
运行阶段 Service()方法
销毁阶段 destroy() 方法
这篇文章介绍的主要就是初始化方法,也就是从web容器调用init()方法开始。
2、HttpServletBean类与init方法
init方法在HttpServletBean类中:
1 | package org.springframework.web.servlet; |
该方法主要做了两件事:
将Servlet初始化参数(init-param)设置到该组件上(如contextAttribute、contextClass、namespace、contextConfigLocation),通过BeanWrapper简化设值过程,方便后续使用;
提供给子类初始化扩展点,initServletBean(),该方法由FrameworkServlet覆盖。
关于这个设置属性,HttpServletBean这个类的设计中,运用了依赖注入思想完成了<init-param>
配置元素的读取。他抽离出HttpServletBean这个类的目的也在于此,就是“以依赖注入的方式来读取Servlet类的<init-param>
配置信息”,而且这里很明显是一种setter注入。
明白了HttpServletBean类的设计思想,我们也就知道可以如何从中获益。具体来说,我们继承HttpServletBean类(就像DispatcherServlet做的那样),在类中定义一个属性,为这个属性加上setter方法后,我们就可以在<init-param>
元素中为其定义值。在类被初始化后,值就会被注入进来,我们可以直接使用它,避免了样板式的getInitParameter()方法的使用,而且还免费享有Spring中资源编辑器的功能,可以在web.xml中,通过“classpath:”直接指定类路径下的资源文件。
个人理解:从这个类的名字上也可以看出它的作用,HttpServletBean,分别为HttpServlet和Bean,一方面继承自HttpServlet,重写init方法作为入口,另一方面又是Bean,并且加载了在web.xml里面通过参数配置,来实现依赖注入。
下面就是调用的initServletBean方法。
2、FrameworkServlet类与initServletBean方法
这个方法在子类FrameworkServlet中重写:
1 | package org.springframework.web.servlet; |
该方法主要做了两件事:
- 初始化web上下文
- 提供给子类初始化扩展点
这个上下文是DispatcherServlet创建的上下文,和上面Listener创建的上下文是父子关系。
下面是初始化web上下文的代码:
1 |
|
结合上面的注释,看一下具体过程:
- 获取由ContextLoaderListener初始化并注册在ServletContext中的根上下文,记为rootContext
- 如果webApplicationContext已经不为空,表示这个Servlet类是通过编程式注册到容器中的(Servlet 3.0+中的ServletContext.addServlet() ),上下文也由编程式传入。若这个传入的上下文还没被初始化,将rootContext上下文设置为它的父上下文,然后将其初始化,否则直接使用。
- 通过wac变量的引用是否为null,判断第2步中是否已经完成上下文的设置(即上下文是否已经用编程式方式传入),如果wac==null成立,说明该Servlet不是由编程式注册到容器中的。此时以contextAttribute属性的值为键,在ServletContext中查找上下文,查找得到,说明上下文已经以别的方式初始化并注册在contextAttribute下,直接使用。
- 检查wac变量的引用是否为null,如果wac==null成立,说明2、3两步中的上下文初始化策略都没成功,此时调用createWebApplicationContext(rootContext),建立一个全新的以rootContext为父上下文的上下文,作为SpringMVC配置元素的容器上下文。大多数情况下我们所使用的上下文,就是这个新建的上下文。
- 以上三种初始化上下文的策略,都会回调onRefresh(ApplicationContext context)方法(回调的方式根据不同策略有不同),onRefresh方法在DispatcherServlet类中被覆写,以上面得到的上下文为依托,完成SpringMVC中默认实现类的初始化。
- 最后,将这个上下文发布到ServletContext中,也就是将上下文以一个和Servlet类在web.xml中注册名字有关的值为键,设置为ServletContext的一个属性。你可以通过改变publishContext的值来决定是否发布到ServletContext中,默认为true。
以上面6点跟踪FrameworkServlet类中的代码,可以比较清晰的了解到整个容器上下文的建立过程,也就能够领会到FrameworkServlet类的设计目的,它是用来建立一个和Servlet关联的Spring容器上下文,并将其注册到ServletContext中的。跳脱开SpringMVC体系,我们也能通过继承FrameworkServlet类,得到与Spring容器整合的好处,FrameworkServlet和HttpServletBean一样,是一个可以独立使用的类。整个SpringMVC设计中,处处体现开闭原则,这里显然也是其中一点。
可以看见最下面的onRefresh方法,和上面的套路一样,都是留给子类实现的。
3、DispatcherServlet类与onRefresh方法
FrameworkServlet的子类自然就是DispatcherServlet了,看一下它的onRefresh方法:
1 | package org.springframework.web.servlet; |
DispatcherServlet类覆写了父类FrameworkServlet中的onRefresh(ApplicationContext context)方法,提供了SpringMVC各种编程元素的初始化。当然这些编程元素,都是作为容器上下文中一个个bean而存在的。具体的初始化策略,在initStrategies()方法中封装。
4、initHandlerMappings方法
我们以其中initHandlerMappings(context)方法为例,分析一下这些SpringMVC编程元素的初始化策略,其他的方法,都是以类似的策略初始化的。
1 | /** |
这段光是看代码和注释就可以看明白,关键的一个变量在于this.detectAllHandlerMappings
- 如果为true:会把上下文中所有HandlerMapping类型的Bean都注册在handlerMappings这个List变量中。
- 如果为false:那么将尝试获取名为handlerMapping的Bean,新建一个只有一个元素的List,将其赋给handlerMappings
- 如果经过上面的过程,handlerMappings变量仍为空,那么说明你没有在上下文中提供自己HandlerMapping类型的Bean定义。此时,SpringMVC将采用默认初始化策略来初始化handlerMappings。
而这个变量默认为true,可以通过修改web.xml中DispatcherServlet的初始参数来将其设置为false
1 | <init-param> |
下面再看一下这个默认的初始化策略:
1 | /** |
它是一个范型的方法,承担所有SpringMVC编程元素的默认初始化策略比如HandlerAdapters的默认初始化策略也通过它。方法的内容比较直白,就是以传递类的名称为键,从defaultStrategies这个Properties变量中获取实现类,然后反射初始化。
需要说明一下的是defaultStrategies变量的初始化,它是在DispatcherServlet的静态初始化代码块中加载的。
1 | private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties"; |
这个DispatcherServlet.properties里面,以键值对的方式,记录了SpringMVC默认实现类,在org.springframework.web.servlet包里面。
1 | # Default implementation classes for DispatcherServlet's strategy interfaces. |
spring 3.1 以后有些类可能进行了替换,比如
RequestMappingHandlerMapping 来替换 DefaultAnnotationHandlerMapping,
用 RequestMappingHandlerAdapter 来替换 AnnotationMethodHandlerAdapter。
看一下默认的几个HandlerMapping的作用:
- BeanNameUrlHandlerMapping:通过对比url和bean的name找到对应的对象
- RequestMappingHandlerMapping:主要是针对注解配置@RequestMapping的,取代了最早的DefaultAnnotationHandlerMapping
再看一下HandlerAdapter:
HttpRequestHandlerAdapter:要求handler实现Controller接口,该接口的方法为
ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
SimpleControllerHandlerAdapter:要求handler实现HttpRequestHandler接口,该接口的方法为:
void handleRequest(HttpServletRequest request, HttpServletResponse response)
RequestMappingHandlerAdapter:和上面的RequestMappingHandlerMapping配对使用,针对@RequestMapping,取代了最早的AnnotationMethodHandlerAdapter。
至此,我们分析完了initHandlerMappings(context)方法的执行过程,其他的初始化过程与这个方法非常类似。所有初始化方法执行完后,SpringMVC正式完成初始化,静静等待Web请求的到来。
三、总结
上面介绍了DispatcherServlet对应Servlet生命周期的init阶段,可以发现HttpServletBean、FrameworkServlet、DispatcherServlet三个不同的类层次,SpringMVC的设计者将三种不同的职责分别抽象,运用模版方法设计模式分别固定在三个类层次中。
- HttpServletBean完成的是
<init-param>
配置元素的依赖注入 - FrameworkServlet完成的是容器上下文的建立
- DispatcherServlet完成的是SpringMVC具体编程元素的初始化策略。
四、参考地址
https://my.oschina.net/lichhao/blog/100138