SpringBoot 源码解析 (六)----- Spring Boot的核心能力 - 内置Servlet容器源码分析(Tomcat)

目录

 

正文

Spring Boot 默认使用 Tomcat 作为嵌入式的 Servlet 容器,只要引入了 spring-boot-start-web 依赖,则默认是用 Tomcat 作为 Servlet 容器:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
回到顶部

Servlet 容器的使用

默认 servlet 容器

我们看看 spring-boot-starter-web 这个 starter 中有什么

核心就是引入了 tomcat 和 SpringMvc,我们先来看 tomcat

Spring Boot 默认支持 Tomcat,Jetty,和 Undertow 作为底层容器。如图:

而 Spring Boot 默认使用 Tomcat,一旦引入 spring-boot-starter-web 模块,就默认使用 Tomcat 容器。

切换 servlet 容器

那如果我么想切换其他 Servlet 容器呢,只需如下两步:

  • 将 tomcat 依赖移除掉
  • 引入其他 Servlet 容器依赖

引入 jetty:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <!--移除 spring-boot-starter-web 中的 tomcat-->
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <groupId>org.springframework.boot</groupId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<!--引入 jetty-->
<artifactId>spring-boot-starter-jetty</artifactId>

</dependency>

回到顶部

Servlet 容器自动配置原理

EmbeddedServletContainerAutoConfiguration

其中EmbeddedServletContainerAutoConfiguration是嵌入式 Servlet 容器的自动配置类,该类在spring-boot-autoconfigure.jar 中的 web 模块可以找到。

我们可以看到EmbeddedServletContainerAutoConfiguration 被配置在 spring.factories 中,看过我前面文章的朋友应该知道 SpringBoot 自动配置的原理,这里将 EmbeddedServletContainerAutoConfiguration 配置类加入到 IOC 容器中,接着我们来具体看看这个配置类:

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication// 在 Web 环境下才会起作用
@Import(BeanPostProcessorsRegistrar.class)// 会 Import 一个内部类 BeanPostProcessorsRegistrar
public class EmbeddedServletContainerAutoConfiguration {
@Configuration
</span><strong><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Tomcat类和Servlet类必须在classloader中存在
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 文章开头我们已经导入了web的starter,其中包含tomcat和SpringMvc
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 那么classPath下会存在Tomcat.class和Servlet.class</span>
@ConditionalOnClass({ Servlet.<span style="color: rgba(0, 0, 255, 1)">class</span>, Tomcat.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> })
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 当前Spring容器中不存在EmbeddedServletContainerFactory类型的实例</span>
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.<span style="color: rgba(0, 0, 255, 1)">class</span>, search =<span style="color: rgba(0, 0, 0, 1)"> SearchStrategy.CURRENT)
</span></strong><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> EmbeddedTomcat {

    @Bean
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
        </span><strong><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 上述条件注解成立的话就会构造TomcatEmbeddedServletContainerFactory这个EmbeddedServletContainerFactory</span>
        <span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">new</span></strong><span style="color: rgba(0, 0, 0, 1)"><strong> TomcatEmbeddedServletContainerFactory();</strong>
    }
}

@Configuration
@ConditionalOnClass({ Servlet.</span><span style="color: rgba(0, 0, 255, 1)">class</span>, Server.<span style="color: rgba(0, 0, 255, 1)">class</span>, Loader.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">,
        WebAppContext.</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> })
@ConditionalOnMissingBean(value </span>= EmbeddedServletContainerFactory.<span style="color: rgba(0, 0, 255, 1)">class</span>, search =<span style="color: rgba(0, 0, 0, 1)"> SearchStrategy.CURRENT)
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> EmbeddedJetty {

    @Bean
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() {
        </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> JettyEmbeddedServletContainerFactory();
    }

}

@Configuration
@ConditionalOnClass({ Servlet.</span><span style="color: rgba(0, 0, 255, 1)">class</span>, Undertow.<span style="color: rgba(0, 0, 255, 1)">class</span>, SslClientAuthMode.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> })
@ConditionalOnMissingBean(value </span>= EmbeddedServletContainerFactory.<span style="color: rgba(0, 0, 255, 1)">class</span>, search =<span style="color: rgba(0, 0, 0, 1)"> SearchStrategy.CURRENT)
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> EmbeddedUndertow {

    @Bean
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() {
        </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> UndertowEmbeddedServletContainerFactory();
    }

}

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">other code...</span>

}

在这个自动配置类中配置了三个容器工厂的 Bean,分别是:

  • TomcatEmbeddedServletContainerFactory

  • JettyEmbeddedServletContainerFactory

  • UndertowEmbeddedServletContainerFactory

这里以大家熟悉的 Tomcat 为例,首先 Spring Boot 会判断当前环境中是否引入了 Servlet 和 Tomcat 依赖,并且当前容器中没有自定义的EmbeddedServletContainerFactory的情况下,则创建 Tomcat 容器工厂。其他 Servlet 容器工厂也是同样的道理。

EmbeddedServletContainerFactory

  • 嵌入式 Servlet 容器工厂
public interface EmbeddedServletContainerFactory {
EmbeddedServletContainer <strong>getEmbeddedServletContainer</strong>( ServletContextInitializer... initializers);

}

内部只有一个方法,用于获取嵌入式的 Servlet 容器。

该工厂接口主要有三个实现类,分别对应三种嵌入式 Servlet 容器的工厂类,如图所示:

TomcatEmbeddedServletContainerFactory

以 Tomcat 容器工厂 TomcatEmbeddedServletContainerFactory 类为例:

public class TomcatEmbeddedServletContainerFactory extends AbstractEmbeddedServletContainerFactory implements ResourceLoaderAware {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">other code...</span>
@Override public EmbeddedServletContainer getEmbeddedServletContainer(ServletContextInitializer... initializers) { //创建一个 Tomcat Tomcat tomcat = new Tomcat();
   </span></strong><strong><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">配置Tomcat的基本环节</span></strong>
    File baseDir = (<span style="color: rgba(0, 0, 255, 1)">this</span>.baseDirectory != <span style="color: rgba(0, 0, 255, 1)">null</span> ? <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.baseDirectory: createTempDir(</span>"tomcat"<span style="color: rgba(0, 0, 0, 1)">));
   <strong> tomcat.setBaseDir(baseDir.getAbsolutePath());</strong>
    Connector connector </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> Connector(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.protocol);
  <strong>  tomcat.getService().addConnector(connector);</strong>
    customizeConnector(connector);
 <strong>   tomcat.setConnector(connector);
    tomcat.getHost().setAutoDeploy(</strong></span><strong><span style="color: rgba(0, 0, 255, 1)">false</span></strong><span style="color: rgba(0, 0, 0, 1)"><strong>);</strong>
    configureEngine(tomcat.getEngine());
    </span><span style="color: rgba(0, 0, 255, 1)">for</span> (Connector additionalConnector : <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.additionalTomcatConnectors) {
        tomcat.getService().addConnector(additionalConnector);
    }
    prepareContext(tomcat.getHost(), initializers);
    
    </span><strong><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">包装tomcat对象,返回一个嵌入式Tomcat容器,内部会启动该tomcat容器</span>
    <span style="color: rgba(0, 0, 255, 1)">return</span></strong><span style="color: rgba(0, 0, 0, 1)"><strong> getTomcatEmbeddedServletContainer(tomcat);</strong>
}

}

首先会创建一个 Tomcat 的对象,并设置一些属性配置,最后调用getTomcatEmbeddedServletContainer(tomcat) 方法,内部会启动 tomcat,我们来看看:

protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(Tomcat tomcat) {
    return new TomcatEmbeddedServletContainer(tomcat, getPort() >= 0);
}

该函数很简单,就是来创建 Tomcat 容器并返回。看看 TomcatEmbeddedServletContainer 类:

public class TomcatEmbeddedServletContainer implements EmbeddedServletContainer {
</span><span style="color: rgba(0, 0, 255, 1)">public</span> TomcatEmbeddedServletContainer(Tomcat tomcat, <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)"> autoStart) {
    Assert.notNull(tomcat, </span>"Tomcat Server must not be null"<span style="color: rgba(0, 0, 0, 1)">);
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.tomcat =<span style="color: rgba(0, 0, 0, 1)"> tomcat;
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.autoStart =<span style="color: rgba(0, 0, 0, 1)"> autoStart;
    
    </span><strong><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">初始化嵌入式Tomcat容器,并启动Tomcat</span>

initialize();
}

</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span> initialize() <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> EmbeddedServletContainerException {
    TomcatEmbeddedServletContainer.logger
            .info(</span>"Tomcat initialized with port(s): " + getPortsDescription(<span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">));
    </span><span style="color: rgba(0, 0, 255, 1)">synchronized</span> (<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.monitor) {
        </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
            addInstanceIdToEngineName();
            </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
                </span><span style="color: rgba(0, 0, 255, 1)">final</span> Context context =<span style="color: rgba(0, 0, 0, 1)"> findContext();
                context.addLifecycleListener(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> LifecycleListener() {

                    @Override
                    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> lifecycleEvent(LifecycleEvent event) {
                        </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (context.equals(event.getSource())
                                </span>&amp;&amp;<span style="color: rgba(0, 0, 0, 1)"> Lifecycle.START_EVENT.equals(event.getType())) {
                            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Remove service connectors so that protocol
                            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> binding doesn't happen when the service is
                            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> started.</span>

removeServiceConnectors();
}
}

                });

                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Start the server to trigger initialization listeners
                </span><strong><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">启动tomcat</span>
                <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.tomcat.start();

                </span></strong><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> We can re-throw failure exception directly in the main thread</span>

rethrowDeferredStartupExceptions();

                </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
                    ContextBindings.bindClassLoader(context, getNamingToken(context),
                            getClass().getClassLoader());
                }
                </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (NamingException ex) {
                    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Naming is not enabled. Continue</span>

}

                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Unlike Jetty, all Tomcat threads are daemon threads. We create a
                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> blocking non-daemon to stop immediate shutdown</span>

startDaemonAwaitThread();
}
catch (Exception ex) {
containerCounter.decrementAndGet();
throw ex;
}
}
catch (Exception ex) {
stopSilently();
throw new EmbeddedServletContainerException(
"Unable to start embedded Tomcat", ex);
}
}
}
}

到这里就启动了嵌入式的 Servlet 容器,其他容器类似。

回到顶部

Servlet 容器启动原理

SpringBoot 启动过程

我们回顾一下前面讲解的 SpringBoot 启动过程,也就是 run 方法:

public ConfigurableApplicationContext run(String... args) {
    // 计时工具
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
ConfigurableApplicationContext context </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
Collection</span>&lt;SpringBootExceptionReporter&gt; exceptionReporters = <span style="color: rgba(0, 0, 255, 1)">new</span> ArrayList&lt;&gt;<span style="color: rgba(0, 0, 0, 1)">();

configureHeadlessProperty();

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 第一步:获取并启动监听器</span>
SpringApplicationRunListeners listeners =<span style="color: rgba(0, 0, 0, 1)"> getRunListeners(args);
listeners.starting();

</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
    ApplicationArguments applicationArguments </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> DefaultApplicationArguments(args);

    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 第二步:根据SpringApplicationRunListeners以及参数来准备环境</span>
    ConfigurableEnvironment environment =<span style="color: rgba(0, 0, 0, 1)"> prepareEnvironment(listeners,applicationArguments);
    configureIgnoreBeanInfo(environment);

    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体</span>
    Banner printedBanner =<span style="color: rgba(0, 0, 0, 1)"> printBanner(environment);

    </span><strong><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 第三步:创建Spring容器</span>
    context =</strong><span style="color: rgba(0, 0, 0, 1)"><strong> createApplicationContext();</strong>

    exceptionReporters </span>=<span style="color: rgba(0, 0, 0, 1)"> getSpringFactoriesInstances(
            SpringBootExceptionReporter.</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">,
            </span><span style="color: rgba(0, 0, 255, 1)">new</span> Class[] { ConfigurableApplicationContext.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> }, context);

    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 第四步:Spring容器前置处理</span>

prepareContext(context, environment, listeners, applicationArguments,printedBanner);

    </span><strong><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 第五步:刷新容器</span>

refreshContext(context);

     // 第六步:Spring 容器后置处理
afterRefresh(context, applicationArguments);

    // 第七步:发出结束执行的事件
listeners.started(context);
// 第八步:执行 Runners
this.callRunners(context, applicationArguments);
stopWatch.stop();
// 返回容器
return context;
}
catch (Throwable ex) {
handleRunFailure(context, listeners, exceptionReporters, ex);
throw new IllegalStateException(ex);
}
}

我们回顾一下第三步:创建 Spring 容器

public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
            + "annotation.AnnotationConfigApplicationContext";

public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework."
+ "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";

protected ConfigurableApplicationContext createApplicationContext() {
Class
<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
//根据应用环境,创建不同的 IOC 容器
contextClass = Class.forName(this.webEnvironment
?
DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
}

创建 IOC 容器,如果是 web 应用,则创建AnnotationConfigEmbeddedWebApplicationContext的 IOC 容器;如果不是,则创建 AnnotationConfigApplicationContext 的 IOC 容器;很明显我们创建的容器是 AnnotationConfigEmbeddedWebApplicationContext接着我们来看看第五步,刷新容器refreshContext(context);
private void refreshContext(ConfigurableApplicationContext context) {refresh(context);
}

protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.
class, applicationContext);
//调用容器的 refresh() 方法刷新容器
((AbstractApplicationContext) applicationContext).refresh();
}

容器刷新过程

调用抽象父类 AbstractApplicationContext 的refresh() 方法;

AbstractApplicationContext

 1 public void refresh() throws BeansException, IllegalStateException {
 2     synchronized (this.startupShutdownMonitor) {
 3         /**
 4          * 刷新上下文环境
 5          */
 6         prepareRefresh();
 7 
 8         /**
 9          * 初始化 BeanFactory,解析 XML,相当于之前的 XmlBeanFactory 的操作,
10          */
11         ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
12 
13         /**
14          * 为上下文准备 BeanFactory,即对 BeanFactory 的各种功能进行填充,如常用的注解 @Autowired @Qualifier 等
15          * 添加 ApplicationContextAwareProcessor 处理器
16          * 在依赖注入忽略实现 *Aware 的接口,如 EnvironmentAware、ApplicationEventPublisherAware 等
17          * 注册依赖,如一个 bean 的属性中含有 ApplicationEventPublisher(beanFactory),则会将 beanFactory 的实例注入进去
18          */
19         prepareBeanFactory(beanFactory);
20 
21         try {
22             /**
23              * 提供子类覆盖的额外处理,即子类处理自定义的 BeanFactoryPostProcess
24              */
25             postProcessBeanFactory(beanFactory);
26 
27             /**
28              * 激活各种 BeanFactory 处理器, 包括 BeanDefinitionRegistryBeanFactoryPostProcessor 和普通的 BeanFactoryPostProcessor
29              * 执行对应的 postProcessBeanDefinitionRegistry 方法 和  postProcessBeanFactory 方法
30              */
31             invokeBeanFactoryPostProcessors(beanFactory);
32 
33             /**
34              * 注册拦截 Bean 创建的 Bean 处理器,即注册 BeanPostProcessor,不是 BeanFactoryPostProcessor,注意两者的区别
35              * 注意,这里仅仅是注册,并不会执行对应的方法,将在 bean 的实例化时执行对应的方法
36              */
37             registerBeanPostProcessors(beanFactory);
38 
39             /**
40              * 初始化上下文中的资源文件,如国际化文件的处理等
41              */
42             initMessageSource();
43 
44             /**
45              * 初始化上下文事件广播器,并放入 applicatioEventMulticaster, 如 ApplicationEventPublisher
46              */
47             initApplicationEventMulticaster();
48 
49             /**
50              * 给子类扩展初始化其他 Bean
51              */
52             onRefresh();
53 
54             /**
55              * 在所有 bean 中查找 listener bean,然后注册到广播器中
56              */
57             registerListeners();
58 
59             /**
60              * 设置转换器
61              * 注册一个默认的属性值解析器
62              * 冻结所有的 bean 定义,说明注册的 bean 定义将不能被修改或进一步的处理
63              * 初始化剩余的非惰性的 bean,即初始化非延迟加载的 bean
64              */
65             finishBeanFactoryInitialization(beanFactory);
66 
67             /**
68              * 通过 spring 的事件发布机制发布 ContextRefreshedEvent 事件,以保证对应的监听器做进一步的处理
69              * 即对那种在 spring 启动后需要处理的一些类,这些类实现了 ApplicationListener<ContextRefreshedEvent>,
70              * 这里就是要触发这些类的执行 (执行 onApplicationEvent 方法)
71              * spring 的内置 Event 有 ContextClosedEvent、ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent、RequestHandleEvent
72              * 完成初始化,通知生命周期处理器 lifeCycleProcessor 刷新过程,同时发出 ContextRefreshEvent 通知其他人
73              */
74             finishRefresh();
75         }
76 
77         finally {
78     
79             resetCommonCaches();
80         }
81     }
82 }

我们看第 52 行的方法:

protected void onRefresh() throws BeansException {

}

很明显抽象父类 AbstractApplicationContext 中的 onRefresh 是一个空方法,并且使用 protected 修饰,也就是其子类可以重写 onRefresh 方法,那我们看看其子类 AnnotationConfigEmbeddedWebApplicationContext 中的 onRefresh 方法是如何重写的,AnnotationConfigEmbeddedWebApplicationContext 又继承 EmbeddedWebApplicationContext,如下:

public class AnnotationConfigEmbeddedWebApplicationContext extends EmbeddedWebApplicationContext {

那我们看看其父类 EmbeddedWebApplicationContext 是如何重写 onRefresh 方法的:

EmbeddedWebApplicationContext

@Override
protected void onRefresh() {
    super.onRefresh();
    try {
        //核心方法:会获取嵌入式的 Servlet 容器工厂,并通过工厂来获取 Servlet 容器
        createEmbeddedServletContainer();
    }
    catch (Throwable ex) {
        throw new ApplicationContextException("Unable to start embedded container", ex);}
}

在 createEmbeddedServletContainer 方法中会获取嵌入式的 Servlet 容器工厂,并通过工厂来获取 Servlet 容器:

 1 private void createEmbeddedServletContainer() {
 2     EmbeddedServletContainer localContainer = this.embeddedServletContainer;
 3     ServletContext localServletContext = getServletContext();
 4     if (localContainer == null && localServletContext == null) {
 5         //先获取嵌入式 Servlet 容器工厂
 6         EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
 7         //根据容器工厂来获取对应的嵌入式 Servlet 容器
 8         this.embeddedServletContainer = containerFactory.getEmbeddedServletContainer(getSelfInitializer());
 9     }
10     else if (localServletContext != null) {
11         try {
12             getSelfInitializer().onStartup(localServletContext);
13         }
14         catch (ServletException ex) {
15             throw new ApplicationContextException("Cannot initialize servlet context",ex);
16         }
17     }
18     initPropertySources();
19 }

关键代码在第 6 和第 8 行,先获取 Servlet 容器工厂,然后根据容器工厂来获取对应的嵌入式 Servlet 容器

获取 Servlet 容器工厂

protected EmbeddedServletContainerFactory getEmbeddedServletContainerFactory() {
    //从 Spring 的 IOC 容器中获取 EmbeddedServletContainerFactory.class 类型的 Bean
    String[] beanNames = getBeanFactory().getBeanNamesForType(EmbeddedServletContainerFactory.class);
    //调用 getBean 实例化 EmbeddedServletContainerFactory.class
    return getBeanFactory().getBean(beanNames[0], EmbeddedServletContainerFactory.class);
}

我们看到先从 Spring 的 IOC 容器中获取 EmbeddedServletContainerFactory.class 类型的 Bean,然后调用 getBean 实例化 EmbeddedServletContainerFactory.class,大家还记得我们第一节 Servlet 容器自动配置类 EmbeddedServletContainerAutoConfiguration 中注入 Spring 容器的对象是什么吗?当我们引入 spring-boot-starter-web 这个启动器后,会注入TomcatEmbeddedServletContainerFactory这个对象到 Spring 容器中,所以这里获取到的Servlet 容器工厂是TomcatEmbeddedServletContainerFactory,然后调用

TomcatEmbeddedServletContainerFactory 的 getEmbeddedServletContainer 方法获取 Servlet 容器,并且启动 Tomcat,大家可以看看文章开头的 getEmbeddedServletContainer 方法。

大家看一下第 8 行代码获取 Servlet 容器方法的参数 getSelfInitializer(),这是个啥?我们点进去看看

private ServletContextInitializer getSelfInitializer() {
    //创建一个 ServletContextInitializer 对象,并重写 onStartup 方法,很明显是一个回调方法
    return new ServletContextInitializer() {
        public void onStartup(ServletContext servletContext) throws ServletException {
            EmbeddedWebApplicationContext.this.selfInitialize(servletContext);
        }
    };
}

创建一个 ServletContextInitializer 对象,并重写 onStartup 方法,很明显是一个回调方法,这里给大家留一点疑问:

  • ServletContextInitializer 对象创建过程是怎样的?
  • onStartup 是何时调用的?
  • onStartup 方法的作用是什么?

ServletContextInitializer是 Servlet 容器初始化的时候,提供的初始化接口。这里涉及到 Servlet、Filter 实例的注册,我们留在下一篇具体讲