你的位置:首页 > Java教程

[Java教程]【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.

接着会执行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.

上面代码执行后,这个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.

回到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被设置为// 所以在DispatcherServlet中使用的IOC容器是 ConfigurableWebApplicationContext wac =   (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); wac.setEnvironment(getEnvironment()); // 设置双亲上下文(也就是根上下文) wac.setParent(parent); wac.setConfigLocation(getContextConfigLocation()); configureAndRefreshWebApplicationContext(wac); return wac;}

建立DispatcherServlet的上下文,需要把根上下文作为参数传递给它,再使用反射实例化上下文对象,并为它设置参数,按默认配置的话这个上下文对象就是

通过上面web容器一系列的操作后,在这个上下文体系建立和初始化完毕的基础上,Spring MVC就可以发挥作用了。此时DispatcherServlet就持有一个以自己的Servlet名称命名的IOC容器,这个IOC容器建立后,意味着DispatcherServlet拥有自己的Bean定义空间,这为使用各个独立的

这个调用关系最初由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容器已经初始化完成的基础之上的。执行完其他的各个初始化操作后,整个初始化过程就基本完成了。