# Spring Boot 嵌入式容器

我们都知道，默认情况下 SpringBoot 应用可以打jar包运行，在启动IOC容器时引导启动嵌入式Web容器（Tomcat），而且根据之前的IOC原理，我们知道在 `ServletWebServerApplicationContext` 的 `onRefresh` 方法中创建了嵌入式 Tomcat，这一篇咱来展开研究嵌入式 Tomcat 创建的过程。

## 0. 前置知识

Tomcat 的内部核心结构包含如下组件：

* Service：一个 Tomcat-Server 可以有多个 Service ， Service 中包含下面的所有组件。
* Connector：用于与客户端交互，接收客户端的请求，并将结果响应给客户端。
* Engine：负责处理来自 Service 中的 Connector 的所有请求。
* Host：可理解为主机，一个主机绑定一个端口号。
* Context：可理解为应用，一个主机下有多个应用，一个应用中有多个 Servlet （可以简单理解为 webapps 中一个文件夹代表一个 Context ）。

## 1. ServletWebServerApplicationContext#onRefresh

```java
protected void onRefresh() {
    super.onRefresh();
    try {
        createWebServer();
    }
    catch (Throwable ex) {
        throw new ApplicationContextException("Unable to start web server", ex);
    }
}
```

在 `ServletWebServerApplicationContext` 的 `onRefresh` 方法会调用 `createWebServer` 方法创建嵌入式 Tomcat。

```java
private void createWebServer() {
    WebServer webServer = this.webServer;
    ServletContext servletContext = getServletContext();
    // 如果WebServer和ServletContext都为null时，证明需要创建嵌入式Tomcat
    if (webServer == null && servletContext == null) {
        // 创建了嵌入式Tomcat的工厂
        ServletWebServerFactory factory = getWebServerFactory();
        // 创建嵌入式Tomcat
        this.webServer = factory.getWebServer(getSelfInitializer());
    }
    else if (servletContext != null) {
        try {
            getSelfInitializer().onStartup(servletContext);
        }
        catch (ServletException ex) {
            throw new ApplicationContextException("Cannot initialize servlet context", ex);
        }
    }
    initPropertySources();
}
```

这部分源码我们之前也都看过，重点来看 `getWebServer` 中的创建嵌入式 Tomcat 的部分：

## 2. TomcatServletWebServerFactory#getWebServer

```java
public static final String DEFAULT_PROTOCOL = "org.apache.coyote.http11.Http11NioProtocol";
private String protocol = DEFAULT_PROTOCOL;

public WebServer getWebServer(ServletContextInitializer... initializers) {
    Tomcat tomcat = new Tomcat();
    // 给嵌入式Tomcat创建一个临时文件夹，用于存放Tomcat运行中需要的文件
    File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
    tomcat.setBaseDir(baseDir.getAbsolutePath());
    // Tomcat核心概念：Connector，默认放入的protocol为NIO模式
    Connector connector = new Connector(this.protocol);
    // 给Service添加Connector
    tomcat.getService().addConnector(connector);
    // 执行定制器，修改即将设置到Tomcat中的Connector
    customizeConnector(connector);
    tomcat.setConnector(connector);
    // 关闭热部署（嵌入式Tomcat不存在修改web.xml、war包等情况）
    tomcat.getHost().setAutoDeploy(false);
    // 设置backgroundProcessorDelay机制
    configureEngine(tomcat.getEngine());
    for (Connector additionalConnector : this.additionalTomcatConnectors) {
        tomcat.getService().addConnector(additionalConnector);
    }
    // 2.1 生成TomcatEmbeddedContext
    prepareContext(tomcat.getHost(), initializers);
    // 3. 创建TomcatWebServer
    return getTomcatWebServer(tomcat);
}
```

咱来看这个方法，首先它new了一个 `Tomcat` 对象（它还不是真正的嵌入式 Tomcat），之后下面的一大堆都是对 Tomcat 的配置，这里面着重看一眼这个最复杂的 `prepareContext` 方法：

### 2.1 prepareContext

```java
protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
    File documentRoot = getValidDocumentRoot();
    // 创建TomcatEmbeddedContext
    TomcatEmbeddedContext context = new TomcatEmbeddedContext();
    if (documentRoot != null) {
        context.setResources(new LoaderHidingResourceRoot(context));
    }
    context.setName(getContextPath());
    context.setDisplayName(getDisplayName());
    // 设置contextPath，很熟悉了
    context.setPath(getContextPath());
    // 给嵌入式Tomcat创建docbase的临时文件夹
    File docBase = (documentRoot != null) ? documentRoot : createTempDir("tomcat-docbase");
    context.setDocBase(docBase.getAbsolutePath());
    // 注册监听器
    context.addLifecycleListener(new FixContextListener());
    context.setParentClassLoader((this.resourceLoader != null) ? this.resourceLoader.getClassLoader()
                                 : ClassUtils.getDefaultClassLoader());
    // 设置默认编码映射
    resetDefaultLocaleMapping(context);
    addLocaleMappings(context);
    context.setUseRelativeRedirects(false);
    try {
        context.setCreateUploadTargets(true);
    }
    catch (NoSuchMethodError ex) {
        // Tomcat is < 8.5.39. Continue.
    }
    configureTldSkipPatterns(context);
    // 自定义的类加载器，可以加载web应用的jar包
    WebappLoader loader = new WebappLoader(context.getParentClassLoader());
    loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName());
    // 指定类加载器遵循双亲委派机制
    loader.setDelegate(true);
    context.setLoader(loader);
    // 注册默认的Servlet
    if (isRegisterDefaultServlet()) {
        addDefaultServlet(context);
    }
    // 如果需要jsp支持，注册jsp的Servlet和Initializer
    if (shouldRegisterJspServlet()) {
        addJspServlet(context);
        addJasperInitializer(context);
    }
    // 注册监听器
    context.addLifecycleListener(new StaticResourceConfigurer(context));
    ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
    host.addChild(context);
    configureContext(context, initializersToUse);
    postProcessContext(context);
}
```

回到 `getWebServer` 中，最下面执行最核心的方法： `getTomcatWebServer`

## 3. getTomcatWebServer

```java
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
    return new TomcatWebServer(tomcat, getPort() >= 0);
}
```

它只是new了一个 `TomcatWebServer` 而已，进入构造方法中：

```java
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
    Assert.notNull(tomcat, "Tomcat Server must not be null");
    this.tomcat = tomcat;
    this.autoStart = autoStart;
    initialize();
}
```

构造方法中竟然还暗藏玄机，它在属性赋值后执行了 `initialize` 方法。

## 4. initialize

```java
private void initialize() throws WebServerException {
    logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
    try {
        // 4.1 设置Engine的id
        addInstanceIdToEngineName();

        // 4.2 获取第一个Context
        Context context = findContext();
        // 4.3 添加监听器
        context.addLifecycleListener((event) -> {
            if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
                // Remove service connectors so that protocol binding doesn't
                // happen when the service is started.
                removeServiceConnectors();
            }
        });

        // Start the server to trigger initialization listeners
        // 4.4 启动Tomcat
        this.tomcat.start();

        // We can re-throw failure exception directly in the main thread
        rethrowDeferredStartupExceptions();

        // ......
    }
}
```

把整个启动过程分为几步来看：

### 4.1 addInstanceIdToEngineName：设置Engine的id

```java
private void addInstanceIdToEngineName() {
    int instanceId = containerCounter.incrementAndGet();
    if (instanceId > 0) {
        Engine engine = this.tomcat.getEngine();
        engine.setName(engine.getName() + "-" + instanceId);
    }
}
```

这部分在初始化时，`containerCounter` 的值是-1，调用 `incrementAndGet` 方法后返回0。因为是0，下面的if也就不进入了。

![img](https://cdn.nlark.com/yuque/0/2022/png/229542/1672233260667-62582531-13b3-462e-85b7-5932b6d86ec2.png)

### 4.2 findContext：获取第一个Context

```java
private Context findContext() {
    for (Container child : this.tomcat.getHost().findChildren()) {
        if (child instanceof Context) {
            return (Context) child;
        }
    }
    throw new IllegalStateException("The host does not contain a Context");
}
```

这一步它要拿到Tomcat中的 `host`，来获取它的 `children` 。通过Debug发现已经存在一组 `Container` 了，当然也只有一个：

![img](https://cdn.nlark.com/yuque/0/2022/png/229542/1672233301591-db3cca7a-4a72-4a75-9b8a-76d27af429fe.png)

拿到之后，返回去。

### 4.3 addLifecycleListener：添加监听器

```java
context.addLifecycleListener((event) -> {
    if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
        // Remove service connectors so that protocol binding doesn't
        // happen when the service is started.
        // 删除ServiceConnectors，以便在启动服务时不会发生协议绑定。
        removeServiceConnectors();
    }
});
```

这一步的单行注释是解释不让 Connector 初始化，可为什么人家组件都初始化了，就单单不让它初始化呢？这要回归到IOC容器的启动原理中。

创建嵌入式 Tomcat 的时机是 `onRefresh` 方法，此时还有很多单实例Bean没有被创建，此时如果直接初始化所有组件后，Connector 也被初始化，此时客户端就可以与 Tomcat 进行交互，但这个时候单实例Bean还没有初始化完毕（尤其是 `DispatcherServlet`），就会导致传入的请求 Tomcat 无法处理，出现异常。

所以 SpringBoot 为了避免这个问题，会**在嵌入式 Tomcat 发布事件时检测此时的** `**Context**` **状态是否为** `**"START_EVENT"**` **，如果是则将这些** `**Connector**` **先移除掉**。

### 4.4 this.tomcat.start：启动Tomcat

```java
public void start() throws LifecycleException {
    getServer();
    // 4.5
    server.start();
}
```

第一步的操作跟前面是一样的，下面是 `server.start()` ，它来真正启动嵌入式 Tomcat 。

### 4.5 server.start

来到 `LifecycleBase` ：

```java
public final synchronized void start() throws LifecycleException {

    if (LifecycleState.STARTING_PREP.equals(state) || LifecycleState.STARTING.equals(state) ||
        LifecycleState.STARTED.equals(state)) {

        if (log.isDebugEnabled()) {
            Exception e = new LifecycleException();
            log.debug(sm.getString("lifecycleBase.alreadyStarted", toString()), e);
        } else if (log.isInfoEnabled()) {
            log.info(sm.getString("lifecycleBase.alreadyStarted", toString()));
        }

        return;
    }

    if (state.equals(LifecycleState.NEW)) {
        init();
    } else if (state.equals(LifecycleState.FAILED)) {
        // ......
    }
```

这里启动时会根据当前的状态来走不同的分支，而刚启动的 Tomcat 状态为 `"NEW"` ，进入 init 方法：

![img](https://cdn.nlark.com/yuque/0/2022/png/229542/1672235170194-38411c9e-b699-4660-a697-c9fbb18801f2.png)

#### 4.5.1 StandardServer#init

来到父类 `LifecycleBase` 中（`StandardServer` 没有重写）：

```java
public final synchronized void init() throws LifecycleException {
    if (!state.equals(LifecycleState.NEW)) {
        invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
    }

    try {
        setStateInternal(LifecycleState.INITIALIZING, null, false);
        initInternal();
        setStateInternal(LifecycleState.INITIALIZED, null, false);
    } catch (Throwable t) {
        handleSubClassException(t, "lifecycleBase.initFail", toString());
    }
}
```

上面对于不为NEW的状态，会额外执行方法，当前状态为**NEW**，不进入，走下面的try块。try块在 `initInternal` 的前后设置了两次生命周期的状态（初始化中、初始化完成），说明 `initInternal` 方法中一定是真正的 Tomcat 组件初始化的过程。

#### 4.5.2 StandardServer#initInternal

来到 `StandardServer` ：

```java
protected void initInternal() throws LifecycleException {

    super.initInternal();

    // ......
}
```

先来到父类 `LifecycleBase` 的 `initInternal` 方法：

```csharp
protected void initInternal() throws LifecycleException {
    // If oname is not null then registration has already happened via preRegister().
    // 如果oname不为null，则已经通过preRegister()进行了注册
    if (oname == null) {
        mserver = Registry.getRegistry(null, null).getMBeanServer();
        oname = register(this, getObjectNameKeyProperties());
    }
}
```

默认情况下 `oname` 为null，走下面的初始化过程。初始化完成后的效果：

![img](https://cdn.nlark.com/yuque/0/2022/png/229542/1672235297931-3ffdf3de-eb83-4888-ba7b-d4e6275c305d.png)

回到子类 `StandardServer` ：

```java
protected void initInternal() throws LifecycleException {

    super.initInternal();

    // ......
    // Initialize our defined Services
    for (int i = 0; i < services.length; i++) {
        services[i].init();
    }
}
```

中间的一大段我们暂且不关心，主要来看最后一步：它要初始化这些 `Server` 中的 `Service` 。

#### 4.5.3 又回到LifecycleBase

```java
public final synchronized void init() throws LifecycleException {
    if (!state.equals(LifecycleState.NEW)) {
        invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
    }

    try {
        setStateInternal(LifecycleState.INITIALIZING, null, false);
        initInternal();
        setStateInternal(LifecycleState.INITIALIZED, null, false);
    } catch (Throwable t) {
        handleSubClassException(t, "lifecycleBase.initFail", toString());
    }
}
```

再走一遍流程，进到 `initInternal` 方法，这次初始化的是 `StandardService`：

#### 4.5.4 StandardService#initInternal

```java
protected void initInternal() throws LifecycleException {

    super.initInternal();

    if (engine != null) {
        engine.init();
    }
    
    // Initialize any Executors
    for (Executor executor : findExecutors()) {
        if (executor instanceof JmxEnabled) {
            ((JmxEnabled) executor).setDomain(getDomain());
        }
        executor.init();
    }
    
    // Initialize mapper listener
    mapperListener.init();
    
    // Initialize our defined Connectors
    synchronized (connectorsLock) {
        for (Connector connector : connectors) {
            connector.init();
        }
    }
}
```

发现了这里面要依次初始化几个组件：`Engine`，`Executor`，`LifecycleMBeanBase(mapperListener)`，`Connector` 。

到这里咱应该有种意识：这几个家伙的初始化不会又回到 `LifecycleBase` 中了吧？可以很确定的回答：基本是的。。。所以接下来咱就不贴 `LifecycleBase` 的源码了，直接看这几个组件的实现吧：

#### 4.5.5 StandardEngine#initInternal

```java
protected void initInternal() throws LifecycleException {
    // Ensure that a Realm is present before any attempt is made to start
    // one. This will create the default NullRealm if necessary.
    // 在尝试启动一个Realm之前，请确保存在一个Realm。如有必要，这将创建默认的NullRealm
    getRealm();
	super.initInternal();
}

public Realm getRealm() {
    Realm configured = super.getRealm();
    // If no set realm has been called - default to NullRealm
    // This can be overridden at engine, context and host level
    if (configured == null) {
        configured = new NullRealm();
        this.setRealm(configured);
    }
    return configured;
}
```

很明显这里是初始化Realm的，且实现很简单，不再展开。

#### 4.5.6 Executor#initInternal

```java
protected void initInternal() throws LifecycleException {
    super.initInternal();
}
```

它还是调的父类 `LifecycleMBeanBase` 的方法，不再赘述。

#### 4.5.7 MapperListener#initInternal

`MapperListener` 没有重写 `initInternal` 方法，相当于也跟上面一样，不再赘述。

```java
protected void initInternal() throws LifecycleException {
    // If oname is not null then registration has already happened via preRegister().
    // 如果oname不为null，则已经通过preRegister()进行了注册
    if (oname == null) {
        mserver = Registry.getRegistry(null, null).getMBeanServer();
        oname = register(this, getObjectNameKeyProperties());
    }
}
```

跟上面一样，不再赘述。

#### 4.5.8 Connector#initInternal

```java
protected void initInternal() throws LifecycleException {
    super.initInternal();
    // ......
    
    // Initialize adapter
    // CoyoteAdapter负责连接Coyote和Servlet容器(web应用)
    adapter = new CoyoteAdapter(this);
    protocolHandler.setAdapter(adapter);
    // ......
    
    try {
        protocolHandler.init();
    } catch (Exception e) {
        throw new LifecycleException(
            sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);
    }
}
```

中间部分它初始化了一个 `CoyoteAdapter` ，它负责连接 `Connector` 和 `Container` ，也就是 `Coyote` 和 `Servlet容器`。

最下面的try-catch中，它又调用了 `protocolHandler.init` ：

#### 4.5.9 protocolHandler.init

来到 `AbstractHttp11Protocol` ：

```java
public void init() throws Exception {
    // Upgrade protocols have to be configured first since the endpoint
    // init (triggered via super.init() below) uses this list to configure
    // the list of ALPN protocols to advertise
    // 必须先配置升级协议，因为端点初始化（通过下面的super.init()触发）使用此列表来配置要发布的ALPN协议列表
    for (UpgradeProtocol upgradeProtocol : upgradeProtocols) {
    configureUpgradeProtocol(upgradeProtocol);
}

super.init();
}
```

Debug发现这个 `upgradeProtocols` 为空，直接走下面父类（`AbstractProtocol`）的 `init` 方法：

```java
public void init() throws Exception {
    if (getLog().isInfoEnabled()) {
        getLog().info(sm.getString("abstractProtocolHandler.init", getName()));
        logPortOffset();
    }
    
    if (oname == null) {
        // Component not pre-registered so register it
        oname = createObjectName();
        if (oname != null) {
            Registry.getRegistry(null, null).registerComponent(this, oname, null);
        }
    }
    
    if (this.domain != null) {
        rgOname = new ObjectName(domain + ":type=GlobalRequestProcessor,name=" + getName());
        Registry.getRegistry(null, null).registerComponent(
            getHandler().getGlobal(), rgOname, null);
    }
    
    String endpointName = getName();
    endpoint.setName(endpointName.substring(1, endpointName.length()-1));
    endpoint.setDomain(domain);
    
    endpoint.init();
}
```

上面又是一堆初始化，这个咱暂且不关注，注意最底下有一个 `endpoint.init` ：

#### 4.5.10 endpoint.init

来到 `AbstractEndPoint` ：

```java
public final void init() throws Exception {
    // Debug为false
    if (bindOnInit) {
        bindWithCleanup();
        bindState = BindState.BOUND_ON_INIT;
    }
    if (this.domain != null) {
        // Register endpoint (as ThreadPool - historical name)
        oname = new ObjectName(domain + ":type=ThreadPool,name=\"" + getName() + "\"");
        Registry.getRegistry(null, null).registerComponent(this, oname, null);

        ObjectName socketPropertiesOname = new ObjectName(domain +
                ":type=ThreadPool,name=\"" + getName() + "\",subType=SocketProperties");
        socketProperties.setObjectName(socketPropertiesOname);
        Registry.getRegistry(null, null).registerComponent(socketProperties, socketPropertiesOname, null);

        for (SSLHostConfig sslHostConfig : findSslHostConfigs()) {
            registerJmx(sslHostConfig);
        }
    }
}
```

这里面又是初始化 `oname` ，又是配置 `socketProperties` 的，但这里面再也没见到 `init` 方法，证明这部分初始化过程已经结束了。

值得注意的是，Debug发现 `bindOnInit` 变量为false，说明**嵌入式 Tomcat 不在初始化期间绑定端口号**。

#### 4.5.11 初始化小结

嵌入式 Tomcat 的组件初始化步骤顺序如下：

1. Server
2. Service
3. Engine
4. Executor
5. MapperListener
6. Connector
7. Protocol
8. EndPoint

***

至此，初始化过程完毕，回到 `start` 方法中：

```java
public final synchronized void start() throws LifecycleException {
    // ......
    if (state.equals(LifecycleState.NEW)) {
        init();
    } // else if ......

    try {
        setStateInternal(LifecycleState.STARTING_PREP, null, false);
        startInternal();
        // ......
    }
```

接下来到了真正启动的部分了：`startInternal`

### 4.6 StandardServer#startInternal

```java
protected void startInternal() throws LifecycleException {
    // 发布启动事件
    fireLifecycleEvent(CONFIGURE_START_EVENT, null);
    setState(LifecycleState.STARTING);
    
    // 4.6.1 NamingResources启动
    globalNamingResources.start();
    
    // Start our defined Services
    synchronized (servicesLock) {
        for (int i = 0; i < services.length; i++) {
            // 4.6.2 Service启动
            services[i].start();
        }
    }
    
    if (periodicEventDelay > 0) {
        monitorFuture = getUtilityExecutor().scheduleWithFixedDelay(
            new Runnable() {
                @Override
                public void run() {
                    startPeriodicLifecycleEvent();
                }
            }, 0, 60, TimeUnit.SECONDS);
    }
}
```

`startInternal` 方法中有两部分启动：`globalNamingResources` 启动，`services` 启动。分别来看：

#### 4.6.1 globalNamingResources.start

来到 `NamingResourcesImpl` ，因为它也实现了 `LifecycleBase` ，还是会来到上面的 `startInternal` 方法中：

```java
protected void startInternal() throws LifecycleException {
    fireLifecycleEvent(CONFIGURE_START_EVENT, null);
	setState(LifecycleState.STARTING);
}
```

这部分只是发布事件和设置状态而已，与之前一致，不再赘述。

#### 4.6.2 StandardService#start

最终又来到 `startInternal` 方法了：

```java
protected void startInternal() throws LifecycleException {

    if(log.isInfoEnabled())
        log.info(sm.getString("standardService.start.name", this.name));
    setState(LifecycleState.STARTING);
    
    // Start our defined Container first
    if (engine != null) {
        synchronized (engine) {
            // 4.6.3
            engine.start();
        }
    }
    
    synchronized (executors) {
        for (Executor executor: executors) {
            // 4.6.6
            executor.start();
        }
    }
    
    // 4.6.7
    mapperListener.start();
    
    // Start our defined Connectors second
    synchronized (connectorsLock) {
        for (Connector connector: connectors) {
            // If it has already failed, don't try and start it
            if (connector.getState() != LifecycleState.FAILED) {
                // 4.6.8
                connector.start();
            }
        }
    }
}
```

发现这部分与之前的初始化几乎一致！也是依次启动 `Engine` 、`Executor` 、`MapperListener` 、`Connector` 。

#### 4.6.3 Engine#start

```java
protected synchronized void startInternal() throws LifecycleException {
    // Log our server identification information
    if (log.isInfoEnabled()) {
        log.info(sm.getString("standardEngine.start", ServerInfo.getServerInfo()));
    }

    // Standard container startup
    super.startInternal();
}
```

它直接调的父类 `ContainerBase` 的 `startInternal` 方法：

```java
protected synchronized void startInternal() throws LifecycleException {

    // Start our subordinate components, if any
    logger = null;
    getLogger();
    // Cluster与集群相关，SpringBoot项目中使用嵌入式Tomcat，不存在集群
    Cluster cluster = getClusterInternal();
    if (cluster instanceof Lifecycle) {
        ((Lifecycle) cluster).start();
    }
    // Realm与授权相关
    Realm realm = getRealmInternal();
    if (realm instanceof Lifecycle) {
        ((Lifecycle) realm).start();
    }

    // Start our child containers, if any
    // Container的类型是StandardHost
    Container children[] = findChildren();
    List<Future<Void>> results = new ArrayList<>();
    for (int i = 0; i < children.length; i++) {
        // 4.6.4 异步初始化Host
        results.add(startStopExecutor.submit(new StartChild(children[i])));
    }

    MultiThrowable multiThrowable = null;

    for (Future<Void> result : results) {
        try {
            result.get();
        } catch (Throwable e) {
            log.error(sm.getString("containerBase.threadedStartFailed"), e);
            if (multiThrowable == null) {
                multiThrowable = new MultiThrowable();
            }
            multiThrowable.add(e);
        }

    }
    if (multiThrowable != null) {
        throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"),
                                     multiThrowable.getThrowable());
    }

    // Start the Valves in our pipeline (including the basic), if any
    if (pipeline instanceof Lifecycle) {
        ((Lifecycle) pipeline).start();
    }

    setState(LifecycleState.STARTING);

    // Start our thread
    if (backgroundProcessorDelay > 0) {
        monitorFuture = Container.getService(ContainerBase.this).getServer()
            .getUtilityExecutor().scheduleWithFixedDelay(
                new ContainerBackgroundProcessorMonitor(), 0, 60, TimeUnit.SECONDS);
    }
}
```

这里面又是嵌套的初始化了其他组件，一一来看：

#### 4.6.4 new StartChild(children\[i])

这部分比较有意思，它用了异步初始化，先来看看 `StartChild` 的定义：

```java
private static class StartChild implements Callable<Void>
```

它实现了带返回值的异步多线程接口 `**Callable**` ！那里面的核心方法就是 `**call**` ：

```java
public Void call() throws LifecycleException {
    child.start();
	return null;
}
```

它在这里初始化 `child`，而通过Debug得知 `child` 的类型是 `StandardHost`，故来到 `StandardHost` 的 `start` 方法：

```java
protected synchronized void startInternal() throws LifecycleException {
    // Set error report valve
    String errorValve = getErrorReportValveClass();
    if ((errorValve != null) && (!errorValve.equals(""))) {
        try {
            boolean found = false;
            Valve[] valves = getPipeline().getValves();
            for (Valve valve : valves) {
                if (errorValve.equals(valve.getClass().getName())) {
                    found = true;
                    break;
                }
            }
            if(!found) {
                Valve valve =
                    (Valve) Class.forName(errorValve).getConstructor().newInstance();
                getPipeline().addValve(valve);
            }
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            log.error(sm.getString(
                "standardHost.invalidErrorReportValveClass",
                errorValve), t);
        }
    }
    super.startInternal();
}
```

上面的一个大if结构是设置错误提示页面的，下面又调父类的 `startInternal` ：

```java
protected synchronized void startInternal() throws LifecycleException {
    // ......
    // Start our child containers, if any
    Container children[] = findChildren();
    List<Future<Void>> results = new ArrayList<>();
    for (int i = 0; i < children.length; i++) {
        results.add(startStopExecutor.submit(new StartChild(children[i])));
    }
```

又回来了。。。因为一个 `Host` 包含一个 `Context` 。

`Host` 搜索children就会搜到它下面的 `Context` ，之后又是下面的初始化过程，进入 Context 的初始化：

#### 4.6.5 TomcatEmbeddedContext#start

这个方法非常非常长（300行+），小册就不全部贴出来了，咱现在是启动阶段，那我们只关心里面的 start 相关代码。

借由前面的Debug过程，我们先总结一个规律：**所有带生命周期性质的组件，都会在启动时走到** `**startInternal**` **方法**。

那我们接下来Debug这个300行+的代码时，只需要在方法头和方法尾打上断点，中间部分只要走 `startInternal` 方法的，就是组件的初始化。通过Debug，发现有如下组件被调用了 start 方法：

* StandardRoot
* DirResourceSet
* WebappLoader
* JarResourceSet
* StandardWrapper
* StandardPineline
* StandardWrapperValve
* NonLoginAuthenticator
* StandardContextValve
* StandardManager
* LazySessionIdGenerator

组件的 `startInternal` 方法就不一一列举了，小册把这些组件的核心功能列举一下，有兴趣的小伙伴可以深入研究一下，小册也只是引导小伙伴们对嵌入式 Tomcat 的底层原理有一个大概的认识和了解。

#### 4.6.6 Executor#start

```java
synchronized (executors) {
    for (Executor executor: executors) {
        // 4.6.6
        executor.start();
    }
}
```

`Engine` 启动完成后，下一步到了 `Executor` 的启动。但由于 `Executor` 没有实现 `startInternal` 方法，故这一步不再展开。

#### 4.6.7 MapperListener#start

```java
mapperListener.start();
```

`Executor` 启动完成后，接下来启动 `MapperListener` ：

```java
public void startInternal() throws LifecycleException {
    setState(LifecycleState.STARTING);

    Engine engine = service.getContainer();
    if (engine == null) {
        return;
    }
    
    // 获取当前部署的主机名（本地调试为localhost）
    findDefaultHost();
    
    // 4.6.7.1 把当前自身注册到Engine、Host、Context、Wrapper中
    addListeners(engine);
    
    // 取出的Container的类型为Host
    Container[] conHosts = engine.findChildren();
    for (Container conHost : conHosts) {
        Host host = (Host) conHost;
        if (!LifecycleState.NEW.equals(host.getState())) {
            // Registering the host will register the context and wrappers
            // 4.6.7.2 将Host、Context、Wrapper注册到当前监听器中
            registerHost(host);
        }
    }
}
```

这里面它干了三件事请：获取主机名，将监听器注册到各组件中，将各组件注册到监听器（实现双向）。咱主要看一眼两方互相注册的动作：

**4.6.7.1 addListeners**

```java
private void addListeners(Container container) {
    container.addContainerListener(this);
    container.addLifecycleListener(this);
    for (Container child : container.findChildren()) {
        addListeners(child);
    }
}
```

很明显这是递归调用，而且从 `Engine` 开始一层一层往下执行，都把当前监听器注册进去。

**4.6.7.2 registerHost**

```java
private void registerHost(Host host) {
    String[] aliases = host.findAliases();
    mapper.addHost(host.getName(), aliases, host);
    
    for (Container container : host.findChildren()) {
        if (container.getState().isAvailable()) {
            registerContext((Context) container);
        }
    }
    
    // Default host may have changed
    findDefaultHost();
    
    // log ......
}
```

注意这里面的for循环，又调了 `registerContext` ，可见这个思路也跟上面差不多，类似于递归，不过这是手动一步一步往里设置（毕竟类型不一样）。

#### 4.6.8 Connector#start

```java
// Start our defined Connectors second
synchronized (connectorsLock) {
    for (Connector connector: connectors) {
        // If it has already failed, don't try and start it
        if (connector.getState() != LifecycleState.FAILED) {
            connector.start();
        }
    }
}
```

最后一步是启动 `Connector` 。但通过Debug发现根本没有 `Connector` ！

![img](https://cdn.nlark.com/yuque/0/2022/png/229542/1672236078792-01867ada-45e2-49b7-ad67-43862e5ff2f5.png)

为什么之前还看到一个 `Connector` ，现在就没了呢？还记得在 `this.tomcat.start();` 之前有一个监听器吗？

```java
context.addLifecycleListener((event) -> {
    if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
        // Remove service connectors so that protocol binding doesn't
        // happen when the service is started.
        removeServiceConnectors();
    }
});
```

很明显就是这一步把 `Connector` 删了嘛！

Remove service connectors so that protocol binding doesn't happen when the service is started.

删除 `Service` 的 `Connector` ，以便在启动服务时不会发生协议绑定。

那大概率就是这一步让这个家伙给删了。注意这个监听器是在 `context` 中注册的，那我们猜想，应该是 `Context` 启动时触发的这个监听效果。

回过头来重新Debug一次，发现在 `Engine` 启动之后，`Connector` 就已经没了。

![img](https://cdn.nlark.com/yuque/0/2022/png/229542/1672236109931-ef37a405-3d5b-4d26-897b-9c5f411d5e9c.png)

将断点打在上面 `context` 的监听器上，放行，发现果然 **在** `**TomcatEmbeddedContext**` **的启动期间触发了这个监听器**。

![img](https://cdn.nlark.com/yuque/0/2022/png/229542/1672236122435-ba176084-7727-470e-af08-544193de7c76.png)

至于为什么要删除的原因，上面4.3章节也描述了，是为了**防止 SpringBoot 应用还没有初始化完成时就已经可以接收客户端的请求**。

***

至此，`tomcat.start();` 方法彻底执行完成。

#### 4.6.9 启动小结

启动过程依次启动了如下组件：

1. NamingResources
2. Service
3. Engine
4. Host
5. Context
6. Wrapper
7. MapperListener

### 4.7 回到initialize

```java
private void initialize() throws WebServerException {
    // ......
    this.tomcat.start();
    
    // We can re-throw failure exception directly in the main thread
    // 如果上面的启动出现问题，则抛出异常
    rethrowDeferredStartupExceptions();
    
    try {
        // 将当前Context与当前ClassLoader绑定
        ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
    }
    catch (NamingException ex) {
        // Naming is not enabled. Continue
    }
    
    // Unlike Jetty, all Tomcat threads are daemon threads. We create a
    // blocking non-daemon to stop immediate shutdown
    // 4.8 阻止Tomcat结束
    startDaemonAwaitThread();
    }
    catch (Exception ex) {
        stopSilently();
        destroySilently();
        throw new WebServerException("Unable to start embedded Tomcat", ex);
    }
    }
}
```

嵌入式 Tomcat 启动完成后，在try块的最底下会起一个新的线程，阻止 Tomcat 结束。

### 4.8 startDaemonAwaitThread

```java
private void startDaemonAwaitThread() {
    Thread awaitThread = new Thread("container-" + (containerCounter.get())) {
        @Override
        public void run() {
            TomcatWebServer.this.tomcat.getServer().await();
        }
    };
    awaitThread.setContextClassLoader(getClass().getClassLoader());
    awaitThread.setDaemon(false);
    awaitThread.start();
}
```

这里面它会起一个新的 `awaitThread` 线程，并回调 Tomcat 中 `Server` 的 `await` 方法，并且它还设置 `Daemon` 为false。

先解释一下为什么设置 `Daemon` ：Tomcat 中所有的进程都是 Daemon 线程，在Java应用中，**只要有一个非 Daemon 线程还在运行，则 Daemon 线程就不会停止，整个应用也不会终止**。既然要让 Tomcat 一直运行以监听客户端请求，就必须需要让 Tomcat 内部的 Daemon 线程都存活，根据前面的描述，就必须制造一个能卡住停止的非 Daemon 线程。于是上面新起的 `awaitThread` 线程就被设置为非 Daemon 线程。

下面看看线程中执行的 `await` 方法：（源码很长，只截取出跟 SpringBoot 嵌入式 Tomcat 有关的部分）

```java
public void await() {
    // Negative values - don't wait on port - tomcat is embedded or we just don't like ports
    // 如果关闭Tomcat的端口是-2，则直接返回，不卡线程
    if (getPortWithOffset() == -2) {
        // undocumented yet - for embedding apps that are around, alive.
        return;
    }
    // 如果关闭Tomcat的端口是-1，代表是嵌入式Tomcat
    if (getPortWithOffset() == -1) {
        try {
            awaitThread = Thread.currentThread();
            while(!stopAwait) {
                try {
                    Thread.sleep( 10000 );
                } catch( InterruptedException ex ) {
                    // continue and check the flag
                }
            }
        } finally {
            awaitThread = null;
        }
        return;
    }

    // Set up a server socket to wait on
    // 退出端口正常的处理（属于外部Tomcat逻辑）......
}
```

可以发现，如果设置的 Tomcat 的退出端口是 -1，则代表是嵌入式 Tomcat，它会**每10秒会检查一次stopAwait的值**，如果为true则停止卡线程，让 Tomcat 停止。

默认请款下 Tomcat 的退出端口是8005，为什么这里会变成 -1 呢？追踪源码，发现在 `Tomcat` 的 `getServer` 方法中有设置：

```java
public Server getServer() {
    if (server != null) {
        return server;
    }

    System.setProperty("catalina.useNaming", "false");

    server = new StandardServer();

    initBaseDir();

    // Set configuration source
    ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(new File(basedir), null));

    // 设置端口号为 -1，代表嵌入式
    server.setPort( -1 );

    Service service = new StandardService();
    service.setName("Tomcat");
    server.addService(service);
    return server;
}
```

至此，嵌入式 Tomcat 已经成功创建好，但 `Connector` 还没有归还，还在被删除中。

## 5. ServletWebServerApplicationContext#startWebServer

当IOC容器的 onRefresh 方法执行完，单实例Bean初始化完成后，来到 `finishRefresh` 方法：

```java
protected void finishRefresh() {
    super.finishRefresh();
    WebServer webServer = startWebServer();
    if (webServer != null) {
        publishEvent(new ServletWebServerInitializedEvent(webServer, this));
    }
}
```

在这里它会真正启动嵌入式 Tomcat 容器：

```java
private WebServer startWebServer() {
    WebServer webServer = this.webServer;
    if (webServer != null) {
        webServer.start();
    }
    return webServer;
}
```

可以看到它在这里调用了 `TomcatWebServer` 的 `start` 方法。

## 6. TomcatWebServer#start

```java
public void start() throws WebServerException {
    synchronized (this.monitor) {
        if (this.started) {
            return;
        }
        try {
            // 6.1 还原、启动Connector
            addPreviouslyRemovedConnectors();
            // 只拿一个Connector
            Connector connector = this.tomcat.getConnector();
            if (connector != null && this.autoStart) {
                // 6.2 延迟启动
                performDeferredLoadOnStartup();
            }
            // 检查Connector是否正常启动
            checkThatConnectorsHaveStarted();
            this.started = true;
            logger.info("Tomcat started on port(s): " + getPortsDescription(true) + " with context path '"
                    + getContextPath() + "'");
        }
        // catch ......
        finally {
            // 解除ClassLoader与TomcatEmbeddedContext的绑定关系
            Context context = findContext();
            ContextBindings.unbindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
        }
    }
}
```

源码中的注释已解释的比较清楚，下面分述源码中两个重要的环节：还原 `Connector` 和启动 `Connector` ：

### 6.1 addPreviouslyRemovedConnectors：还原Connector

```java
private void addPreviouslyRemovedConnectors() {
    Service[] services = this.tomcat.getServer().findServices();
    for (Service service : services) {
        Connector[] connectors = this.serviceConnectors.get(service);
        if (connectors != null) {
            for (Connector connector : connectors) {
                // 6.1.1 添加并启动
                service.addConnector(connector);
                if (!this.autoStart) {
                    stopProtocolHandler(connector);
                }
            }
            this.serviceConnectors.remove(service);
        }
    }
}
```

可以发现它将一个缓存区的 `Connector` 一个一个取出放入 `Service` 中。注意在 `service.addConnector` 中有顺便启动的部分：

#### 6.1.1 service.addConnector

```java
public void addConnector(Connector connector) {
    synchronized (connectorsLock) {
        connector.setService(this);
        Connector results[] = new Connector[connectors.length + 1];
        System.arraycopy(connectors, 0, results, 0, connectors.length);
        results[connectors.length] = connector;
        connectors = results;
    }
    
    try {
        if (getState().isAvailable()) {
            // 6.1.2 启动Connector
            connector.start();
        }
    } catch (LifecycleException e) {
        throw new IllegalArgumentException(
            sm.getString("standardService.connector.startFailed", connector), e);
    }
    
    // Report this property change to interested listeners
    support.firePropertyChange("connector", null, connector);
}
```

前面的部分是取出 `Connector` ，并与 `Service` 绑定，之后中间部分的try块，会启动 `Connector` ：

#### 6.1.2 connector.start

```java
protected void startInternal() throws LifecycleException {

    // Validate settings before starting
    if (getPortWithOffset() < 0) {
        throw new LifecycleException(sm.getString(
            "coyoteConnector.invalidPort", Integer.valueOf(getPortWithOffset())));
    }
    
    setState(LifecycleState.STARTING);
    
    try {
        // 启动ProtocolHandler
        protocolHandler.start();
    } catch (Exception e) {
        throw new LifecycleException(
            sm.getString("coyoteConnector.protocolHandlerStartFailed"), e);
    }
}
```

`Connector` 的启动会引发 `ProtocolHandler` 的启动：

#### 6.1.3 protocolHandler.start

```java
public void start() throws Exception {
    if (getLog().isInfoEnabled()) {
        getLog().info(sm.getString("abstractProtocolHandler.start", getName()));
        logPortOffset();
    }
    
    // 启动EndPoint
    endpoint.start();
    monitorFuture = getUtilityExecutor().scheduleWithFixedDelay(
        new Runnable() {
            @Override
            public void run() {
                if (!isPaused()) {
                    startAsyncTimeout();
                }
            }
        }, 0, 60, TimeUnit.SECONDS);
}
```

`ProtocolHandler` 的启动会引发 EndPoint 的启动，至此所有组件均已启动完毕。

### 6.2 performDeferredLoadOnStartup：延迟启动

```java
private void performDeferredLoadOnStartup() {
    try {
        for (Container child : this.tomcat.getHost().findChildren()) {
            if (child instanceof TomcatEmbeddedContext) {
                // 延迟启动Context
                ((TomcatEmbeddedContext) child).deferredLoadOnStartup();
            }
        }
    }
    catch (Exception ex) {
        if (ex instanceof WebServerException) {
            throw (WebServerException) ex;
        }
        throw new WebServerException("Unable to start embedded Tomcat connectors", ex);
    }
}
```

发现这里面会延迟启动 `TomcatEmbeddedContext` ，此处它对比较老的表现层框架（如Struts）做了一些兼容支持，主要是替换类加载器，由于 SpringBoot 默认使用WebMvc或WebFlux，已不采用过老的表现层框架，故此处不再展开讨论。

至此，嵌入式 Tomcat 完整启动。

## 小结

1. 嵌入式 Tomcat 与外置的 Tomcat 在核心组件上都是一样的，主要包括 `Service` 、`Connector` 、`Engine` 、`Host` 、`Context` 。
2. Tomcat 的启动过程分为初始化和启动两个步骤，分别按照核心组件的顺序启动。
3. 嵌入式 Tomcat 在启动时要先移除掉 `Connector` ，防止IOC容器还没有全部启动完成后就能接收客户端的请求。
