使用过SpringMVC的都知道DispatcherServlet,下面介绍下该Servlet的启动与初始化。作为Servlet,DispatcherServlet的启动与Serlvet的启动过程是相联系的。在Serlvet的初始化过程程中,Serlvet的init方法会被调用,以进行初始化。DispatcherServlet的基类HttpServletBean中的这个初始化过程源码如下:

public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
} // 获取Servlet的初始化参数,对Bean属性进行配置
try {
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
throw ex;
} // 调用子类的initServletBean方法进行具体的初始化工作
initServletBean(); if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
} // initServletBean这个初始化工作位于FrameworkServlet类中
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
}
long startTime = System.currentTimeMillis(); // 这里初始化上下文
try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
catch (RuntimeException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
} if (this.logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
elapsedTime + " ms");
}
} protected WebApplicationContext initWebApplicationContext() {
// 调用WebApplicationContextUtils来得到根上下文,它保存在ServletContext中
// 使用它作为当前MVC上下文的双亲上下文
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null; if (this.webApplicationContext != null) {
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
wac = findWebApplicationContext();
}
if (wac == null) {
wac = createWebApplicationContext(rootContext);
} if (!this.refreshEventReceived) {
onRefresh(wac);
} // 把当前建立的上下文存到ServletContext中,使用的属性名是跟当前Servlet名相关的
if (this.publishContext) {
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
} return wac;
}

在初始化开始时,需要读取配置在ServletContext中的Bean属性参数,这些属性参数设置在web.xml的Web容器初始化参数中。

接着会执行DispatcherServlet持有的IOC容器的初始化过程,在这个过程中,一个新的上下文会被建立起来,这个DispatcherServlet持有的上下文被设置为根上下文的子上下文。可以这么理解,根上下文是和web应用相对应的一个上下文,而DispatcherServlet持有的上下文是和Servlet对应的一个上下文。在一个web应用中可以容纳多个Servlet存在;对应的,对于应用在web容器中的上下文体系,一个根上下文可以作为许多Serlvet上下文的双亲上下文。对这点的理解有助于在web环境中IOC容器的Bean设置和检索有所帮助,因为在向IOC容器getBean时,IOC容器会先向其双亲上下文去getBean,换句话说就是在根上下文中定义的Bean是可以被各个Servlet持有的上下文得到和共享的。DispatcherServlet持有的上下文被建立后,也需要和其他 IOC容器一样完成初始化操作,这个初始化操作就是通过refresh方法完成的,最后DispatcherServlet再给自己持有的上下文命名并设置到web窗口的上下文中(即ServletContext),这个名称和在web.xml中设置的DispatcherServlet的Servlet名称有关,进而保证这个上下文在web环境上下文体系中的唯一性。

上面代码执行后,这个Servlet的上下文就建立起来了,具体取得根上下文的过程是在WebApplicationContextUtils中实现的,源码如下:

public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
// ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE这个属性代表的根上下文
// 在ContextLoaderListener初始化的过程中被建立,并设置到ServletContext中
return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}

这个根上下文是ContextLoader设置到ServletContext中的,使用属性ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,同时对这个IOC容器的Bean配置文件,ContextLoader也进行了设置,默认的位置是在/WEB-INF/applicationContext.xml文件中,由于这个根上下文是DispatcherServlet建立的上下文的双亲上下文,所以根上下文中管理的bean是可以被DispatcherServlet的上下文使用的,反过来则不行,通过getBean向IOC容器获取bean时,会先到其双亲IOC容器尝试获取。

回到FrameworkServlet继续看DispatcherServlet的上下文是怎样建立的,源码如下:

protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
Class<?> contextClass = getContextClass();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Servlet with name '" + getServletName() +
"' will try to create custom WebApplicationContext context of class '" +
contextClass.getName() + "'" + ", using parent context [" + parent + "]");
}
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
} // 实例化需要的具体上下文对象,并为这个上下文对象设置属性
// 这里使用的是DEFAULT_CONTEXT_CLASS,这个DEFAULT_CONTEXT_CLASS被设置为XmlWebApplicationContext.class,
// 所以在DispatcherServlet中使用的IOC容器是XmlWebApplicationContext
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); wac.setEnvironment(getEnvironment());
// 设置双亲上下文(也就是根上下文)
wac.setParent(parent);
wac.setConfigLocation(getContextConfigLocation()); configureAndRefreshWebApplicationContext(wac); return wac;
}

建立DispatcherServlet的上下文,需要把根上下文作为参数传递给它,再使用反射实例化上下文对象,并为它设置参数,按默认配置的话这个上下文对象就是XmlWebApplicationContext对象,这个类型是在DEFAULT_CONTEXT_CLASS参数中设置好并允许BeanUtils使用的,实例化结束之后,还需要为这个上下文对象设置好一些基本的配置,这些配置包括它的双亲上下文、Bean定义配置的文件位置等,完成配置后就通过调用IOC容器的refresh方法来完成IOC容器的最终初始化。

通过上面web容器一系列的操作后,在这个上下文体系建立和初始化完毕的基础上,Spring MVC就可以发挥作用了。此时DispatcherServlet就持有一个以自己的Servlet名称命名的IOC容器,这个IOC容器建立后,意味着DispatcherServlet拥有自己的Bean定义空间,这为使用各个独立的XML文件来配置MVC中各个Bean创建了条件,初始化完成后,Spring MVC的具体实现和普通的Spring应用程序的实现并没有太多差别,在DispatcherServlet的初始化过程中,以对HandlerMapping的初始化调用作为触发点,可以看下图Spring MVC模块初始化的方法调用关系图,

这个调用关系最初由HttpServletBean的init方法触发,这个HttpServletBean是HttpServlet的子类,接着会在HttpServletBean的子类FrameworkServlet中对IOC容器完成初始化,在这个初始化方法中会调用DispatcherServlet的initStrategies方法,在这个initStrategies方法中,启动整个Spring MVC框架的初始化工作。

从上面的方法调用关系图也可以看出对MVC的初始化是在DispatcherServlet的initStrategies中完成的,包括对各种MVC框架的实现元素,比如国际化支持LocalResolver、视图生成的ViewResolver等的初始化工作,源码如下:

protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}

上面各个初始化方法的名称应该比较好理解,这里以常见的HandlerMapping为例来说明initHandlerMappings()实现,Mapping映射的作用就是为HTTP请求找到相应的Controller控制器,HandlerMappings完成对MVC中Controller的定义和配置,DispatcherServlet中HandlerMappings初始化过程源码如下:

private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null; // 这里导入所有的HandlerMapping Bean,这些Bean可以在当前的DispatcherServlet的IOC
// 容器中,也可能在其双亲上下文中,这个detectAllHandlerMappings的默认值设为true,
// 即默认地从所有的IOC容器中获取
if (this.detectAllHandlerMappings) {
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
OrderComparator.sort(this.handlerMappings);
}
}
else {
try {
// 可以根据名称从当前的IOC容器中通过getBean获取HandlerMapping
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
}
} // 如果没找到HandlerMappings,那么需要为Servlet设置默认的HandlerMappings,
// 这些默认的值可以设置在DispatcherServlet.properties中
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
}
}
}

在HandlerMapping初始化的过程中,把在Bean配置文件中配置好的handlerMapping从IOC容器中取得。经过上面的读取过程,handlerMappings变量就已经获取了在BeanDefinition中配置好的映射关系。其他的初始化过程和handlerMappings比较类似,都是从IOC容器中读入配置,所以说MVC初始化过程是建立在IOC容器已经初始化完成的基础之上的。执行完其他的各个初始化操作后,整个初始化过程就基本完成了。

【Spring】DispatcherServlet的启动和初始化的更多相关文章

  1. 让Spring Boot项目启动时可以根据自定义配置决定初始化哪些Bean

    让Spring Boot项目启动时可以根据自定义配置决定初始化哪些Bean 问题描述 实现思路 思路一 [不符合要求] 思路二[满足要求] 思路三[未试验] 问题描述 目前我工作环境下,后端主要的框架 ...

  2. Spring Boot 2 (七):Spring Boot 如何解决项目启动时初始化资源

    Spring Boot 2 (七):Spring Boot 如何解决项目启动时初始化资源 在项目启动的时候需要做一些初始化的操作,比如初始化线程池,提前加载好加密证书等.今天就给大家介绍一个 Spri ...

  3. Spring IOC容器启动流程源码解析(四)——初始化单实例bean阶段

    目录 1. 引言 2. 初始化bean的入口 3 尝试从当前容器及其父容器的缓存中获取bean 3.1 获取真正的beanName 3.2 尝试从当前容器的缓存中获取bean 3.3 从父容器中查找b ...

  4. SpringBoot 源码解析 (三)----- Spring Boot 精髓:启动时初始化数据

    在我们用 springboot 搭建项目的时候,有时候会碰到在项目启动时初始化一些操作的需求 ,针对这种需求 spring boot为我们提供了以下几种方案供我们选择: ApplicationRunn ...

  5. Tomcat启动时加载数据到缓存---web.xml中listener加载顺序(例如顺序:1、初始化spring容器,2、初始化线程池,3、加载业务代码,将数据库中数据加载到内存中)

    最近公司要做功能迁移,原来的后台使用的Netty,现在要迁移到在uap上,也就是说所有后台的代码不能通过netty写的加载顺序加载了. 问题就来了,怎样让迁移到tomcat的代码按照原来的加载顺序进行 ...

  6. Spring 快速开始 启动Spring

    [启动Spring必须配置] [web.xml部署描述符方式] 1.配置Servlet级别上下文 <servlet> <servlet-name>springDispatche ...

  7. spring mvc之启动过程源码分析

    简介 这两个星期都在看spring mvc源码,看来看去还是还是很多细节没了解清楚,在这里把看明白的记录下,欢迎在评论中一起讨论. 一.铺垫 spring mvc是基于servlet的,在正式分析之前 ...

  8. spring boot应用启动原理分析

    spring boot quick start 在spring boot里,很吸引人的一个特性是可以直接把应用打包成为一个jar/war,然后这个jar/war是可以直接启动的,不需要另外配置一个We ...

  9. Spring Boot应用启动原理分析(转)

    在spring boot里,很吸引人的一个特性是可以直接把应用打包成为一个jar/war,然后这个jar/war是可以直接启动的,不需要另外配置一个Web Server. 如果之前没有使用过sprin ...

随机推荐

  1. java基础点总结

    基础知识这种东西,没注意到的永远比想象中多.大部分都是在面试中问到的... 1.static关键字 变量,方法修饰;静态代码块;静态内部类; 静态导入:import static ,静态方法省略类名, ...

  2. [转] Vmware vs Virtualbox vs KVM vs XEN: virtual machines performance comparison

    http://www.ilsistemista.net/index.php/virtualization/1-virtual-machines-performance-comparison.html? ...

  3. KVM虚拟化概述与安装

    虚拟化是构建云计算基础架构不可或缺的关键技术之一,云计算的云端系统,其实质上就是一个大型的KVM分布式系统,虚拟化通过在一个物理平台上虚拟出更多的虚拟平台,而其中的每一个虚拟平台则可以作为独立的终端加 ...

  4. Spring+Redis集成+关系型数据库持久化

    本篇文章主要介绍了"Spring+Redis集成+关系型数据库持久化",主要涉及到Spring+Redis集成+关系型数据库持久化方面的内容,对于Spring+Redis集成+关系 ...

  5. Android屏幕相关的概念

    1. 屏幕尺寸 实际的物理尺寸,作为屏幕的对角线测量. 为简单起见,安卓所有的实际屏幕尺寸为四个广义的大小:小,正常,大,和特大. 2. 屏幕密度 一个屏幕的物理区域内像素的数量:通常称为DPI(每英 ...

  6. 每天学点SpringCloud(四):Feign的使用及自定义配置

    Feign:SpringCloud的官网对它的定义是这样的: 是一个声明式的Web服务客户端.它支持Feign本身的注解.JAX-RS注解以及SpringMVC的注解.Spring Cloud集成Ri ...

  7. 吴恩达机器学习笔记7-梯度下降III(Gradient descent intuition) --梯度下降的线性回归

    梯度下降算法和线性回归算法比较如图: 对我们之前的线性回归问题运用梯度下降法,关键在于求出代价函数的导数,即: 我们刚刚使用的算法,有时也称为批量梯度下降.实际上,在机器学习中,通常不太会给算法起名字 ...

  8. #Java学习之路——基础阶段(第二篇)

    我的学习阶段是跟着CZBK黑马的双源课程,学习目标以及博客是为了审查自己的学习情况,毕竟看一遍,敲一遍,和自己归纳总结一遍有着很大的区别,在此期间我会参杂Java疯狂讲义(第四版)里面的内容. 前言: ...

  9. 向github提交代码不用输入帐号密码

    解决方案:方案一: 在你的用户目录下新建一个文本文件.git-credentials Windows:C:/Users/username Mac OS X: /Users/username Linux ...

  10. 签名时出错: 未在路径 C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\bin

    在运行winform程序时,由于清理解决方案等缘故,出现了下面的情况 解决办法:项目-属性-签名-取消勾选“为ClickOne清单签名” 问题完美解决