【Tomcat源码分析】启动过程深度解析 (二)

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云原生内存数据库 Tair,内存型 2GB
简介: 本文深入探讨了Tomcat启动Web应用的过程,重点解析了其加载ServletContextListener及Servlet的机制。文章从Bootstrap反射调用Catalina的start方法开始,逐步介绍了StandardServer、StandardService、StandardEngine、StandardHost、StandardContext和StandardWrapper的启动流程。每个组件通过Lifecycle接口协调启动,子容器逐层启动,直至整个服务器完全启动。此外,还详细分析了Pipeline及其Valve组件的作用,展示了Tomcat内部组件间的协作机制。

前言

前文已述,Tomcat 的初始化由 Bootstrap 反射调用 Catalina 的 load 方法完成,包括解析 server.xml、实例化各组件、初始化组件等步骤。此番,我们将深入探究 Tomcat 如何启动 Web 应用,并解析其加载 ServletContextListener 及 Servlet 的机制。

前文参考文章:

【Tomcat 源码分析】揭秘 Tomcat 启动-初篇

Tomcat 启动逻辑层层递进,各部件协同运作。其启动流程自上而下,依次启动各个组件,如图:

承接前文,我们已解析了 Catalina.load() 方法,接下来将深入探讨 daemon.start() 方法的执行过程。

Bootstrap

daemon.start()

启动过程与初始化类似,均由 Bootstrap 反射调用 Catalina 的 start 方法。

public void start()
    throws Exception {
   
    if( catalinaDaemon==null ) init();

    Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
    method.invoke(catalinaDaemon, (Object [])null);
}

Catalina

public void start() {
   

    if (getServer() == null) {
   
        load();
    }

    if (getServer() == null) {
   
        log.fatal("Cannot start server. Server instance is not configured.");
        return;
    }

    long t1 = System.nanoTime();

    // Start the new server
    try {
   
        //调用Server的start方法,启动Server组件
        getServer().start();
    } catch (LifecycleException e) {
   
        log.fatal(sm.getString("catalina.serverStartFail"), e);
        try {
   
            getServer().destroy();
        } catch (LifecycleException e1) {
   
            log.debug("destroy() failed for failed Server ", e1);
        }
        return;
    }

    long t2 = System.nanoTime();
    if(log.isInfoEnabled()) {
   
        log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
    }

    // Register shutdown hook
    // 注册勾子,用于安全关闭tomcat
    if (useShutdownHook) {
   
        if (shutdownHook == null) {
   
            shutdownHook = new CatalinaShutdownHook();
        }
        Runtime.getRuntime().addShutdownHook(shutdownHook);

        // If JULI is being used, disable JULI's shutdown hook since
        // shutdown hooks run in parallel and log messages may be lost
        // if JULI's hook completes before the CatalinaShutdownHook()
        LogManager logManager = LogManager.getLogManager();
        if (logManager instanceof ClassLoaderLogManager) {
   
            ((ClassLoaderLogManager) logManager).setUseShutdownHook(
                    false);
        }
    }

    // Bootstrap中会设置await为true,其目的在于让tomcat在shutdown端口阻塞监听关闭命令
    if (await) {
   
        await();
        stop();
    }
}

Server

在先前的 Lifecycle 文章中,我们已阐述 StandardServer 重写了 startInternal 方法,并在此基础上实现了自身的启动逻辑。

StandardServer.startInternal

protected void startInternal() throws LifecycleException {
   

    fireLifecycleEvent(CONFIGURE_START_EVENT, null);
    setState(LifecycleState.STARTING);

    globalNamingResources.start();

    // Start our defined Services
    synchronized (servicesLock) {
   
        for (int i = 0; i < services.length; i++) {
   
            services[i].start();
        }
    }
}

在启动过程中,LifecycleBase 首先发出 STARTING_PREP 事件,StandardServer 额外还会发出 CONFIGURE_START_EVENT 和 STARTING 事件,通知 LifecycleListener 在启动前进行准备工作。例如,NamingContextListener 会处理 CONFIGURE_START_EVENT 事件,实例化 Tomcat 相关的上下文以及 ContextResource 资源。

随后,启动 Service 组件,这部分逻辑将在后续文章中详细分析。最后,LifecycleBase 发出 STARTED 事件,完成启动流程。

Service

StandardService 的 start 代码如下所示:

  1. 启动 Engine,Engine 的子容器也会被启动,Web 应用的部署将在这一步骤完成;
  2. 启动 Executor,这是 Tomcat 使用 Lifecycle 封装的线程池,继承自 java.util.concurrent.Executor 以及 Tomcat 的 Lifecycle 接口;
  3. 启动 Connector 组件,Connector 完成 Endpoint 的启动,此时意味着 Tomcat 可以对外提供请求服务。

StandardService.startInternal

protected void startInternal() throws LifecycleException {
   

    setState(LifecycleState.STARTING);

    // 启动Engine
    if (engine != null) {
   
        synchronized (engine) {
   
            engine.start();
        }
    }

    // 启动Executor线程池
    synchronized (executors) {
   
        for (Executor executor: executors) {
   
            executor.start();
        }
    }

    // 启动MapperListener
    mapperListener.start();

    // 启动Connector
    synchronized (connectorsLock) {
   
        for (Connector connector: connectors) {
   
            try {
   
                // If it has already failed, don't try and start it
                if (connector.getState() != LifecycleState.FAILED) {
   
                    connector.start();
                }
            } catch (Exception e) {
   
                // logger......
            }
        }
    }
}

Engine

StandardEngine 的标准实现为 org.apache.catalina.core.StandardEngine。其构造函数的主要职责是使用默认的基础阀门创建 StandardEngine 组件。

/**
 * Create a new StandardEngine component with the default basic Valve.
 */
public StandardEngine() {
   
    super();
    pipeline.setBasic(new StandardEngineValve());
    /* Set the jmvRoute using the system property jvmRoute */
    try {
   
        setJvmRoute(System.getProperty("jvmRoute"));
    } catch(Exception ex) {
   
        log.warn(sm.getString("standardEngine.jvmRouteFail"));
    }
    // By default, the engine will hold the reloading thread
    backgroundProcessorDelay = 10;
}

让我们来深入分析 StandardEngine.startInternal 方法。

StandardEngine.startInternal

@Override
protected synchronized void startInternal() throws LifecycleException {
   

    // Log our server identification information
    if(log.isInfoEnabled())
        log.info( "Starting Servlet Engine: " + ServerInfo.getServerInfo());

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

StandardEngine、StandardHost、StandardContext、StandardWrapper 这几个容器之间存在着父子关系。一个父容器可以包含多个子容器,并且每个子容器都对应一个父容器。Engine 是顶层父容器,它没有父容器。各个组件的包含关系如下图所示:

默认情况下,StandardEngine 只有一个子容器 StandardHost,一个 StandardContext 对应一个 Web 应用,而一个 StandardWrapper 对应一个 Web 应用里面的一个 Servlet。

StandardEngine、StandardHost、StandardContext、StandardWrapper 都是继承自 ContainerBase,各个容器的启动是由父容器调用子容器的 start 方法完成的,也就是说由 StandardEngine 启动 StandardHost,再由 StandardHost 启动 StandardContext,以此类推。

由于它们都继承自 ContainerBase,当调用 start 启动 Container 容器时,首先会执行 ContainerBase 的 start 方法。ContainerBase 会寻找子容器,并在线程池中启动子容器。StandardEngine 也不例外。

ContainerBase

ContainerBase 的 startInternal 方法如下所示,主要分为以下三个步骤:

  1. 启动子容器
  2. 启动 Pipeline,并发出 STARTING 事件
  3. 如果 backgroundProcessorDelay 参数 >= 0,则开启 ContainerBackgroundProcessor 线程,用于调用子容器的 backgroundProcess 方法
protected synchronized void startInternal() throws LifecycleException {
   
    // 省略若干代码......

    // 把子容器的启动步骤放在线程中处理,默认情况下线程池只有一个线程处理任务队列
    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])));
    }

    // 阻塞当前线程,直到子容器start完成
    boolean fail = false;
    for (Future<Void> result : results) {
   
        try {
   
            result.get();
        } catch (Exception e) {
   
            log.error(sm.getString("containerBase.threadedStartFailed"), e);
            fail = true;
        }
    }

    // 启用Pipeline
    if (pipeline instanceof Lifecycle)
        ((Lifecycle) pipeline).start();
    setState(LifecycleState.STARTING);

    // 开启ContainerBackgroundProcessor线程用于调用子容器的backgroundProcess方法,默认情况下backgroundProcessorDelay=-1,不会启用该线程
    threadStart();
}

ContainerBase 会将 StartChild 任务丢给线程池处理,并获取 Future 对象。然后,它会遍历所有 Future,并进行阻塞式的 result.get() 操作,这将异步启动转换为同步启动,只有所有子容器都启动完成才会继续运行。

我们再来看看提交到线程池的 StartChild 任务,它实现了 java.util.concurrent.Callable 接口,并在 call 方法中完成子容器的 start 动作。

private static class StartChild implements Callable<Void> {
   

    private Container child;

    public StartChild(Container child) {
   
        this.child = child;
    }

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

启动 Pipeline

默认使用 StandardPipeline 实现类,它也是一个 Lifecycle。在容器启动时,StandardPipeline 会遍历 Valve 链表,如果 Valve 是 Lifecycle 的子类,则会调用其 start 方法启动 Valve 组件,代码如下:

public class StandardPipeline extends LifecycleBase
        implements Pipeline, Contained {
   


    protected synchronized void startInternal() throws LifecycleException {
   

        Valve current = first;
        if (current == null) {
   
            current = basic;
        }
        while (current != null) {
   
            if (current instanceof Lifecycle)
                ((Lifecycle) current).start();
            current = current.getNext();
        }

        setState(LifecycleState.STARTING);
    }

}

Host

在分析 Host 时,我们可以从 Host 的构造函数入手,该方法主要负责设置基础阀门。

ublic StandardHost() {
   
    super();
    pipeline.setBasic(new StandardHostValve());
}

StandardEngine.startInternal

protected synchronized void startInternal() throws LifecycleException {
   

    // errorValve默认使用org.apache.catalina.valves.ErrorReportValve
    String errorValve = getErrorReportValveClass();
    if ((errorValve != null) && (!errorValve.equals(""))) {
   
        try {
   
            boolean found = false;

            // 如果所有的阀门中已经存在这个实例,则不进行处理,否则添加到  Pipeline 中
            Valve[] valves = getPipeline().getValves();
            for (Valve valve : valves) {
   
                if (errorValve.equals(valve.getClass().getName())) {
   
                    found = true;
                    break;
                }
            }

            // 如果未找到则添加到 Pipeline 中,注意是添加到 basic valve 的前面
            // 默认情况下,first valve 是 AccessLogValve,basic 是 StandardHostValve
            if(!found) {
   
                Valve valve =
                    (Valve) Class.forName(errorValve).getConstructor().newInstance();
                getPipeline().addValve(valve);
            }
        } catch (Throwable t) {
   
            // 处理异常,省略......
        }
    }

    // 调用父类 ContainerBase,完成统一的启动动作
    super.startInternal();
}

StandardHost Pipeline 包含的 Valve 组件:

  1. basic:org.apache.catalina.core.StandardHostValve
  2. first:org.apache.catalina.valves.AccessLogValve

需要注意的是,在往 Pipeline 中添加 Valve 阀门时,是添加到 first 后面,basic 前面。

Context

接下来,让我们深入分析 Context 的实现类 org.apache.catalina.core.StandardContext。

首先,我们来看看构造方法,该方法用于设置 Context.pipeline 的基础阀门。

public StandardContext() {
   
    super();
    pipeline.setBasic(new StandardContextValve());
    broadcaster = new NotificationBroadcasterSupport();
    // Set defaults
    if (!Globals.STRICT_SERVLET_COMPLIANCE) {
   
        // Strict servlet compliance requires all extension mapped servlets
        // to be checked against welcome files
        resourceOnlyServlets.add("jsp");
    }
}

启动方法与之前分析的容器启动方法类似,这里不再赘述。

Wrapper

Wrapper 是一个 Servlet 的包装,我们先来看看构造方法。其主要作用是设置基础阀门 StandardWrapperValve

public StandardWrapper() {
   
    super();
    swValve=new StandardWrapperValve();
    pipeline.setBasic(swValve);
    broadcaster = new NotificationBroadcasterSupport();
}

这里每个容器中的 pipeline 设置的 StandardEngineValve、StandardHostValve、StandardContextValve、StandardWrapperValve 都是非常重要的,它们在 HTTP 请求处理过程中扮演着关键角色,我们将在后续的文章中详细讲解。

结语

至此,整个启动过程便告一段落。整个启动过程由父组件控制子组件的启动,一层层往下传递,直到最后全部启动完成。

如有问题,欢迎微信搜索【码上遇见你】。

免费的Chat GPT可微信搜索【AI贝塔】进行体验,无限使用。

好了,本章节到此告一段落。希望对你有所帮助,祝学习顺利。

相关文章
|
4月前
|
应用服务中间件 容器
从零手写实现 tomcat-07-war 如何解析处理三方的 war 包?
这是一个关于构建Web应用和理解类加载器的文章摘要。作者探讨了如何解析和处理WAR包,使用Netty权威指南系列(BIO, NIO, AIO)作为背景阅读。文章通过一个简单的Web项目实例,展示了项目的目录结构,包括`pom.xml`, `web.xml`和`IndexServlet`。作者还介绍了自定义的`WebAppClassLoader`,它扩展了`URLClassLoader`,用于根据类路径加载非当前项目类。最后提到了一个名为mini-cat的开源项目,它是简易版Tomcat实现,可在GitHub上找到。
|
20天前
|
监控 网络协议 Java
Tomcat源码解析】整体架构组成及核心组件
Tomcat,原名Catalina,是一款优雅轻盈的Web服务器,自4.x版本起扩展了JSP、EL等功能,超越了单纯的Servlet容器范畴。Servlet是Sun公司为Java编程Web应用制定的规范,Tomcat作为Servlet容器,负责构建Request与Response对象,并执行业务逻辑。
Tomcat源码解析】整体架构组成及核心组件
|
14天前
|
前端开发 Java 应用服务中间件
【Tomcat源码分析 】"深入探索:Tomcat 类加载机制揭秘"
本文详细介绍了Java类加载机制及其在Tomcat中的应用。首先回顾了Java默认的类加载器,包括启动类加载器、扩展类加载器和应用程序类加载器,并解释了双亲委派模型的工作原理及其重要性。接着,文章分析了Tomcat为何不能使用默认类加载机制,因为它需要解决多个应用程序共存时的类库版本冲突、资源共享、类库隔离及JSP文件热更新等问题。最后,详细展示了Tomcat独特的类加载器设计,包括Common、Catalina、Shared、WebApp和Jsp类加载器,确保了系统的稳定性和安全性。通过这种设计,Tomcat实现了不同应用程序间的类库隔离与共享,同时支持JSP文件的热插拔。
【Tomcat源码分析 】"深入探索:Tomcat 类加载机制揭秘"
|
15天前
|
设计模式 应用服务中间件 容器
【Tomcat源码分析】Pipeline 与 Valve 的秘密花园
本文深入剖析了Tomcat中的Pipeline和Valve组件。Valve作为请求处理链中的核心组件,通过接口定义了关键方法;ValveBase为其基类,提供了通用实现。Pipeline则作为Valve容器,通过首尾相连的Valve链完成业务处理。StandardPipeline实现了Pipeline接口,提供了详细的Valve管理逻辑。通过对代码的详细分析,揭示了模板方法模式和责任链模式的应用,展示了系统的扩展性和模块化设计。
【Tomcat源码分析】Pipeline 与 Valve 的秘密花园
|
3月前
|
存储 Linux 芯片
【启动】芯片启动过程全解析
【启动】芯片启动过程全解析
83 0
|
17天前
|
设计模式 人工智能 安全
【Tomcat源码分析】生命周期机制 Lifecycle
Tomcat内部通过各种组件协同工作,构建了一个复杂的Web服务器架构。其中,`Lifecycle`机制作为核心,管理组件从创建到销毁的整个生命周期。本文详细解析了Lifecycle的工作原理及其方法,如初始化、启动、停止和销毁等关键步骤,并展示了LifecycleBase类如何通过状态机和模板模式实现这一过程。通过深入理解Lifecycle,我们可以更好地掌握组件生命周期管理,提升系统设计能力。欢迎关注【码上遇见你】获取更多信息,或搜索【AI贝塔】体验免费的Chat GPT。希望本章内容对你有所帮助。
|
1月前
|
网络协议 Java 应用服务中间件
Tomcat源码分析 (一)----- 手撕Java Web服务器需要准备哪些工作
本文探讨了后端开发中Web服务器的重要性,特别是Tomcat框架的地位与作用。通过解析Tomcat的内部机制,文章引导读者理解其复杂性,并提出了一种实践方式——手工构建简易Web服务器,以此加深对Web服务器运作原理的认识。文章还详细介绍了HTTP协议的工作流程,包括请求与响应的具体格式,并通过Socket编程在Java中的应用实例,展示了客户端与服务器间的数据交换过程。最后,通过一个简单的Java Web服务器实现案例,说明了如何处理HTTP请求及响应,强调虽然构建基本的Web服务器相对直接,但诸如Tomcat这样的成熟框架提供了更为丰富和必要的功能。
|
3月前
|
存储 并行计算 算法
深入解析Java并发库(JUC)中的Phaser:原理、应用与源码分析
深入解析Java并发库(JUC)中的Phaser:原理、应用与源码分析
|
3月前
|
Java 应用服务中间件 API
Tomcat处理一个HTTP请求的执行流程的详细解析
Tomcat处理一个HTTP请求的执行流程的详细解析
96 4

推荐镜像

更多