Spring5源码14-SpringMVC入口及启动流程

726 阅读14分钟

欢迎大家关注 github.com/hsfxuebao ,希望对大家有所帮助,要是觉得可以的话麻烦给点一下Star哈

1. 前言

Spring 的 Mvc 基于 Servlet 功能实现的,通过实现Servlet 接口的 DispatcherServlet 来封装其核心功能实现,通过将请求分派给处理程序,同时带有可配置的处理程序映射、视图解析、本地语言、主题解析以及上载文件的支持。默认的处理程序是非常简单的Controller 接口

@FunctionalInterface
public interface Controller {
    @Nullable
    ModelAndView handleRequest(HttpServletRequest var1, HttpServletResponse var2) throws Exception;
}

对Spring mvc 或者其他成熟的MVC 框架而言,解决的问题无外乎以下几点:

  • 将web 页面的请求传给服务器
  • 根据不同得到请求处理不同的逻辑单元
  • 返回处理结果数据并跳转至相应页面

1.1 各个上下文的区别

在开始前,我们首先需要了解 ServletContext、ServletConfig、BeanFactory、ApplicationContext、WebApplicationContext 的区别:

  • ServletContext :应该说是Serlvet层面的上下文。包含了WebApplicationContext。Servlet规范中的概念,本质上并不是Spring的概念。他是servlet用来和容器间进行交互的接口的组合。也就是说,这个接口定 义了一系列的方法, servlet通过这些方法可以很方便地与自己所在的容器进行一些交互,比如通过getMajorVersion与getMinorVersion来获取容器的版本信息等.从它的定 义中也可以看出,在一个应用中(一个JVM)只有一个ServletContext,换句话说,容器中所有的servlet都共享同一个ServletContext.

  • ServletConfig :它与ServletContext的区别在于,servletConfig是针对servlet而言的,每个servlet都有它独有的serveltConfig信息,相互之间不共享.

  • BeanFactory :Spring 中最基础的容器, 提供了最简单的 IOC 功能。

  • ApplicationContext :这个类是Spring实现容器功能的核心接口,它也是Spring实现IoC功能中最重要的接口,从它的名字中可以看出,它维护了整个程序运行期间所需要的上下文信息, 注意这里的应用程序并不一定是web程序,也可能是其它类型的应用. 在Spring中允许存在多个applicationContext,这些context相互之间还形成了父与子,继承与被继承的关系,这也是通常我们所说的,在spring中存在两个context,一个是root context,一个是servlet applicationContext的意思。这点后面会进一步阐述.

  • WebApplicationContext : 其实这个接口不过是applicationContext接口的一个子接口罢了,只不过说它的应用形式是web罢了。它在ApplicationContext的基础上,添加了对ServletContext的引用,即getServletContext方法.

他们四者的关系如下:

  • ServletContext针对Servlet来说,是Servlet的全局上下文。目前看到应该是作用于最大的,可以在前后端传递数据
  • ServletConfig 针对的每个Servlet ,是每个Servlet的配置内容,即init-param标签内容
  • BeanFactory提供了最基础的SpringIOC功能.
  • ApplicationContextBeanFactory的基础上增加了更多的功能。这里实际上是ApplicationContext中有BeanFactory变量而并非ApplicationContext继承了BeanFactory。 关于IOC的–些功能,ApplicationContext实际.上还是委托给了BeanFactory完成,即调用BeanFactory的方法。

1.2 Servlet 的生命周期

Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。

Servlet 是一个java 编写的程序,此程序基于http 协议,在服务端运行达到是按照servlet 规范编写的一个类。主要处理客户端的请求并将结果发送到客户端。Servlet 的生命周期是由Servlet 容器来控制的,可以分为三个阶段。

1.2.1 初始化阶段

  • servlet容器加载 servlet类, 把 servlet 类的 .class 文件中的数据读取到内存中
  • servlet 容器创建了一个ServletConfig对象。ServletConfig 对象包含了 servlet 的初始化配置信息
  • servlet 容器创建了一个servlet 对象
  • servlet 容器调用 servlet 对象的 init 方法进行初始化。

1.2.2 运行阶段

当一个 servlet 容器收到一个请求后,servlet容器会针对这个请求创建一个 servletRequestservletResponse 对象,然后调用service 方法。并将这两个参数传递给 service 方法。service 方法通过 servletRequest 对象获取请求的信息,并处理该请求再通过 servletResponse 对象生成这个请求的相应结果。然后销毁servletRequest 和 servletResponse 对象。

1.2.3 销毁阶段

web应用被终止时,servlet 容器会先调用servlet对象的destory 方法,然后再销毁servlet 对象,同时也会销毁与 servlet 对象想关联的servletConfig 对象。我们可以在destory 方法的实现中,释放servlet 所占用的资源,如关闭数据库连接等。

2. 环境搭建

2.1 新建mvc工程

不在阐述

2.2 代码

仿照官网案例,使用注解版:

/**
 * 只要写了这个,相当于配置了SpringMVC的DispatcherServlet
 * 1、Tomcat一启动就加载他
 *        1)、创建了容器、制定了配置类(所有ioc、aop等spring的功能就ok)
 *        2)、注册一个Servlet;    DispatcherServlet;
 *        3)、以后所有的请求都交给了 DispatcherServlet;
 *     效果,访问Tomcat部署的这个Web应用下的所有请求都会被     DispatcherServlet 处理
 *     DispatcherServlet就会进入强大的基于注解的mvc处理流程(@GetMapping)
 * 必须Servlet3.0以上才可以;Tomcat6.0以上才支持Servlet3.0规范
 *
 * Servlet3.0是javaEE的Web的规范标准,Tomcat是Servlet3.0规范的一个实现;
 */
public class AppStarter implements WebApplicationInitializer {

   @Override
   public void onStartup(javax.servlet.ServletContext servletContext) throws ServletException {
      //1、创建ioc容器
      AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
      //2、传入一个配置类
      context.register(SpringConfig.class);
      //以上截止,ioc容器都没有启动
      //3、配置了 DispatcherServlet,利用Servlet的初始化机制
      DispatcherServlet servlet = new DispatcherServlet(context);
      ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
      registration.setLoadOnStartup(1);
      registration.addMapping("/"); //映射路径

      //启动了容器?上面的Servlet添加到 servletContext 里面以后,Tomcat就会对 DispatcherServlet进行初始化
      //<servlet></servlet>
      //    servletContext.addServlet("abc",XXXX.class)
   }
}

创建配置类SpringConfig:

/**
 * Spring不扫描controller组件、
 * AOP咋实现的,事务 看Spring容器
 */
@Configuration
@ComponentScan(value = "com.hsf.web")
public class SpringConfig {
   // Spring父容器
}

2.3 配置tomcat启动

image.png 启动tomcat,并访问 http://localhost:8080/springmvc_source/ 即可。

2.4 启动流程

2.4.1 Tomcat通过SPI机制扫描

org.apache.catalina.startup.ContextConfig#webConfig

...
if (ok) {
    // 5.查找ServletContainerInitializer实现,并创建实例,查找范围分为两部分。
    // 6.初始化typeInitializerMap和initializerClassMap两个映射(主要用于后续的注解检测)
    processServletContainerInitializers();
}
...

接下来:

protected void processServletContainerInitializers() {
    /**
    * 5. 查找META-INF/services/javax.servlet.ServletContainerInitializer配置文件,
    * 获取ServletContainerInitializer,这里的ServletContainerInitializer可以使用HandlesTypes注解指定需要处理的类型
    *  - Web应用下的包:如果javax.servlet.context..orderedLibs不为空,仅搜索该属性包含的包,否则搜索WEB-INF/Iib下所有包。
    *  - 容器包:搜索所有包。
     */
    List<ServletContainerInitializer> detectedScis;
    try {
        WebappServiceLoader<ServletContainerInitializer> loader = new WebappServiceLoader<>(context);
        detectedScis = loader.load(ServletContainerInitializer.class);
    } catch (IOException e) {
        log.error(sm.getString(
                "contextConfig.servletContainerInitializerFail",
                context.getName()),
            e);
        ok = false;
        return;
    }
    ...
}

扫描META-INF/services/javax.servlet.ServletContainerInitializer并加载对应的类,在Spring-web工程下正好有: image.png

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
   @Override
   public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
         throws ServletException {

      List<WebApplicationInitializer> initializers = Collections.emptyList();

      if (webAppInitializerClasses != null) {
         initializers = new ArrayList<>(webAppInitializerClasses.size());
         for (Class<?> waiClass : webAppInitializerClasses) {
            // Be defensive: Some servlet containers provide us with invalid classes,
            // no matter what @HandlesTypes says...
            // 所有非接口 非抽象类WebApplicationInitializer实现类
            if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
                  WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
               try {
                  initializers.add((WebApplicationInitializer)
                        ReflectionUtils.accessibleConstructor(waiClass).newInstance());
               }
               catch (Throwable ex) {
                  throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
               }
            }
         }
      }

      if (initializers.isEmpty()) {
         servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
         return;
      }

      servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
      AnnotationAwareOrderComparator.sort(initializers);
      // todo 遍历所有符合条件的WebApplicationInitializer  回调onStartup方法
      for (WebApplicationInitializer initializer : initializers) {
         initializer.onStartup(servletContext);
      }
   }
}

SpringServletContainerInitializer只处理WebApplicationInitializer,通过代码可以知道会 遍历WebApplicationInitializer并执行onStartup方法。

在这里,有一个问题,谁在什么时候调用了ServletContainerInitializer.onStartup 方法呢?具体可看Tomcat源码系列

2.4.2 Tomcat的初始化方法

DispatcherServlet的继承图: image.png

调用Servlet的init方法,会直接调到FrameworkServlet#initServletBean方法:

protected final void initServletBean() throws ServletException {
   ...
   long startTime = System.currentTimeMillis();

   try {
      // todo
      this.webApplicationContext = initWebApplicationContext();
      // 留给子类覆盖操作
      initFrameworkServlet();
   }
    ...
}

protected WebApplicationContext initWebApplicationContext() {
   // 先会 获取之前的WebApplicationContext(构建父子容器)
   WebApplicationContext rootContext =
         WebApplicationContextUtils.getWebApplicationContext(getServletContext());
   WebApplicationContext wac = null;

   // 1. 如果 webApplicationContext 在构造函数的时候被注入,则 wac != null, 则可以直接使用
   if (this.webApplicationContext != null) {
      // A context instance was injected at construction time -> use it
      // 当前 web-ios 容器
      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
               // todo 父子容器的体现
               cwac.setParent(rootContext);
            }
            // todo 2. 刷新上下文环境 配置并且刷新容器
            configureAndRefreshWebApplicationContext(cwac);
         }
      }
   }
  ...

   return wac;
}

configureAndRefreshWebApplicationContext(cwac);中会刷新Spring容器。

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
   ...
   // todo 刷新AbstractApplicationContext#refresh
   wac.refresh();
}

在这里会启动Spring容器的刷新操作。

2.5 流程框图

image.png

3. 父子容器(注解版)

3.1 父子容器

官网的框图: image.png

以前xml配置版:

  1. web.xml中配置ContextLoaderListener,指定Spring配置文件的位置
  2. web.xml中配置DispatcherServlet,指定SpringMVC配置文件位置
  3. 以上会产生父子容器

父容器(Spring配置文件进行包扫描并保存所有组件的容器),子容器(SpringMVC配置文件进行包扫描并保存所有组件的容器),子容器通过webloc.setParent(springloc))可以调用父容器,双亲委派,容器隔离

3.2 WebApplicationInitializer继承图

image.png

继承AbstractAnnotationConfigDispatcherServletInitializer可以更快的整合SpringMVC和Spring容器。代码如下:

/**
 * 最快速的整合注解版SpringMVC和Spring的
 */
public class QuickAppStarter extends AbstractAnnotationConfigDispatcherServletInitializer {
   @Override //根容器的配置(Spring的配置文件===Spring的配置类)
   protected Class<?>[] getRootConfigClasses() {
      return new Class<?>[]{SpringConfig.class};
   }

   @Override //web容器的配置(SpringMVC的配置文件===SpringMVC的配置类)
   protected Class<?>[] getServletConfigClasses() {
      return new Class<?>[]{SpringMVCConfig.class};
   }

   @Override //Servlet的映射,DispatcherServlet的映射路径
   protected String[] getServletMappings() {
      return new String[]{"/"};
   }

   @Override
   protected void customizeRegistration(ServletRegistration.Dynamic registration) {
//    super.customizeRegistration(registration);

//    registration.addMapping("");//
   }
}

SpringConfigSpringMVCConfig的配置如下:

/**
 * Spring不扫描controller组件、
 * AOP咋实现的,事务 看Spring容器
 */
@Configuration
@ComponentScan(value = "com.hsf.web",excludeFilters = {
      @ComponentScan.Filter(type= FilterType.ANNOTATION,value = Controller.class)
})
public class SpringConfig {
   // Spring父容器
}

/**
 * SpringMVC只扫描controller组件,可以不指定父容器类,让MVC扫所有。@Component+@RequestMapping就生效了
 */
@ComponentScan(value = "com.hsf.web",includeFilters = {
      @ComponentScan.Filter(type= FilterType.ANNOTATION,value = Controller.class)
},useDefaultFilters = false)
@Configuration
public class SpringMVCConfig {

   // SpringMVC 子容器,能扫描的Spring容器中的组件
}

我们在QuickAppStarter类中的三个方法分别打上 断点,Spring容器和SpringMVC的刷新流程是在什么时机触发的?

3.3 父子容器启动流程分析

3.3.1 WebApplicationInitializer.onStartup方法

分析和第2小节一样,我们直接来到QuickAppStarter.onStartup方法,需要看其父类的 AbstractDispatcherServletInitializer.onStartup方法:

@Override
public void onStartup(ServletContext servletContext) throws ServletException {
    // todo 先执行父类的onStartup方法
   super.onStartup(servletContext);
   // todo 注册 DispatcherServlet
   registerDispatcherServlet(servletContext);
}

3.3.1.1 先执行父类AbstractContextLoaderInitializer.onStartup(servletContext);

@Override
public void onStartup(ServletContext servletContext) throws ServletException {
   // todo 注册ContextLoadListener.contextInitialized
   registerContextLoaderListener(servletContext);
}

protected void registerContextLoaderListener(ServletContext servletContext) {
   // todo 创建 根容器
   WebApplicationContext rootAppContext = createRootApplicationContext();
   if (rootAppContext != null) {
      ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
      listener.setContextInitializers(getRootApplicationContextInitializers());
      servletContext.addListener(listener);
   }
   else {
      logger.debug("No ContextLoaderListener registered, as " +
            "createRootApplicationContext() did not return an application context");
   }
}

我们来AbstractAnnotationConfigDispatcherServletInitializer#createRootApplicationContext方法:

// 重写了爷爷类的根 容器方法
@Override
@Nullable
protected WebApplicationContext createRootApplicationContext() {
   // todo 获取根 配置
   Class<?>[] configClasses = getRootConfigClasses();
   if (!ObjectUtils.isEmpty(configClasses)) {
      // 创建IOC容器,并把配置类注册进来
      AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
      context.register(configClasses);
      return context;
   }
   else {
      return null;
   }
}

getRootConfigClasses();其实就是从子类(我们写的QuickStarter.getRootConfigClasses() )获取根 容器的配置类。

3.3.1.2 在执行registerDispatcherServlet

AbstractDispatcherServletInitializer#registerDispatcherServlet:

protected void registerDispatcherServlet(ServletContext servletContext) {
   String servletName = getServletName();
   Assert.hasLength(servletName, "getServletName() must not return null or empty");

   // 创建Servlet 容器
   WebApplicationContext servletAppContext = createServletApplicationContext();
   Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");

   FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
   Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
   dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());

   ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
   if (registration == null) {
      throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
            "Check if there is another servlet registered under the same name.");
   }

   registration.setLoadOnStartup(1);
   registration.addMapping(getServletMappings());
   registration.setAsyncSupported(isAsyncSupported());

   Filter[] filters = getServletFilters();
   if (!ObjectUtils.isEmpty(filters)) {
      for (Filter filter : filters) {
         registerServletFilter(servletContext, filter);
      }
   }

   customizeRegistration(registration);
}

核心方法AbstractAnnotationConfigDispatcherServletInitializer#createServletApplicationContext:

protected WebApplicationContext createServletApplicationContext() {
   AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
   // 获取web的配置文件
   Class<?>[] configClasses = getServletConfigClasses();
   if (!ObjectUtils.isEmpty(configClasses)) {
      context.register(configClasses);
   }
   return context;
}

getServletConfigClasses()其实就是调用子类(我们写的QuickStarter.getServletConfigClasses() )的配置类。

3.3.2 何时刷新容器呢?

ContextLoaderListener何时被调用?

Servlet应用监听器回调:在当前web应用启动以后(Tomcat把Web应用加载以后),调用contextInitialized方法,tomcat源码对应为: org.apache.catalina.core.StandardContext#startInternal:

protected synchronized void startInternal() throws LifecycleException {
        ...

        // Configure and call application event listeners
        // 21.实例化应用监听器(ApplicationListener),分为事件监听器(ServletContextAttribute-
        //Listener ServletRequestAttributeListener ServletRequestListener,HttpSessionldListener,HttpSession-
        //AttributeListener)以及生命周期监听器(HttpSessionListener、ServletContextListener)。这些监听
        //器可以通过Contexti部署描述文件、可编程的方式(ServletContainerInitializer)或者Web.xml添加,
        //并且触发ServletContextListener..contextInitialized。
        if (ok) {
            if (!listenerStart()) {
                log.error(sm.getString("standardContext.listenerFail"));
                ok = false;
            }
        }
    ...
}

public boolean listenerStart() {

    ...
    Object instances[] = getApplicationLifecycleListeners();
    if (instances == null || instances.length == 0) {
        return ok;
    }

    ServletContextEvent event = new ServletContextEvent(getServletContext());
    ServletContextEvent tldEvent = null;
    if (noPluggabilityListeners.size() > 0) {
        noPluggabilityServletContext = new NoPluggabilityServletContext(getServletContext());
        tldEvent = new ServletContextEvent(noPluggabilityServletContext);
    }
    for (Object instance : instances) {
        if (!(instance instanceof ServletContextListener)) {
            continue;
        }
        ServletContextListener listener = (ServletContextListener) instance;
        try {
            fireContainerEvent("beforeContextInitialized", listener);
            if (noPluggabilityListeners.contains(listener)) {
                listener.contextInitialized(tldEvent);
            } else {
                listener.contextInitialized(event);
            }
            fireContainerEvent("afterContextInitialized", listener);
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            fireContainerEvent("afterContextInitialized", listener);
            getLogger().error(sm.getString("standardContext.listenerStart",
                    instance.getClass().getName()), t);
            ok = false;
        }
    }
    return ok;

}

获取所有的生命周期监听器getApplicationLifecycleListeners():

@Override
public Object[] getApplicationLifecycleListeners() {
    return applicationLifecycleListenersObjects;
}

我们在第3.3.1小节中registerContextLoaderListener:

protected void registerContextLoaderListener(ServletContext servletContext) {
   // todo 创建 根容器
   WebApplicationContext rootAppContext = createRootApplicationContext();
   if (rootAppContext != null) {
      ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
      listener.setContextInitializers(getRootApplicationContextInitializers());
      servletContext.addListener(listener);
   }
   ...
}

其中,servletContext.addListener(listener);就是加入到ApplicationContext的 applicationLifecycleListenersObjects属性中。

下面,我们分析下ContextLoaderListener.contextInitialized() 都做了什么? org.springframework.web.context.ContextLoaderListener#contextInitialized:

@Override
public void contextInitialized(ServletContextEvent event) {
   // 初始化web-ios容器
   initWebApplicationContext(event.getServletContext());
}

核心方法ContextLoader#initWebApplicationContext:

// 初始化 webApplicationContext
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
   // 初始化完成的 webApplicationContext 会被保存到  servletContext 的属性中,key 为 ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
   // 所以这里是判断是否已经初始化了webApplicationContext,就抛出异常(web.xml 中声明了多次ContextLoader 的定义),不可重复初始化。
   if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
      throw new IllegalStateException(
            "Cannot initialize context because there is already a root application context present - " +
            "check whether you have multiple ContextLoader* definitions in your web.xml!");
   }

   servletContext.log("Initializing Spring root WebApplicationContext");
   Log logger = LogFactory.getLog(ContextLoader.class);
   if (logger.isInfoEnabled()) {
      logger.info("Root WebApplicationContext: initialization started");
   }
   long startTime = System.currentTimeMillis();

   try {
      // Store context in local instance variable, to guarantee that
      // it is available on ServletContext shutdown.
      if (this.context == null) {
         // todo 创建 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);
            }
            // todo 刷新上下文环境
            configureAndRefreshWebApplicationContext(cwac, servletContext);
         }
      }
      //  将创建好的 WebApplicationContext 保存到 servletContext 中
      servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
      // 映射当前的类加载器 与 创建的实例到全局变量 currentContextPerThread 中。
      ClassLoader ccl = Thread.currentThread().getContextClassLoader();
      if (ccl == ContextLoader.class.getClassLoader()) {
         currentContext = this.context;
      }
      else if (ccl != null) {
         currentContextPerThread.put(ccl, this.context);
      }

      if (logger.isInfoEnabled()) {
         long elapsedTime = System.currentTimeMillis() - startTime;
         logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
      }

      return this.context;
   }
   catch (RuntimeException | Error ex) {
      logger.error("Context initialization failed", ex);
      servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
      throw ex;
   }
}

因为 我们在之前 已经创建了根容器,所以就直接进入configureAndRefreshWebApplicationContext(cwac, servletContext);方法:

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
   ...
   // todo  刷新容器
   wac.refresh();
}

在这里,直接 根 容器的刷新操作。

DispatcherServlet 何时初始化?

Servlet初始化回调:调用链路:Servlet.init() -> GenericServlet.init()->HttpServletBean.init():

public final void init() throws ServletException {

   // Set bean properties from init parameters.
   // 1. 封装及验证初始化参数
   PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
   if (!pvs.isEmpty()) {
      try {
         // 2. 将当前 Servlet 实例转化为 BeanWrapper 实例
         BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
         // 3. 注册于相对于 Resource 的属性编辑器
         ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
         bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
         // 4. 属性注入
         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;
      }
   }

   // Let subclasses do whatever initialization they like.
   // todo  5.servletBean的初始化 留给子类的模板方法
   initServletBean();
}

核心方法initServletBean(),留给子类实现,我们直接看FrameworkServlet#initServletBean

@Override
protected final void initServletBean() throws ServletException {
   ...
   long startTime = System.currentTimeMillis();

   try {
      // todo
      this.webApplicationContext = initWebApplicationContext();
      // 留给子类覆盖操作
      initFrameworkServlet();
   }
   ...
}

核心方法initWebApplicationContext():

protected WebApplicationContext initWebApplicationContext() {
   // 先会 获取之前的WebApplicationContext(构建父子容器)
   WebApplicationContext rootContext =
         WebApplicationContextUtils.getWebApplicationContext(getServletContext());
   WebApplicationContext wac = null;

   // 1. 如果 webApplicationContext 在构造函数的时候被注入,则 wac != null, 则可以直接使用
   if (this.webApplicationContext != null) {
      // A context instance was injected at construction time -> use it
      // 当前 web-ios 容器
      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
               // todo 父子容器的体现
               cwac.setParent(rootContext);
            }
            // todo 2. 刷新上下文环境 配置并且刷新容器
            configureAndRefreshWebApplicationContext(cwac);
         }
      }
   }
   // 3. 如果构造函数并没有注入,则wac为null,根据 contextAttribute 属性加载 WebApplicationContext
   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
      // 根据 contextAttribute 属性加载 WebApplicationContext
      wac = findWebApplicationContext();
   }
   // 4. 如果上面两个方式都没有加载到 WebApplicationContext,则尝试自己加载
   if (wac == null) {
      // No context instance is defined for this servlet -> create a local one
      // todo 自己尝试创建WebApplicationContext
      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) {
         // todo 5.刷新
         onRefresh(wac);
      }
   }

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

   return wac;
}

我们关注下面的核心点:

  • 构建父子容器cwac.setParent(rootContext);
  • 刷新子容器configureAndRefreshWebApplicationContext(cwac);
    protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
        ...
        // todo 刷新AbstractApplicationContext#refresh
        wac.refresh();
    }
    
    在这里会 调用子容器的刷新方法。子容器启动,并且构建了父子容器。

3.4 SpringMVC父子容器启动流程图

image.png

参考文章

Spring5源码注释github地址
Spring源码深度解析(第2版)
spring源码解析
Spring源码深度解析笔记
Spring注解与源码分析
Spring注解驱动开发B站教程