Spring Boot 嵌入式容器

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

0. 前置知识

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

  • Service:一个 Tomcat-Server 可以有多个 Service , Service 中包含下面的所有组件。

  • Connector:用于与客户端交互,接收客户端的请求,并将结果响应给客户端。

  • Engine:负责处理来自 Service 中的 Connector 的所有请求。

  • Host:可理解为主机,一个主机绑定一个端口号。

  • Context:可理解为应用,一个主机下有多个应用,一个应用中有多个 Servlet (可以简单理解为 webapps 中一个文件夹代表一个 Context )。

1. ServletWebServerApplicationContext#onRefresh

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

ServletWebServerApplicationContextonRefresh 方法会调用 createWebServer 方法创建嵌入式 Tomcat。

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

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

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

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

它只是new了一个 TomcatWebServer 而已,进入构造方法中:

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

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

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

4.2 findContext:获取第一个Context

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

拿到之后,返回去。

4.3 addLifecycleListener:添加监听器

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

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

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

4.5 server.start

来到 LifecycleBase

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

4.5.1 StandardServer#init

来到父类 LifecycleBase 中(StandardServer 没有重写):

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

protected void initInternal() throws LifecycleException {

    super.initInternal();

    // ......
}

先来到父类 LifecycleBaseinitInternal 方法:

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

回到子类 StandardServer

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

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

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();
        }
    }
}

发现了这里面要依次初始化几个组件:EngineExecutorLifecycleMBeanBase(mapperListener)Connector

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

4.5.5 StandardEngine#initInternal

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

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

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

4.5.7 MapperListener#initInternal

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

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

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 ,它负责连接 ConnectorContainer ,也就是 CoyoteServlet容器

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

4.5.9 protocolHandler.init

来到 AbstractHttp11Protocol

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 方法:

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

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 方法中:

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

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 方法中:

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

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

4.6.2 StandardService#start

最终又来到 startInternal 方法了:

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();
            }
        }
    }
}

发现这部分与之前的初始化几乎一致!也是依次启动 EngineExecutorMapperListenerConnector

4.6.3 Engine#start

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();
}

它直接调的父类 ContainerBasestartInternal 方法:

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 的定义:

private static class StartChild implements Callable<Void>

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

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

它在这里初始化 child,而通过Debug得知 child 的类型是 StandardHost,故来到 StandardHoststart 方法:

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

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

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

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

4.6.7 MapperListener#start

mapperListener.start();

Executor 启动完成后,接下来启动 MapperListener

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

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

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

4.6.7.2 registerHost

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

// 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

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

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.

删除 ServiceConnector ,以便在启动服务时不会发生协议绑定。

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

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

img

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

img

至于为什么要删除的原因,上面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

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

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 中 Serverawait 方法,并且它还设置 Daemon 为false。

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

下面看看线程中执行的 await 方法:(源码很长,只截取出跟 SpringBoot 嵌入式 Tomcat 有关的部分)

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 呢?追踪源码,发现在 TomcatgetServer 方法中有设置:

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 方法:

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

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

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

可以看到它在这里调用了 TomcatWebServerstart 方法。

6. TomcatWebServer#start

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

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

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

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

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:延迟启动

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 在核心组件上都是一样的,主要包括 ServiceConnectorEngineHostContext

  2. Tomcat 的启动过程分为初始化和启动两个步骤,分别按照核心组件的顺序启动。

  3. 嵌入式 Tomcat 在启动时要先移除掉 Connector ,防止IOC容器还没有全部启动完成后就能接收客户端的请求。

最后更新于