【Spring】DispatcherServlet的启动和初始化
使用过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的启动和初始化的更多相关文章
- 让Spring Boot项目启动时可以根据自定义配置决定初始化哪些Bean
让Spring Boot项目启动时可以根据自定义配置决定初始化哪些Bean 问题描述 实现思路 思路一 [不符合要求] 思路二[满足要求] 思路三[未试验] 问题描述 目前我工作环境下,后端主要的框架 ...
- Spring Boot 2 (七):Spring Boot 如何解决项目启动时初始化资源
Spring Boot 2 (七):Spring Boot 如何解决项目启动时初始化资源 在项目启动的时候需要做一些初始化的操作,比如初始化线程池,提前加载好加密证书等.今天就给大家介绍一个 Spri ...
- Spring IOC容器启动流程源码解析(四)——初始化单实例bean阶段
目录 1. 引言 2. 初始化bean的入口 3 尝试从当前容器及其父容器的缓存中获取bean 3.1 获取真正的beanName 3.2 尝试从当前容器的缓存中获取bean 3.3 从父容器中查找b ...
- SpringBoot 源码解析 (三)----- Spring Boot 精髓:启动时初始化数据
在我们用 springboot 搭建项目的时候,有时候会碰到在项目启动时初始化一些操作的需求 ,针对这种需求 spring boot为我们提供了以下几种方案供我们选择: ApplicationRunn ...
- Tomcat启动时加载数据到缓存---web.xml中listener加载顺序(例如顺序:1、初始化spring容器,2、初始化线程池,3、加载业务代码,将数据库中数据加载到内存中)
最近公司要做功能迁移,原来的后台使用的Netty,现在要迁移到在uap上,也就是说所有后台的代码不能通过netty写的加载顺序加载了. 问题就来了,怎样让迁移到tomcat的代码按照原来的加载顺序进行 ...
- Spring 快速开始 启动Spring
[启动Spring必须配置] [web.xml部署描述符方式] 1.配置Servlet级别上下文 <servlet> <servlet-name>springDispatche ...
- spring mvc之启动过程源码分析
简介 这两个星期都在看spring mvc源码,看来看去还是还是很多细节没了解清楚,在这里把看明白的记录下,欢迎在评论中一起讨论. 一.铺垫 spring mvc是基于servlet的,在正式分析之前 ...
- spring boot应用启动原理分析
spring boot quick start 在spring boot里,很吸引人的一个特性是可以直接把应用打包成为一个jar/war,然后这个jar/war是可以直接启动的,不需要另外配置一个We ...
- Spring Boot应用启动原理分析(转)
在spring boot里,很吸引人的一个特性是可以直接把应用打包成为一个jar/war,然后这个jar/war是可以直接启动的,不需要另外配置一个Web Server. 如果之前没有使用过sprin ...
随机推荐
- Java中main方法参数String[ ] args的使用。
我们刚开始学习java时都会被要求记住主方法(main)的写法,就像这样: public static void main(String[] args){ } public static void m ...
- Visual Studio 2017 集成Crystal Report为ASP.NET MVC呈现报表
最近项目需要实现报表功能,平衡各方面的因素,还是使用Crystal Report(水晶报表) 下载较新版本: http://downloads.businessobjects.com/akdlm/cr ...
- weexpack打包weex项目运行/打包记录
构建weex项目 安装weex-toolkit cnpm install -g weex-toolkit 初始化一个项目只需新建文件夹并在目录下执行 weex init 即可 安装依赖:cnpm in ...
- Java定时任务解决方案
很多项目中都会遇到需要定时任务的情况,本篇文章就结合了Spring中以及SpringBoot.SpringCloud中定时任务的解决方案. 在Spring中使用定时器 用SpringBoot比较多的同 ...
- Java学习笔记一:数据类型I
GitHub代码练习地址:https://github.com/Neo-ML/JavaPractice/blob/master/IntPractice1.java https://github.com ...
- Java虚拟机知识汇总
先膜一波布丁大佬hhh
- @vue/cli 构建得项目eslint配置2
使用ESLint+Prettier来统一前端代码风格 加分号还是不加分号?tab还是空格?你还在为代码风格与同事争论得面红耳赤吗? 正文之前,先看个段子放松一下: 去死吧!你这个异教徒! 想起自己刚入 ...
- 编码之痛:操作系统迁移后redis缓存无法命中
前几天一台内网服务器从ubuntu迁移到了centos,检查一切正常后就没有太在意. 今天有同事反馈迁移后的机器上的服务一个缓存总是无法获取,对比了下环境.JVM参数,尝试了war包替换等方式照样复现 ...
- 微软Cloud+AI本地化社区贡献指南
本文主要介绍微软Cloud+AI本地化社区,以及通过多种途径贡献本地化的操作指南. 什么是本地化社区 Cloud+AI本地化社区是微软技术社区的组成部分之一,负责对微软官方技术文档本地化的支持工作.微 ...
- 动车上的书摘-java网络 连接服务器
摘要: 摘要: 原创出处: http://www.cnblogs.com/Alandre/ 泥沙砖瓦浆木匠 希望转载,保留摘要,谢谢! 应该有些延迟,你会看到黑幕中弹出 来自USA的X原子的计量时间: ...