Tomcat源码分析 | 启动过程深度解析

在启动过程中,LifecycleBase 首先发出 STARTING_PREP 事件,Standar
首页 新闻资讯 行业资讯 Tomcat源码分析 | 启动过程深度解析

前言

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

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

图片图片

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

Bootstrap

daemon.start()

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

publicvoidstart()throws Exception {if(catalinaDaemnotallow==null)init();Method method=catalinaDaemon.getClass().getMethod("start",(Class[])null);method.invoke(catalinaDaemon,(Object[])null);}

Catalina

publicvoidstart(){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 servertry {//调用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// 注册勾子,用于安全关闭tomcatif(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 Servicessynchronized(servicesLock){for(inti=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);// 启动Engineif(engine!=null){
        synchronized(engine){engine.start();}
    }// 启动Executor线程池synchronized(executors){for(Executor executor: executors){
            executor.start();}
    }// 启动MapperListenermapperListener.start();// 启动Connectorsynchronized(connectorsLock){for(Connector connector: connectors){
            try {// If it has already failed, don't try and start itif(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.
 */publicStandardEngine(){
    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 threadbackgroundProcessorDelay=10;}

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

StandardEngine.startInternal

@Overrideprotected synchronized void startInternal()throws LifecycleException {// Log our server identification informationif(log.isInfoEnabled())log.info("Starting Servlet Engine: "+ServerInfo.getServerInfo());// Standard container startupsuper.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(inti=0;i<children.length;i++){
        results.add(startStopExecutor.submit(new StartChild(children[i])));}// 阻塞当前线程,直到子容器start完成booleanfail=false;for(Future<Void>result : results){
        try {
            result.get();} catch(Exception e){
            log.error(sm.getString("containerBase.threadedStartFailed"),e);fail=true;}
    }// 启用Pipelineif(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;publicStartChild(Container child){
        this.child=child;}@OverridepublicVoidcall()throws LifecycleException {
        child.start();returnnull;}
}

启动 Pipeline

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

publicclass StandardPipeline extends LifecycleBase
        implements Pipeline,Contained {


    protected synchronized void startInternal()throws LifecycleException {

        Valvecurrent=first;if(current==null){current=basic;}while(current!=null){if(currentinstanceof 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.ErrorReportValveString errorValve=getErrorReportValveClass();if((errorValve!=null)&&(!errorValve.equals(""))){
        try {booleanfound=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 是 StandardHostValveif(!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 的基础阀门。

publicStandardContext(){
    super();pipeline.setBasic(new StandardContextValve());broadcaster=new NotificationBroadcasterSupport();// Set defaultsif(!Globals.STRICT_SERVLET_COMPLIANCE){// Strict servlet compliance requires all extension mapped servlets// to be checked against welcome filesresourceOnlyServlets.add("jsp");}
}

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

Wrapper

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

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

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

结语

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

33    2024-09-11 09:25:03    Tomcat 组件 PREP