Servlet 生命周期从创建到销毁的过程中会执行如下三个方法:

  • init() - 负责初始化 Servlet 对象。在 Servlet 生命周期中只会调用一次。
  • service() - 负责响应客户的请求。每次服务器接收到一个 Servlet 请求时,服务器会产生一个新的线程并调用服务。service 方法中两个参数,分别是 ServletRequest 和 ServletResponse,用于传递 http 请求和回写。
  • destory() - 负责销毁 Servlet 对象。在 Servlet 生命周期中只会调用一次。

DispatcherServlet继承关系图
DispatcherServlet继承关系图

可以看到在Servlet的继承结构中一共有5个类 ,GenericServlet和HttpServlet在java里,流程比较通俗易懂,不再多做叙述 ,剩下的三个类 HttpServletBeanFrameworkServletDispatcherServlet是Spring MVC中的, 本章主要讲解这三个类的创建过程。

这三个类直接实现三个接口: EnvironmentCapableEnvironmentAwareApplication­ContextAware。 XXXAware在spring里表示对XXX可以感知, 通俗点解释就是:如果在某个 类里面想要使用spring的一些东西,就可以 通过实现XXXAware 接口告诉spring, spring看到后就会给你送过来 ,而接收的方式是通过实现接口唯一的方法 set-XXX。 比如,有一个类 想要使用当前的 ApplicationContext, 那么我们只需要让它实现ApplicationContextAware接口 ,然后实现接口中唯一的方法 void setApplicationContext (ApplicationContext applicationContext) 就可以了,spring会自动调用这个方法将applicationContext传给我们, 我们只需要接收就可以 了!很方便吧! EnvironmentCapable, 顾名思义,当然就是具有Environment的能力,也就是可以提供Environment, 所以 EnvironmentCapable唯一的方法是Environment getEnvironment(), 用千实现EnvirorunentCapable接口的类,就是告诉spring它可以提供Environment, 当spring 需要Environment的时候就会调用其getEnvironment方法跟它要。
了解了AwareCapable的意思,下面再来看一下ApplicationContextEnvironment。 前者相信大家都很熟悉了,后者是环境的意思, 具体功能与之前讲过的 ServletContext有点类似。 实际上在HttpServletBeanEnvironment 使用的是Standard-Servlet-Environment (在createEnvironment方法中创建),这里确实封装了ServletContext, 同时还封装了ServletConfigJndiProperty、 系统环境变量和系统属性, 这些都封装到了其propertySources属性下。

Spring版本

Spring 5.X

HttpServletBean

/**
 * Map config parameters onto bean properties of this servlet, and
 * invoke subclass initialization.
 * @throws ServletException if bean properties are invalid (or required
 * properties are missing), or if subclass initialization fails.
 */
@Override
public final void init() throws ServletException {

   // 通过ServletConfig找到web.xml中的init-param参数写入ServletConfigPropertyValues
   //如果是java config方式会跳出
   PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
   if (!pvs.isEmpty()) {
      try {
          // 将 dispatcherServlet 对象包装成 BeanWrapper,可以便于设置属性
         BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
         // 加载文件资源,这里可以理解成加载<init-param>中指定的xml文件,再交由BeanWrapper进行加载
         ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
         bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
         //模板方法,子类实现
         initBeanWrapper(bw);
         bw.setPropertyValues(pvs, true);
      }
      catch (BeansException ex) {
         if (logger.isErrorEnabled()) {
            logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
         }
         throw ex;
      }
   }

   //模板方法,子类实现
   // 交由子类(FrameworkServlet)来进行其特有的初始化工作
   initServletBean();
}

FrameworkServlet

FrameworkServlet 继承自 HttpServletBean,实现了initServletBean()方法。FrameworkServlet 在继承体系结构中,在 Servlet 与 SpringMVC 起到了承上启下的作用,它负责初始化 WebApplicationContext,还负责重写了 Servlet 生命周期中另外两个重要方法——service()destory(),并改写了doGet()doPost()等 http 方法,统一调用processHandler()方法来处理所有 http 请求,子类必须实现doService来处理请求。

@Override
protected final void initServletBean() throws ServletException {
   .......省略..........

   try {
      this.webApplicationContext = initWebApplicationContext();
      initFrameworkServlet();
   }
   catch (ServletException | RuntimeException ex) {
      logger.error("Context initialization failed", ex);
      throw ex;
   }
........省略..........
}

核心代码只有两行,初始化webApplicationContext与初始化FrameworkServlet,其中initFrameworkServlet();又是一个模板方法,交由子类进行自定义实现。

protected WebApplicationContext initWebApplicationContext() {
//获取root Context
   WebApplicationContext rootContext =
         WebApplicationContextUtils.getWebApplicationContext(getServletContext());
   WebApplicationContext wac = null;
    //如果通过构造方法创建了 webApplicationContext
    //java config方式在构造器里就初始化了 webApplicationContext,适用servlet3.0+
   if (this.webApplicationContext != null) {
      // A context instance was injected at construction time -> use it
      wac = this.webApplicationContext;
      if (wac instanceof ConfigurableWebApplicationContext) {
         ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
         if (!cwac.isActive()) {
            // The context has not yet been refreshed -> provide services such as
            // setting the parent context, setting the application context id, etc
            if (cwac.getParent() == null) {
               // The context instance was injected without an explicit parent -> set
               // the root application context (if any; may be null) as the parent
               cwac.setParent(rootContext);
            }
            configureAndRefreshWebApplicationContext(cwac);
         }
      }
   }
   //假如WebApplicationContext已经存在于servletContext,尝试获取
   if (wac == null) {
      // No context instance was injected at construction time -> see if one
      // has been registered in the servlet context. If one exists, it is assumed
      // that the parent context (if any) has already been set and that the
      // user has performed any initialization such as setting the context id
      wac = findWebApplicationContext();
   }
   //如果WebApplicationContext确实还没被创建,直接创建一个
   if (wac == null) {
      // No context instance is defined for this servlet -> create a local one
      wac = createWebApplicationContext(rootContext);
   }

   if (!this.refreshEventReceived) {
      // Either the context is not a ConfigurableApplicationContext with refresh
      // support or the context injected at construction time had already been
      // refreshed -> trigger initial onRefresh manually here.
      synchronized (this.onRefreshMonitor) {
      // DispatcherSevlet 初始化工作的入口
         onRefresh(wac);
      }
   }

   if (this.publishContext) {
      // Publish the context as a servlet context attribute.
      String attrName = getServletContextAttributeName();
      getServletContext().setAttribute(attrName, wac);
   }

   return wac;
}

这里要插播一个小知识点,Spring的几个上下文 里简单提到了容器调用web.xml中配置的ContextLoaderListener,初始化WebApplicationContext上下文环境(即IOC容器),WebApplicationContext被称为根上下文,spring以 WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE 为属性Key,将其存储到ServletContext中。

注:我们常说的WebApplicationContextservletContext也可以理解为容器

这里我们可以简单看一下代码它是怎么被塞进去的,Spring会通过ContextLoaderListener调用contextInitialized()进行初始化:

//ContextLoaderListener
/**
 * Initialize the root web application context.
 */
@Override
public void contextInitialized(ServletContextEvent event) {
   initWebApplicationContext(event.getServletContext());
}
//ContextLoader
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
  。。。。。省略
   try {
      // Store context in local instance variable, to guarantee that
      // it is available on ServletContext shutdown.
      if (this.context == null) {
      //创建WebApplicationContext
         this.context = createWebApplicationContext(servletContext);
      }
      if (this.context instanceof ConfigurableWebApplicationContext) {
         ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
         if (!cwac.isActive()) {
            // The context has not yet been refreshed -> provide services such as
            // setting the parent context, setting the application context id, etc
            if (cwac.getParent() == null) {
               // The context instance was injected without an explicit parent ->
               // determine parent for root web application context, if any.
               ApplicationContext parent = loadParentContext(servletContext);
               cwac.setParent(parent);
            }
            configureAndRefreshWebApplicationContext(cwac, servletContext);
         }
      }
//这里进行了set操作      servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

     。。。。。省略
   }
}

现在回到FrameworkServlet的初始化方法中来,我们从源码上可以看出WebApplicationContext的设置有三种方法:

第一种,通过构造方法已经传递了WebApplicationContext参数,这种情况我们只需要再做一些设置即可。这种方式得多亏了servlet 3.0新增的ServletContext#addServlet()方法,我们可以直接调用这个方法创建一个DispatcherServlet实例,这意味着我们可以通过构造器注入已经准备好的WebApplicationContextDispatcherServlet,即完成Spring MVC容器的注入,再将root WebApplicationContext设置为parent;

第二种,WebApplicationContext已经在ServletContext中,这里会通过当前servlet配置的contextAttribute属性查找一个自定义的WebApplicationContext,将其作为当前servlet的容器,例如,在ServletContext中有一个haha的WebApplicationContext,可以将它配置到Spring MVC中:

<servlet>
        <servlet-name>SpringMVC</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextAttribute</param-name>
            <param-value>haha</param-value>
        </init-param>
    
        <load-on-startup>1</load-on-startup>
        <async-supported>true</async-supported>
    </servlet>

第三种,通过xml配置一般都是走这种方式,直接创建一个WebApplicationContext

//FrameworkServlet
protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) {
   return createWebApplicationContext((ApplicationContext) parent);
}
//FrameworkServlet
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
//获取创建类型
   Class<?> contextClass = getContextClass();
   //检查创建类型
   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");
   }
   //具体创建
   ConfigurableWebApplicationContext wac =
         (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

   wac.setEnvironment(getEnvironment());
   //将root WebApplicationContext设置为parent
   wac.setParent(parent);
   // 获取当前servlet配置的contextConfigLocation,在web.xml中配置的
   String configLocation = getContextConfigLocation();
   if (configLocation != null) {
      wac.setConfigLocation(configLocation);
   }
   // 读取当前WebApplicationContext配置的Spring相关的bean,并进行初始化
   configureAndRefreshWebApplicationContext(wac);

   return wac;
}
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
   if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
      // The application context id is still set to its original default value
      // -> assign a more useful id based on available information
      if (this.contextId != null) {
         wac.setId(this.contextId);
      }
      else {
         // Generate default id...
         wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
               ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
      }
   }

   wac.setServletContext(getServletContext());
   wac.setServletConfig(getServletConfig());
   wac.setNamespace(getNamespace());
   wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

   // The wac environment's #initPropertySources will be called in any case when the context
   // is refreshed; do it eagerly here to ensure servlet property sources are in place for
   // use in any post-processing or initialization that occurs below prior to #refresh
   ConfigurableEnvironment env = wac.getEnvironment();
   if (env instanceof ConfigurableWebEnvironment) {
      ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
   }

   postProcessWebApplicationContext(wac);
   applyInitializers(wac);
   wac.refresh();
}
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
   if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
      // The application context id is still set to its original default value
      // -> assign a more useful id based on available information
      if (this.contextId != null) {
         wac.setId(this.contextId);
      }
      else {
         // Generate default id...
         wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
               ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
      }
   }

   wac.setServletContext(getServletContext());
   wac.setServletConfig(getServletConfig());
   wac.setNamespace(getNamespace());
   wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

//提前将 ServletContext 和 ServletConfig 提前注入到 Environment 变量中
   // The wac environment's #initPropertySources will be called in any case when the context
   // is refreshed; do it eagerly here to ensure servlet property sources are in place for
   // use in any post-processing or initialization that occurs below prior to #refresh
   ConfigurableEnvironment env = wac.getEnvironment();
   if (env instanceof ConfigurableWebEnvironment) {
      ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
   }
    // 后置处理,子类可以覆盖进行一些自定义操作。在 Spring MVC 未使用到,是个空方法。
   postProcessWebApplicationContext(wac);
   applyInitializers(wac);
   //接口方法,实现类重写,下文再叙
   wac.refresh();
}

wac设置了监听器,SourceFilteringListener可以根据入参来选择,所以实际监听的是ContextRefreshListener所监听的事件,当接收到消息时调用FrameworkServlet.this.onApplicationEvent(event)方法,将refreshEventReceived设置为true,表示已经refresh过。

public void onApplicationEvent(ContextRefreshedEvent event) {
        this.refreshEventReceived = true;
        synchronized (this.onRefreshMonitor) {
            onRefresh(event.getApplicationContext());
        }
    }

FrameworkServlet#initWebApplicationContext方法的末尾我们可以看到会根据refreshEventReceived来判断是否需要refresh,当使用第一种第三种,都调用了FrameworkServlet#configureAndRefreshWebApplicationContext()方法,所以已经fresh过,只有第二种才需要在这里调用onRefresh方法,不过通过哪种方式,onRefresh只会被调用一次,DispatcherServlet就是通过重写这个模板方法实现初始化。

if (!this.refreshEventReceived) {
   // Either the context is not a ConfigurableApplicationContext with refresh
   // support or the context injected at construction time had already been
   // refreshed -> trigger initial onRefresh manually here.
   synchronized (this.onRefreshMonitor) {
      onRefresh(wac);
   }
}

DispatcherServlet

onRefresh()DispatcherServlet的入口方法,initStrategies()用于初始化9个组件

/**
 * This implementation calls {@link #initStrategies}.
 */
@Override
protected void onRefresh(ApplicationContext context) {
   initStrategies(context);
}

/**
 * Initialize the strategy objects that this servlet uses.
 * <p>May be overridden in subclasses in order to initialize further strategy objects.
 */
protected void initStrategies(ApplicationContext context) {
    //初始化文件上传处理类
   initMultipartResolver(context);
       //初始化本地化Resolver
   initLocaleResolver(context);
       //初始化主题Resolver
   initThemeResolver(context);
       //初始化请求映射关系。
   initHandlerMappings(context);
   //根据Handler的类型定义不同的处理规则。
   initHandlerAdapters(context);
    //初始化异常处理的handler
   initHandlerExceptionResolvers(context);
   //将指定的ViewName按照定义的RequestToViewNameTranslators替换成想要的格式
   initRequestToViewNameTranslator(context);
   //用于将View解析成页面
   initViewResolvers(context);
   //生成FlashMap管理器
   initFlashMapManager(context);
}

我们以initLocaleResolver(context);为例,可以发现,先从容器中取名为localeResolver或者LocaleResolver.class类型的bean,如果没有则走默认策略。

private void initLocaleResolver(ApplicationContext context) {
   try {
      this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
      if (logger.isTraceEnabled()) {
         logger.trace("Detected " + this.localeResolver);
      }
      else if (logger.isDebugEnabled()) {
         logger.debug("Detected " + this.localeResolver.getClass().getSimpleName());
      }
   }
   catch (NoSuchBeanDefinitionException ex) {
      // We need to use the default.
      this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
      if (logger.isTraceEnabled()) {
         logger.trace("No LocaleResolver '" + LOCALE_RESOLVER_BEAN_NAME +
               "': using default [" + this.localeResolver.getClass().getSimpleName() + "]");
      }
   }
}

getDefaultStrategy方法调用了getDefaultStrategies()

@SuppressWarnings("unchecked")
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
   String key = strategyInterface.getName();
   String value = defaultStrategies.getProperty(key);
   if (value != null) {
      String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
      List<T> strategies = new ArrayList<>(classNames.length);
      for (String className : classNames) {
         try {
            Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
            Object strategy = createDefaultStrategy(context, clazz);
            strategies.add((T) strategy);
         }
         catch (ClassNotFoundException ex) {
            throw new BeanInitializationException(
                  "Could not find DispatcherServlet's default strategy class [" + className +
                  "] for interface [" + key + "]", ex);
         }
         catch (LinkageError err) {
            throw new BeanInitializationException(
                  "Unresolvable class definition for DispatcherServlet's default strategy class [" +
                  className + "] for interface [" + key + "]", err);
         }
      }
      return strategies;
   }
   else {
      return new LinkedList<>();
   }
}

真正执行创建的方法是ClassUtils.forName(),通过代码我们可以发现是通过defaultStrategies这个配置文件取得相关的类名进行创建bean,这个变量可以追溯到静态块中初始化的内容,我们可以发现原来是取跟它同包下的一个DispatcherServlet.properties文件

private static final Properties defaultStrategies;

static {
   // Load default strategy implementations from properties file.
   // This is currently strictly internal and not meant to be customized
   // by application developers.
   try {
      ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
      defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
   }
   catch (IOException ex) {
      throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
   }
}

DispatcherServlet.properties文件:

# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
   org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
   org.springframework.web.servlet.function.support.RouterFunctionMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
   org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
   org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
   org.springframework.web.servlet.function.support.HandlerFunctionAdapter


org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
   org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
   org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

我们可以看到除了multipartResolver其他都有相应的默认值,因为不是所有的项目都需要上传,或者不一定使用multipart的方式,也可能用appache的包进行上传。

至此DispatcherServlet的初始化就完成了。

小结

  1. Spring MVC中Servlet分为三个层次,HttpServletBean,FrameworkServlet,DispatcherServletHttpServletBean继承自Java的HttpServlet,作用是将Servlet中的配置设置到相应的属性中;FrameworkServlet初始化了Spring MVC的容器,即WebApplicationContextDispatcherServlet初始化了自身的9大组件。
  2. Spring容器也称IOC容器称为Root WebApplicationContext,是Spring MVCWebApplicationContext的父容器,Root WebApplicationContextServletContext的一个属性,通过ContextLoaderListener初始化并设置。

DispatcherServlet-Init初始化
DispatcherServlet-Init初始化

参照

《看透springMvc源代码分析与实践》