为您推荐:
精华内容
最热下载
问答
  • 5星
    165.73MB q6115759 2021-03-11 09:29:13
  • 5星
    8.69MB xiaohua1992 2021-04-12 20:47:00
  • 5星
    185MB csde12 2021-07-12 16:56:02
  • 5星
    8.51MB GZXGYZ 2021-08-16 10:12:36
  • 5星
    9.92MB tangyang8942 2021-07-04 21:07:05
  • 5星
    15.07MB z18223345669 2021-01-11 19:37:48
  • 5星
    24.84MB tangyang8942 2021-06-13 22:04:38
  • 5星
    18.79MB tangyang8942 2021-04-07 16:53:20
  • 5星
    5.16MB weixin_46831482 2021-09-14 15:57:03
  • 5星
    36.05MB tangyang8942 2021-06-12 16:15:06
  • Tomcat组件生命周期管理 在Tomcat总体结构 (Tomcat源代码解析之二)中,我们列出了Tomcat中Server,Service,Connector,Engine,Host,Context的继承关系图,你会发现它们都实现了org.apache.catalina.Lifecycle接口,...

    Tomcat组件生命周期管理

    Tomcat总体结构 (Tomcat源代码解析之二) 中,我们列出了Tomcat中Server,Service,Connector,Engine,Host,Context的继承关系图,你会发现它们都实现了 org.apache.catalina.Lifecycle 接口,而 org.apache.catalina.util.LifecycleBase 采用了 模板方法模式 来对所有支持生命周期管理的组件的生命周期各个阶段进行了总体管理,每个需要生命周期管理的组件只需要继承这个基类,然后覆盖对应的钩子方法即可完成相应的生命周期阶段性的管理工作。 下面我们首先来看看 org.apache.catalina.Lifecycle 接口的定义,它的类图如下图所示:

    从上图我们可以清楚的看到LifeCycle中主要有四个生命周期阶段,它们分别是init(初始化),start(启动),stop(停止),destory(销毁)。知道了这四个生命周期阶段以后,咋们就来看看org.apache.catalina.util.LifecycleBase是如何实现模板方法模式的。 那接下来我们就来看看org.apache.catalina.util.LifecycleBase类的定义,它的类图如下所示:

    上图中用红色标注的四个方法就是模板方法模式中的钩子方法,子类可以通过实现钩子方法来纳入到基类已经流程化好的生命周期管理中。
    上面我们对LifeCycle和LifeCycleBase有了一个总体的认识,接下来,我们通过查看org.apache.catalina.util.LifecycleBase的源代码来具体的分析一下。 咋们首先来看org.apache.catalina.util.LifecycleBase的init方法的实现。
    <span style="color: rgb(102, 102, 102); font-family: 'Open Sans', HelveticaNeue-Light, 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12.6000003814697px; line-height: 25.2000007629395px; text-align: justify; background-color: rgb(236, 236, 236);">org.apache.catalina.util.LifecycleBase#init</span>
    
    @Override
    public final synchronized void init() throws LifecycleException {
            // 1
            if (!state.equals(LifecycleState.NEW)) {
                invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
            }
            setStateInternal(LifecycleState.INITIALIZING, null, false);
    
            try {
                // 2 
                initInternal();
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                setStateInternal(LifecycleState.FAILED, null, false);
                throw new LifecycleException(
                        sm.getString("lifecycleBase.initFail",toString()), t);
            }
            // 3 
            setStateInternal(LifecycleState.INITIALIZED, null, false);
    }
    

    下面我们逐一来分析一下上述代码中标注了数字的地方:

    1. 标注1的代码首先检测当前组件的状态是不是NEW(新建),如果不是就调用org.apache.catalina.util.LifecycleBase#invalidTransition方法来将当前的状态转换过程终止,而invalidTransition的实现是抛出了org.apache.catalina.LifecycleException异常。接着调用了setStateInternal方法将状态设置为INITIALIZING(正在初始化)
    2. 标注2的代码就是init模板方法的钩子,子类可以通过实现protected abstract void initInternal() throws LifecycleException;方法来纳入初始化的流程。
    3. 标注3的代码将组件的状态改为INITIALIZED(已初始化)。

    上面我们分析了init模板方法,接下来我们再看看start方法具体做了什么事情。start的代码如下:

    <span style="color: rgb(102, 102, 102); font-family: 'Open Sans', HelveticaNeue-Light, 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12.6000003814697px; line-height: 25.2000007629395px; text-align: justify; background-color: rgb(236, 236, 236);">org.apache.catalina.util.LifecycleBase#start</span>
    
    public final synchronized void start() throws LifecycleException {
        // 1
        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;
        }
    
        // 2
        if (state.equals(LifecycleState.NEW)) {
            init();
        } else if (state.equals(LifecycleState.FAILED)){
            stop();
        } else if (!state.equals(LifecycleState.INITIALIZED) &&
                !state.equals(LifecycleState.STOPPED)) {
            invalidTransition(Lifecycle.BEFORE_START_EVENT);
        }
        // 3
        setStateInternal(LifecycleState.STARTING_PREP, null, false);
    
        try {
        //4   
            startInternal();
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            setStateInternal(LifecycleState.FAILED, null, false);
            throw new LifecycleException(
                    sm.getString("lifecycleBase.startFail",toString()), t);
        }
    
        // 5
        if (state.equals(LifecycleState.FAILED) ||
                state.equals(LifecycleState.MUST_STOP)) {
            stop();
        } else {
            // Shouldn't be necessary but acts as a check that sub-classes are
            // doing what they are supposed to.
            if (!state.equals(LifecycleState.STARTING)) {
                invalidTransition(Lifecycle.AFTER_START_EVENT);
            }
    
            setStateInternal(LifecycleState.STARTED, null, false);
        }
    }
    

    下面我们逐一来分析一下上述代码中标注了数字的地方:

    1. 标注1的代码检测当前组件的状态是不是STARTING_PREP(准备启动),STARTING(正在启动),STARTED(已启动).如果是这三个状态中的任何一个,则抛出LifecycleException
    2. 标注2的代码的检查其实主要是为了保证组件状态的完整性,在正常启动的流程中,应该是不会出现没有初始化就启动,或者还没启动就已经失败的情况。
    3. 标注3的代码设置组件的状态为STARTING_PREP(准备启动状态)
    4. 标注4的代码是start模板方法的钩子方法,子类通过实现org.apache.catalina.util.LifecycleBase#startInternal这个方法来纳入到组件启动的流程中来。
    5. 标注5的代码做了一些状态检查,然后最终将组件的状态设置为STARTED(已启动)

    上面我们分析了init和start方法的流程,对于stop和destroy方法的总体过程是类似的,大家可以自己阅读一下,但是通过上面的分析,我们可以得出生命周期方法的总体的骨架,如果用伪代码来表示可以简化为如下:


    <span style="color: rgb(102, 102, 102); font-family: 'Open Sans', HelveticaNeue-Light, 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12.6000003814697px; line-height: 25.2000007629395px; text-align: justify; background-color: rgb(236, 236, 236);">org.apache.catalina.util.LifecycleBase#lifeCycleMethod</span>
    
    public final synchronized void lfieCycleMethod() throws LifecycleException {
            stateCheck();//状态检查
            //设置为进入相应的生命周期之前的状态
            setStateInternal(LifecycleState.BEFORE_STATE, null, false);
            lfieCycleMethodInternal();//钩子方法
            //进入相应的生命周期之后的状态
            setStateInternal(LifecycleState.AFTER_STATE, null, false);
        }
    

    Tomcat启动的总过程

    通过上面的介绍,我们总体上清楚了各个组件的生命周期的各个阶段具体都是如何运作的。接下来我们就来看看,Tomcat具体是如何一步步启动起来的。我们都知道任何Java程序都有一个main函数入口,Tomcat中的main入口是org.apache.catalina.startup.Bootstrap#main,下面我们就来分析一下它的代码:

    <span style="color: rgb(102, 102, 102); font-family: 'Open Sans', HelveticaNeue-Light, 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12.6000003814697px; line-height: 25.2000007629395px; text-align: justify; background-color: rgb(236, 236, 236);">org.apache.catalina.startup.Bootstrap#main</span>
    
    public static void main(String args[]) {
    
        if (daemon == null) {
            // Don't set daemon until init() has completed
            // 1 
            Bootstrap bootstrap = new Bootstrap();
            try {
                // 2
                bootstrap.init();
            } catch (Throwable t) {
                handleThrowable(t);
                t.printStackTrace();
                return;
            }
            // 3
            daemon = bootstrap;
        } else {
            // When running as a service the call to stop will be on a new
            // thread so make sure the correct class loader is used to prevent
            // a range of class not found exceptions.
            Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
        }
    
        try {
            String command = "start";
            if (args.length > 0) {
                command = args[args.length - 1];
            }
    
            if (command.equals("startd")) {
                args[args.length - 1] = "start";
                daemon.load(args);
                daemon.start();
            } else if (command.equals("stopd")) {
                args[args.length - 1] = "stop";
                daemon.stop();
            } else if (command.equals("start")) {
                // 4
                daemon.setAwait(true);
                daemon.load(args);
                daemon.start();
            } else if (command.equals("stop")) {
                daemon.stopServer(args);
            } else if (command.equals("configtest")) {
                daemon.load(args);
                if (null==daemon.getServer()) {
                    System.exit(1);
                }
                System.exit(0);
            } else {
                log.warn("Bootstrap: command \"" + command + "\" does not exist.");
            }
        } catch (Throwable t) {
            // Unwrap the Exception for clearer error reporting
            if (t instanceof InvocationTargetException &&
                    t.getCause() != null) {
                t = t.getCause();
            }
            handleThrowable(t);
            t.printStackTrace();
            System.exit(1);
        }
    
    }
    

    下面我们逐一来分析一下上述代码中标注了数字的地方:

    1. 标注1的代码初始化了自举类的实例,标注2的代码对BootStrap实例进行了初始化,标注3的代码将实例赋值给了daemon。
    2. 标注4的代码首先调用了BootStrap的load方法,然后调用了start方法。

    接下来我们分别分析一下BootStrap的init,load,start方法具体做了哪些工作。

    BootStrap#init方法

    首先来看org.apache.catalina.startup.Bootstrap#init方法,它的代码如下:

    public void init()throws Exception{
    
        // Set Catalina path
        setCatalinaHome();
        setCatalinaBase();
    
        initClassLoaders();
    
        Thread.currentThread().setContextClassLoader(catalinaLoader);
    
        SecurityClassLoad.securityClassLoad(catalinaLoader);
    
        // Load our startup class and call its process() method
        if (log.isDebugEnabled())
            log.debug("Loading startup class");
        // 1
        Class<?> startupClass =
            catalinaLoader.loadClass
            ("org.apache.catalina.startup.Catalina");
        Object startupInstance = startupClass.newInstance();
    
        // Set the shared extensions class loader
        if (log.isDebugEnabled())
            log.debug("Setting startup class properties");
        String methodName = "setParentClassLoader";
        Class<?> paramTypes[] = new Class[1];
        paramTypes[0] = Class.forName("java.lang.ClassLoader");
        Object paramValues[] = new Object[1];
        paramValues[0] = sharedLoader;
        Method method =
            startupInstance.getClass().getMethod(methodName, paramTypes);
        // 2
        method.invoke(startupInstance, paramValues);
        // 3
        catalinaDaemon = startupInstance;
    
    }
    

    下面我们重点逐一来分析一下上述代码中标注了数字的地方:

    1. 标注1的代码通过反射实例化了org.apache.catalina.startup.Catalina类的实例;
    2. 标注2的代码调用了Catalina实例的setParentClassLoader方法设置了父亲ClassLoader,对于ClassLoader方面的内容,我们在本系列的后续文章再来看看。标注3的代码将Catalina实例赋值给了Bootstrap实例的catalinaDaemon.

    BootStrap#load

    接下来我们再来看看org.apache.catalina.startup.Bootstrap#load方法,通过查看源代码,我们知道此方法通过反射调用了org.apache.catalina.startup.Catalina#load方法,那我们就来看看Catalina的load方法,Catalina#load方法代码如下:


    public void load() {
    
        // 1 
        Digester digester = createStartDigester();
    
        InputSource inputSource = null;
        InputStream inputStream = null;
        File file = null;
        try {
            file = configFile();
            inputStream = new FileInputStream(file);
            inputSource = new InputSource(file.toURI().toURL().toString());
        } catch (Exception e) {
            if (log.isDebugEnabled()) {
                log.debug(sm.getString("catalina.configFail", file), e);
            }
        }
    
    
    
        try {
            inputSource.setByteStream(inputStream);
            digester.push(this);
            digester.parse(inputSource);
        } catch (SAXParseException spe) {
            log.warn("Catalina.start using " + getConfigFile() + ": " +
                    spe.getMessage());
            return;
        } catch (Exception e) {
            log.warn("Catalina.start using " + getConfigFile() + ": " , e);
            return;
        } finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                // Ignore
            }
        }
    
    
        getServer().setCatalina(this);
    
        // Stream redirection
        initStreams();
    
        // Start the new server
        try {
            // 2
            getServer().init();
        } catch (LifecycleException e) {
            if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
                throw new java.lang.Error(e);
            } else {
                log.error("Catalina.start", e);
            }
    
        }
    
    }
    

    上面的代码,我只保留了主流程核心的代码,下面我们重点逐一来分析一下上述代码中标注了数字的地方:

    1. 标注1的代码创建Digester实例解析”conf/server.xml”文件
    2. 标注2的代码最终调用了StandardServer的init方法。

    大家可以自行查看下源代码,我们会发现如下的一个调用流程:

    org.apache.catalina.core.StandardServer#init
    ->org.apache.catalina.core.StandardService#init
    -->org.apache.catalina.connector.Connector#init
    -->org.apache.catalina.core.StandardEngine#init

    因为StandardService,Connector,StandardEngine实现了LifeCycle接口,因此符合我们上文所获的生命周期的管理,最终都是通过他们自己实现的initInternal方法进行初始化

    读到这里的时候,我想大家应该和我一样,以为StandardEngine#init方法会调用StandardHost#init方法,但是当我们查看StandardEngine#init方法的时候,发现并没有进行StandardHost的初始化,它到底做了什么呢?让我们来具体分析一下,我们首先拿StanderEngine的继承关系图来看下:




    通过上图以及前面说的LifeCyecle的模板方法模式,我们知道StandardEngine的初始化钩子方法initInternal方法最终调用了ContainerBase的initInternal方法,那我们拿ContainerBase#initInternal方法的代码看看:
    protected void initInternal() throws LifecycleException {
        BlockingQueue<Runnable> startStopQueue =
            new LinkedBlockingQueue<Runnable>();
        startStopExecutor = new ThreadPoolExecutor(
                getStartStopThreadsInternal(),
                getStartStopThreadsInternal(), 10, TimeUnit.SECONDS,
                startStopQueue,
                new StartStopThreadFactory(getName() + "-startStop-"));
        startStopExecutor.allowCoreThreadTimeOut(true);
        super.initInternal();
    }
    我们可以看到StandardEngine的初始化仅仅是创建了一个ThreadPoolExecutor,当看到这里的时候,笔者当时也纳闷了,StandardEngine#init竟然没有调用StandardHost#init方法,那么StandardHost的init方法是什么时候被调用的呢?遇到这种不知道到底方法怎么调用的时候怎么办呢?笔者介绍个方法给大家。我们现在需要知道StandardHost#init方法何时被调用的,而我们知道init最终会调用钩子的initInternal方法,因此这个时候,我们可以在StandardHost中override initInternal方法,增加了实现方法以后,有两种方法可以用,一种就是设置个断点debug一下就可以看出线程调用栈了,另外一种就是在新增的方法中打印出调用栈。笔者这里采用第二种方法,我们增加如下的initInternal方法到StandardHost中:
    protected void initInternal() throws LifecycleException {
        Throwable ex = new Throwable();
        StackTraceElement[] stackElements = ex.getStackTrace();
        if (stackElements != null) {
            for (int i = stackElements.length - 1; i >= 0; i--) {
                System.out.print(stackElements[i].getClassName() + "\t");
                System.out.print(stackElements[i].getMethodName() + "\t");
                System.out.print(stackElements[i].getFileName() + "\t");
                System.out.println(stackElements[i].getLineNumber());
            }
        }
        super.initInternal();
    }
    上面的代码将会打印出方法调用堆栈,对于调试非常有用,上面的方法运行以后在控制台打印出了如下的堆栈信息:
    java.lang.Thread    run  Thread.java   680
    java.util.concurrent.ThreadPoolExecutor$Worker run  ThreadPoolExecutor.java   918
    java.util.concurrent.ThreadPoolExecutor$Worker runTask  ThreadPoolExecutor.java   895
    java.util.concurrent.FutureTask   run  FutureTask.java   138
    java.util.concurrent.FutureTask$Sync   innerRun FutureTask.java   303
    org.apache.catalina.core.ContainerBase$StartChild   call ContainerBase.java    1549
    org.apache.catalina.core.ContainerBase$StartChild   call ContainerBase.java    1559
    org.apache.catalina.util.LifecycleBase start    LifecycleBase.java    139
    org.apache.catalina.util.LifecycleBase init LifecycleBase.java    102
    org.apache.catalina.core.StandardHost  initInternal StandardHost.java 794
    

    通过控制台的信息,我们看到是StartChild#call方法调用的,而我们查看StartChild#call方法其实是在StandardEngine的startInternal方法中通过异步线程池去初始化子容器。因此到这里我们就理清楚了,StarndardHost的init方法是在调用start方法的时候被初始化。那么接下来我们就来看看,start方法的整体调用流程。

    BootStrap#start

    采用分析load方法一样的方法,经过对BootStrap#start的分析,我们最终可以得到得到如下的调用链:


    org.apache.catalina.startup.Bootstrap#start
    ->org.apache.catalina.startup.Catalina#start 通过反射调用
    -->org.apache.catalina.core.StandardServer#start
    --->org.apache.catalina.core.StandardService#start
    ---->org.apache.catalina.core.StandardEngine#start
    ---->org.apache.catalina.Executor#start
    ---->org.apache.catalina.connector.Connector#start

    综合上文的描述我们总体得到如下的调用链:

    <span style="color: rgb(102, 102, 102); font-family: 'Open Sans', HelveticaNeue-Light, 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12.6000003814697px; line-height: 25.2000007629395px; text-align: justify; background-color: rgb(236, 236, 236);">org.apache.catalina.startup.Bootstrap#main call stack</span>
    
    org.apache.catalina.startup.Bootstrap#main
    ->org.apache.catalina.startup.Bootstrap#init
    ->org.apache.catalina.startup.Bootstrap#load
    -->org.apache.catalina.startup.Catalina#load
    --->org.apache.catalina.core.StandardServer#init
    ---->org.apache.catalina.core.StandardService#init
    ----->org.apache.catalina.connector.Connector#init
    ----->org.apache.catalina.core.StandardEngine#init
    ->org.apache.catalina.startup.Bootstrap#start
    -->org.apache.catalina.startup.Catalina#start 通过反射调用
    --->org.apache.catalina.core.StandardServer#start
    ---->org.apache.catalina.core.StandardService#start
    ----->org.apache.catalina.core.StandardEngine#start
    ----->org.apache.catalina.Executor#start
    ----->org.apache.catalina.connector.Connector#start
    通过上面的分析我们已经搞清楚了Tomcat启动的总体的过程,但是有一些关键的步骤,我们还需要进行进一步的深入探究。let’s do it.

    Tomcat启动过程关键步骤分析

    Connector#init

    我们首先来看一下org.apache.catalina.connector.Connector#init,我们知道Connector的生命周期也是通过LifeCycle的模板方法模式来管理的,那么我们只需要查看一下它的initInternal方法即可知道它是如何初始化的。接下来我们就来看一下initInternal方法,代码如下:

    <span style="color: rgb(102, 102, 102); font-family: 'Open Sans', HelveticaNeue-Light, 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12.6000003814697px; line-height: 25.2000007629395px; text-align: justify; background-color: rgb(236, 236, 236);">org.apache.catalina.connector.Connector#initInternal</span>
    
    protected void initInternal() throws LifecycleException {
    
            super.initInternal();
    
            // Initialize adapter
            adapter = new CoyoteAdapter(this);
            protocolHandler.setAdapter(adapter);
    
            // Make sure parseBodyMethodsSet has a default
            if( null == parseBodyMethodsSet ) {
                setParseBodyMethods(getParseBodyMethods());
            }
    
            if (protocolHandler.isAprRequired() &&
                    !AprLifecycleListener.isAprAvailable()) {
                throw new LifecycleException(
                        sm.getString("coyoteConnector.protocolHandlerNoApr",
                                getProtocolHandlerClassName()));
            }
    
            try {
                // 1 
                protocolHandler.init();
            } catch (Exception e) {
                throw new LifecycleException
                    (sm.getString
                     ("coyoteConnector.protocolHandlerInitializationFailed"), e);
            }
    
            // Initialize mapper listener
            mapperListener.init();
        }
    

    上面代码中,本文最关心的是标注了1的地方,这个地方调用了 org.apache.coyote.ProtocolHandler#init 方法,而ProtocolHandler是在Connector的构造函数中初始化,而Connector的构造函数又是Digester类解析conf/server.xml的时候调用的,明白了这点,我们在来具体看看Connector构造函数中调用的一个核心的方法setProtocol方法,下面是其代码:
    <span style="color: rgb(102, 102, 102); font-family: 'Open Sans', HelveticaNeue-Light, 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12.6000003814697px; line-height: 25.2000007629395px; text-align: justify; background-color: rgb(236, 236, 236);">org.apache.catalina.connector.Connector#setProtocol</span>
    
    public void setProtocol(String protocol) {
    
            if (AprLifecycleListener.isAprAvailable()) {
                //这里统一使用AprEndpoint
                if ("HTTP/1.1".equals(protocol)) {
                    setProtocolHandlerClassName
                        ("org.apache.coyote.http11.Http11AprProtocol");   //Http11AprProtocol$Http11ConnectionHandler
                } else if ("AJP/1.3".equals(protocol)) {
                    setProtocolHandlerClassName
                        ("org.apache.coyote.ajp.AjpAprProtocol");     //AjpAprProtocol$AjpConnectionHandler
                } else if (protocol != null) {
                    setProtocolHandlerClassName(protocol);
                } else {
                    setProtocolHandlerClassName
                        ("org.apache.coyote.http11.Http11AprProtocol");
                }
            } else {
                // 1 
                if ("HTTP/1.1".equals(protocol)) {
                    setProtocolHandlerClassName
                        ("org.apache.coyote.http11.Http11Protocol");  //Http11Protocol$Http11ConnectionHandler
                } else if ("AJP/1.3".equals(protocol)) {
                    setProtocolHandlerClassName
                        ("org.apache.coyote.ajp.AjpProtocol");    //AjpProtocol$AjpConnectionHandler
                } else if (protocol != null) {
                    setProtocolHandlerClassName(protocol);
                }
            }
    }

    从setProtocol的代码中,我们可以看出主要逻辑分为了两块,一种情况是使用 APR(Apache Portable Runtime) ,另外一种是不使用APR的情况。缺省情况下不采用APR库,这样的话,代码会走到标注1的代码分支,这里通过协议的不同,最终初始化了不同的类。如果是http1.1协议就采用 org.apache.coyote.http11.Http11Protocol ,如果是AJP(Apache Jserv Protocol)协议,就采用 org.apache.coyote.ajp.AjpProtocol 类,下面我们来看一下Http11Protocol和AjpProtocol的继承关系图如下:

    通过上图我们可以看到它们都继承了公共的基类org.apache.coyote.AbstractProtocol,而它们自己的init方法最终其实都是调用了AbstractProtocol的init方法,通过查看AbstractProtocol#init代码,我们可以看到最终是调用了org.apache.tomcat.util.net.AbstractEndpoint#init,而AbstractEndpoint的实例化操作是在实例化AjpProtocol和Http11Protocol的时候在其构造函数中实例化的,而AjpProtocol和Http11Protocol构造函数中,其实都是初始化了org.apache.tomcat.util.net.JIoEndpoint类,只不过根据是http协议还是AJP协议,它们具有不同的连接处理类。其中Http11Protocol的连接处理类为org.apache.coyote.http11.Http11Protocol.Http11ConnectionHandler,而连接处理类为org.apache.coyote.ajp.AjpProtocol.AjpConnectionHandler,因此到这里我们基本清楚了Connector的初始化流程,总结如下:
    <span style="color: rgb(102, 102, 102); font-family: 'Open Sans', HelveticaNeue-Light, 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12.6000003814697px; line-height: 25.2000007629395px; text-align: justify; background-color: rgb(236, 236, 236);">Connect init 采用APR的情况</span>
    
    //1 HTTP/1.1协议连接器
    org.apache.catalina.connector.Connector#init
    ->org.apache.coyote.http11.Http11AprProtocol#init
    -->org.apache.tomcat.util.net.AprEndpoint#init
    (org.apache.coyote.http11.Http11AprProtocol.Http11ConnectionHandler)
    
    
    // 2 AJP/1.3协议连接器
    org.apache.catalina.connector.Connector#init
    ->org.apache.coyote.ajp.AjpAprProtocol#init
    -->org.apache.tomcat.util.net.AprEndpoint#init
    (org.apache.coyote.ajp.AjpAprProtocol.AjpConnectionHandler)
    <pre name="code" class="java" style="color: rgb(102, 102, 102); font-size: 14px; line-height: 28px; text-align: justify;"><span style="font-family: 'Open Sans', HelveticaNeue-Light, 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12.6000003814697px; line-height: 25.2000007629395px; background-color: rgb(236, 236, 236);">Connector init 不采用APR的情况</span>
    
    <span style="font-family: 'Open Sans', HelveticaNeue-Light, 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12.6000003814697px; line-height: 25.2000007629395px; background-color: rgb(236, 236, 236);"></span><pre style="margin-top: 0px; margin-bottom: 0px; padding: 0px; border: none; font-family: Menlo, Monaco, 'Andale Mono', 'lucida console', 'Courier New', monospace; line-height: 1.5; overflow-x: auto; font-size: 14px; background: none rgb(255, 255, 255);"><pre name="code" class="java">// 1 HTTP/1.1协议连接器
    org.apache.catalina.connector.Connector#init
    ->org.apache.coyote.http11.Http11Protocol#init
    -->org.apache.tomcat.util.net.JIoEndpoint#init
    (org.apache.coyote.http11.Http11Protocol.Http11ConnectionHandler)
    
    // 2 AJP/1.3协议连接器
    org.apache.catalina.connector.Connector#init
    ->org.apache.coyote.ajp.AjpProtocol#init
    -->org.apache.tomcat.util.net.JIoEndpoint#init
    (org.apache.coyote.ajp.AjpProtocol.AjpConnectionHandler)

    这里需要注意,除了JIoEndpoint外,还有NIoEndpoint,对于Tomcat7.0.24的代码,并没有采用NIOEndPoint,NIOEndpoint采用了NIO的方式进行Socket的处理。

    最后,咋们再来看看org.apache.tomcat.util.net.JIoEndpoint#init的初始化过程,我们首先来看一下JIoEndpoint的继承关系图如下:



     
     
     
     
     
     
     
     通过上图我们知道JIoEndpoint继承了AbstractEndpoint,而通过查看源码可知,JIoEndpoint没有实现自己的init方法,它默认采用了父类的init方法,那么我们就来看看AbstractEndpoint的init,它的代码如下:
    
    public final void init() throws Exception {
            if (bindOnInit) {
                bind();
                bindState = BindState.BOUND_ON_INIT;
            }
    }
    通过查看上面的代码可知,因为bindOnInit默认是true,所以init调用了bind方法,而bind方法是抽象方法,最终由JIoEndpoint来实现,代码如下:
    @Override
    public void bind() throws Exception {
    
            // Initialize thread count defaults for acceptor
            if (acceptorThreadCount == 0) {
                acceptorThreadCount = 1;
            }
            // Initialize maxConnections
            if (getMaxConnections() == 0) {
                // User hasn't set a value - use the default
                setMaxConnections(getMaxThreadsExecutor(true));
            }
    
            if (serverSocketFactory == null) {
                if (isSSLEnabled()) {
                    serverSocketFactory =
                        handler.getSslImplementation().getServerSocketFactory(this);
                } else {
                    serverSocketFactory = new DefaultServerSocketFactory(this);
                }
            }
    
            if (serverSocket == null) {
                try {
                    if (getAddress() == null) {
                        serverSocket = serverSocketFactory.createSocket(getPort(),
                                getBacklog());
                    } else {
                        serverSocket = serverSocketFactory.createSocket(getPort(),
                                getBacklog(), getAddress());
                    }
                } catch (BindException orig) {
                    String msg;
                    if (getAddress() == null)
                        msg = orig.getMessage() + " <null>:" + getPort();
                    else
                        msg = orig.getMessage() + " " +
                                getAddress().toString() + ":" + getPort();
                    BindException be = new BindException(msg);
                    be.initCause(orig);
                    throw be;
                }
            }
    
    }
    

    通过上面代码可以看出,最终是调用了org.apache.tomcat.util.net.ServerSocketFactory#createSocket方法创建一个java.net.ServerSocket,并绑定在conf/server.xml中Connector中配置的端口。

    综上我们可以得出如下结论:

    Connector#init的时候,无论是AJP还是HTTP最终其实是调用了JioEndpoint的初始化,默认情况在初始化的时候就会创建java.net.ServerSocket绑到到配置的端口上。

    Connector#start

    接着我们再来分析一下Connector#start,因为Connector符合LifeCycle模板方法生命周期管理的机制,因此它的start最终会调用startInternal,org.apache.catalina.connector.Connector#startInternal代码如下:

    protected void startInternal() throws LifecycleException {
    
        // Validate settings before starting
        if (getPort() < 0) {
            throw new LifecycleException(sm.getString(
                    "coyoteConnector.invalidPort", Integer.valueOf(getPort())));
        }
    
        setState(LifecycleState.STARTING);
    
        try {
            protocolHandler.start();
        } catch (Exception e) {
            String errPrefix = "";
            if(this.service != null) {
                errPrefix += "service.getName(): \"" + this.service.getName() + "\"; ";
            }
    
            throw new LifecycleException
                (errPrefix + " " + sm.getString
                 ("coyoteConnector.protocolHandlerStartFailed"), e);
        }
    
        mapperListener.start();
    }
    

    通过上面的代码,我们可以清晰的看到最终调用了protocolHandler.start(),而根据Connector#init流程的分析,这里会分是否采用APR,默认是不采用APR的,这里会根据不同的协议(AJP,HTTP)来调用对应的org.apache.coyote.ProtocolHandler#start. 其中AJP会采用org.apache.coyote.ajp.AjpProtocol,HTTP协议采用org.apache.coyote.http11.Http11Protocol,而无论是AjpProtocol还是Http11Protocol都会调用JIoEndpoint的方法,那么接下来我们就来看看JioEndpoint的start方法,它的代码如下:

    <span style="color: rgb(102, 102, 102); font-family: 'Open Sans', HelveticaNeue-Light, 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12.6000003814697px; line-height: 25.2000007629395px; text-align: justify; background-color: rgb(236, 236, 236);">org.apache.tomcat.util.net.JIoEndpoint#startInternal</span>
    
    public void startInternal() throws Exception {
    
            if (!running) {
                running = true;
                paused = false;
    
                // Create worker collection
                if (getExecutor() == null) {
                    createExecutor();
                }
    
                initializeConnectionLatch();
    
                startAcceptorThreads();
    
                // Start async timeout thread
                Thread timeoutThread = new Thread(new AsyncTimeout(),
                        getName() + "-AsyncTimeout");
                timeoutThread.setPriority(threadPriority);
                timeoutThread.setDaemon(true);
                timeoutThread.start();
            }
    }
    
    从上面的代码可以看出,启动了Acceptor线程和AsyncTimeout线程,首先来看看Acceptor线程,我们再来看看startAcceptorThreads方法,代码如下:
    <span style="color: rgb(102, 102, 102); font-family: 'Open Sans', HelveticaNeue-Light, 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12.6000003814697px; line-height: 25.2000007629395px; text-align: justify; background-color: rgb(236, 236, 236);">org.apache.tomcat.util.net.AbstractEndpoint#startAcceptorThreads</span>
    
    protected final void startAcceptorThreads() {
            int count = getAcceptorThreadCount();
            acceptors = new Acceptor[count];
    
            for (int i = 0; i < count; i++) {
                acceptors[i] = createAcceptor();
                String threadName = getName() + "-Acceptor-" + i;
                acceptors[i].setThreadName(threadName);
                Thread t = new Thread(acceptors[i], threadName);
                t.setPriority(getAcceptorThreadPriority());
                t.setDaemon(getDaemon());
                t.start();
            }
    }

    通过上面的代码,我们可以看出其实是通过org.apache.tomcat.util.net.AbstractEndpoint.Acceptor这个Runable接口的实现类来启动线程,接下来我们就来看看Acceptor#run方法,通过查看run方法,它里面其实就是调用了java.net.ServerSocket#accept的方法来接受一个Socket连接。

    启动完了Acceptor线程以后,接着就会启动AsyncTimeout线程,而这里面需要注意的时候,无论是Acceptor还是AsyncTimeout线程,它们都是Daemon线程,而设置为Daemon的原因,我们会在下篇Tomcat的关闭中进行说明。

    StandardEngine#start

    从本文上面的分析中,我们得知StandardEngine继承了ContainerBase,而StandardEngine的startInternal钩子方法也仅仅是调用了父类ContainerBase的startInternal方法,那接下来我们分析一下ContainerBase的startInternal方法,代码如下:


    <span style="color: rgb(102, 102, 102); font-family: 'Open Sans', HelveticaNeue-Light, 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12.6000003814697px; line-height: 25.2000007629395px; text-align: justify; background-color: rgb(236, 236, 236);">org.apache.catalina.core.ContainerBase#startInternal</span>
    
    protected synchronized void startInternal() throws LifecycleException {
    
        // Start our child containers, if any
        Container children[] = findChildren();
        List<Future<Void>> results = new ArrayList<Future<Void>>();
        for (int i = 0; i < children.length; i++) {
            results.add(startStopExecutor.submit(new StartChild(children[i])));
        }
    
        boolean fail = false;
        for (Future<Void> result : results) {
            try {
                result.get();
            } catch (Exception e) {
                log.error(sm.getString("containerBase.threadedStartFailed"), e);
                fail = true;
            }
    
        }
        if (fail) {
            throw new LifecycleException(
                    sm.getString("containerBase.threadedStartFailed"));
        }
    
    
        setState(LifecycleState.STARTING);
    
    }
    

    我们删除了对本文的分析不相关的代码,只留下一些核心的代码,我们可以看到通过startStopExecutor异步的对子容器进行了启动,然后设置状态为 STARTING 的状态。而startStopExecutor是在容器的initInternal方法中进行初始化好的,接下来我们就来看看StartChild,StardChild的代码如下:
    <span style="color: rgb(102, 102, 102); font-family: 'Open Sans', HelveticaNeue-Light, 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12.6000003814697px; line-height: 25.2000007629395px; text-align: justify; background-color: rgb(236, 236, 236);">org.apache.catalina.core.ContainerBase.StartChild</span>
    
    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;
            }
    }

    通过上面的代码,我们可以看到StartChild实现了Callable接口,实现这个接口的类可以将其放到对应的executor中执行(对于executor不熟悉的童鞋可以去看一下相关的文章,本文不做介绍),StartChild在运行的时候就会调用到子容器的start方法,而此时的父容器是StandardEngine,子容器就是StandardHost,接下来我们就来看看StandardHost的启动过程。通过前面对于init流程的分析,我们知道StandardHost不是在StandardEngine#init的时候初始化,因此在执行StandardHost#start的时候,要首先进行init方法的调用,具体的代码如下:
    <span style="color: rgb(102, 102, 102); font-family: 'Open Sans', HelveticaNeue-Light, 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12.6000003814697px; line-height: 25.2000007629395px; text-align: justify; background-color: rgb(236, 236, 236);">org.apache.catalina.util.LifecycleBase#start</span>
    
    public final synchronized void start() throws LifecycleException {
    
    
        if (state.equals(LifecycleState.NEW)) {
            init(); //因为此时的StandardHost还没有初始化,因此会走到这一步代码
        } else if (state.equals(LifecycleState.FAILED)){
            stop();
        } else if (!state.equals(LifecycleState.INITIALIZED) &&
                !state.equals(LifecycleState.STOPPED)) {
            invalidTransition(Lifecycle.BEFORE_START_EVENT);
        }
    
        setStateInternal(LifecycleState.STARTING_PREP, null, false);
    
        try {
            startInternal();
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            setStateInternal(LifecycleState.FAILED, null, false);
            throw new LifecycleException(
                    sm.getString("lifecycleBase.startFail",toString()), t);
        }
    
       setStateInternal(LifecycleState.STARTED, null, false);
    
    }

    上面省略了部分不相关的代码,在调用中我们可以清楚的看到,对于StandardHost的初始化,是在start的时候进行的。那接下来我们在来看一下StandardHost的init方法,通过查看代码,我们发现StandardHost本身没有实现initInternal的钩子方法,也就意味着最终初始化会调用ContainerBase#initInternal方法,而通过上文的描述,我们已经清楚ContainerBase#initInternal主要是初始化了一个startStopExecutor,这个线程池主要是为了异步的初始化子容器来用的。

    我们知道StandardEngine初始化的时候,也是初始化了一个线程池,而StandardHost也初始化了一个线程池,他们的不同点在与创建线程的工厂方法不同,在采用缺省配置的情况下,StandardEngine的线程池中的线程是以Catalina-startStop的形式命名的,而StandardHost是以localhost-startStop的方式进行命名的。大家注意区分。

    StandardHost#start调用init方法初始化完StandardHost以后,会调用钩子的startInternal方法,而startInternal方法又是调用了ContainerBased#startInternal方法,而ContainerBase#startInternal方法最终又会去启动子容器的,对于StandardHost来说,子容器就是StandardContext。 因此分析到这里我们可以得出如下结论:

    对于StandardEngine,StandardHost的启动,父容器在init的时候创建一个启动和停止子容器的线程池,然后父容器启动的时候首先通过异步的方式将子容器的启动通过org.apache.catalina.core.ContainerBase.StartChild提交到父容器中对应的线程池中进行启动,而子容器启动的时候首先会初始化,然后再启动。

    另外这里还需要注意一点就是,StandEngine#start的时候,最终调用了ContainerBase#startInternal方法,而ContainerBase#startInternal的最后,调用了threadStart(),我们来看看它的代码如下:


    <span style="color: rgb(102, 102, 102); font-family: 'Open Sans', HelveticaNeue-Light, 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12.6000003814697px; line-height: 25.2000007629395px; text-align: justify; background-color: rgb(236, 236, 236);">org.apache.catalina.core.ContainerBase#threadStart</span>
    
    protected void threadStart() {
    
            if (thread != null)
                return;
            if (backgroundProcessorDelay <= 0)
                return;
    
            threadDone = false;
            String threadName = "ContainerBackgroundProcessor[" + toString() + "]";
            thread = new Thread(new ContainerBackgroundProcessor(), threadName);
            thread.setDaemon(true);
            thread.start();
    
    }
    

    上面的代码,首先会判断backgroundProcessorDelay是否小于0,而这个值默认情况下是-1,也就意味这后面的代码不会运行,而对于StandardEngine来说,它将backgroundProcessorDelay的值在构造函数中赋值为了10,这样的话,当StandardEngine启动的时候,就会启动名称为“ContainerBackgroundProcessor[StandardEngine[Catalina]]”的线程。

    经过上面的分析,我们已经清楚了StandardEngine启动的过程了,但是我们还有一个地方需要进一步的分析。因为上面的分析我们仅仅只是分析了容器通过conf/server.xml配置文件的配置结构进行的启动,而我们都知道CATALINA-HOME/webapps/中的应用也是需要启动的,那么webapps目录的应用又是如何启动的呢?我们下面来分析一下,通过Tomcat总体结构的描述,我们已经知道,webapps目录下面的应用其实是属于Context的,而Context对应Tomcat中的StandardContext类,因此我们就知道应该对谁下手了,知道了目标以后,咋们还是采用之前的那种方式,要么debug,要么打印调用栈,这里我们还是通过打印调用栈的方式进行,我们在org.apache.catalina.core.StandardContext#initInternal中增加打印调用栈的方法,具体代码如下:


    protected void initInternal() throws LifecycleException {
            super.initInternal();
            Throwable ex = new Throwable();
            StackTraceElement[] stackElements = ex.getStackTrace();
            if (stackElements != null) {
                for (int i = stackElements.length - 1; i >= 0; i--) {
                    System.out.print(stackElements[i].getClassName() + "\t");
                    System.out.print(stackElements[i].getMethodName() + "\t");
                    System.out.print(stackElements[i].getFileName() + "\t");
                    System.out.println(stackElements[i].getLineNumber());
                }
            }
            if (processTlds) {
                this.addLifecycleListener(new TldConfig());
            }
    
            // Register the naming resources
            if (namingResources != null) {
                namingResources.init();
            }
    
            // Send j2ee.object.created notification 
            if (this.getObjectName() != null) {
                Notification notification = new Notification("j2ee.object.created",
                        this.getObjectName(), sequenceNumber.getAndIncrement());
                broadcaster.sendNotification(notification);
            }
    }
    
    运行代码,可以看到控制台有如下的输出:
    java.util.concurrent.ThreadPoolExecutor$Worker    run  ThreadPoolExecutor.java   918
    java.util.concurrent.ThreadPoolExecutor$Worker runTask  ThreadPoolExecutor.java   895
    java.util.concurrent.FutureTask   run  FutureTask.java   138
    java.util.concurrent.FutureTask$Sync   innerRun FutureTask.java   303
    java.util.concurrent.Executors$RunnableAdapter call Executors.java    439
    org.apache.catalina.startup.HostConfig$DeployDirectory  run  HostConfig.java   1671
    org.apache.catalina.startup.HostConfig deployDirectory  HostConfig.java   1113
    org.apache.catalina.core.StandardHost  addChild StandardHost.java 622
    org.apache.catalina.core.ContainerBase addChild ContainerBase.java    877
    org.apache.catalina.core.ContainerBase addChildInternal ContainerBase.java    901
    org.apache.catalina.util.LifecycleBase start    LifecycleBase.java    139
    org.apache.catalina.util.LifecycleBase init LifecycleBase.java    102
    org.apache.catalina.core.StandardContext   initInternal StandardContext.java  6449
    
    通过查看控制台的输出,我们可以看到有一个 org.apache.catalina.startup.HostConfig$DeployDirectory 类,于是乎找到这个类去看看呗。打开一看它是一个Runable接口的实现类,因此我们推断它也是放到某个线程池中进行异步运行的,最终通过IntellIJ IDEA提供的类调用栈分析工具(ctrl+alt+h)得到DeployDirectory构造器方法的调用栈如下图所示:
    通过上图我们可以清楚的看到,最终的调用方是org.apache.catalina.startup.HostConfig#lifecycleEvent,到这里我们就知道了Context的启动是通过某个组件的生命周期事件的监听器来启动的,而HostConfig到底是谁的监听器呢?通过名称我们应该可以猜测出它是StandardHost的监听器,那么它到底监听哪个事件呢?我们查看下org.apache.catalina.startup.HostConfig#lifecycleEvent的代码如下:
    public void lifecycleEvent(LifecycleEvent event) {
    
            // Identify the host we are associated with
            try {
                host = (Host) event.getLifecycle();
                if (host instanceof StandardHost) {
                    setCopyXML(((StandardHost) host).isCopyXML());
                    setDeployXML(((StandardHost) host).isDeployXML());
                    setUnpackWARs(((StandardHost) host).isUnpackWARs());
                }
            } catch (ClassCastException e) {
                log.error(sm.getString("hostConfig.cce", event.getLifecycle()), e);
                return;
            }
    
            // Process the event that has occurred
            if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
                check();
            } else if (event.getType().equals(Lifecycle.START_EVENT)) {
                start();
            } else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
                stop();
            }
    }
    
    通过上面的代码,我们可以看出监听的事件是 Lifecycle.START_EVENT ,而通过查看 org.apache.catalina.LifecycleState 的代码 STARTING(true,Lifecycle.START_EVENT) 就可以得知,此时生命周期状态应该是STARTING,到这里我们应该已经猜到了,HostConfig是在StandardHost#start的时候通过监听器调用,为了验证我们的猜测,我们debug一下代码,我们可以在HostConfig#start方法中打个断点,运行以后得到如下内存结构:




    通过上图也就验证了我们刚才的猜测。

    通过上面的分析我们清楚了webapps目录中context的启动,总结如下:

    webapps目录中应用的启动在StandardHost#start的时候,通过Lifecycle.START_EVENT这个事件的监听器HostConfig进行进一步的启动。

    综合上面的文章所述,最后我们再来一下总结,我们知道Java程序启动以后,最终会以进程的形式存在,而Java进程中又会有很多条线程存在,因此最后我们就来看看Tomcat启动以后,到底启动了哪些线程,通过这些我们可以反过来验证我们对源代码的理解是否正确。接下来我们启动Tomcat,然后运行jstack -l <pid>来看看,在笔者的机器上面,jstack的输入如下所示:

    Full thread dump Java HotSpot(TM) 64-Bit Server VM (20.51-b01-457 mixed mode):
    
    "ajp-bio-8009-AsyncTimeout" daemon prio=5 tid=7f8738afe000 nid=0x115ad6000 waiting on condition [115ad5000]
       java.lang.Thread.State: TIMED_WAITING (sleeping)
            at java.lang.Thread.sleep(Native Method)
            at org.apache.tomcat.util.net.JIoEndpoint$AsyncTimeout.run(JIoEndpoint.java:148)
            at java.lang.Thread.run(Thread.java:680)
    
       Locked ownable synchronizers:
            - None
    
    "ajp-bio-8009-Acceptor-0" daemon prio=5 tid=7f8738b05800 nid=0x1159d3000 runnable [1159d2000]
       java.lang.Thread.State: RUNNABLE
            at java.net.PlainSocketImpl.socketAccept(Native Method)
            at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:439)
            - locked <7f46a8710> (a java.net.SocksSocketImpl)
            at java.net.ServerSocket.implAccept(ServerSocket.java:468)
            at java.net.ServerSocket.accept(ServerSocket.java:436)
            at org.apache.tomcat.util.net.DefaultServerSocketFactory.acceptSocket(DefaultServerSocketFactory.java:60)
            at org.apache.tomcat.util.net.JIoEndpoint$Acceptor.run(JIoEndpoint.java:216)
            at java.lang.Thread.run(Thread.java:680)
    
       Locked ownable synchronizers:
            - None
    
    "http-bio-8080-AsyncTimeout" daemon prio=5 tid=7f8735acb800 nid=0x1158d0000 waiting on condition [1158cf000]
       java.lang.Thread.State: TIMED_WAITING (sleeping)
            at java.lang.Thread.sleep(Native Method)
            at org.apache.tomcat.util.net.JIoEndpoint$AsyncTimeout.run(JIoEndpoint.java:148)
            at java.lang.Thread.run(Thread.java:680)
    
       Locked ownable synchronizers:
            - None
    
    "http-bio-8080-Acceptor-0" daemon prio=5 tid=7f8735acd000 nid=0x1157cd000 runnable [1157cc000]
       java.lang.Thread.State: RUNNABLE
            at java.net.PlainSocketImpl.socketAccept(Native Method)
            at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:439)
            - locked <7f46a8690> (a java.net.SocksSocketImpl)
            at java.net.ServerSocket.implAccept(ServerSocket.java:468)
            at java.net.ServerSocket.accept(ServerSocket.java:436)
            at org.apache.tomcat.util.net.DefaultServerSocketFactory.acceptSocket(DefaultServerSocketFactory.java:60)
            at org.apache.tomcat.util.net.JIoEndpoint$Acceptor.run(JIoEndpoint.java:216)
            at java.lang.Thread.run(Thread.java:680)
    
       Locked ownable synchronizers:
            - None
    
    "ContainerBackgroundProcessor[StandardEngine[Catalina]]" daemon prio=5 tid=7f8732850800 nid=0x111203000 waiting on condition [111202000]
       java.lang.Thread.State: TIMED_WAITING (sleeping)
            at java.lang.Thread.sleep(Native Method)
            at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.run(ContainerBase.java:1508)
            at java.lang.Thread.run(Thread.java:680)
    
       Locked ownable synchronizers:
            - None
    
    
    "main" prio=5 tid=7f8735000800 nid=0x10843e000 runnable [10843c000]
       java.lang.Thread.State: RUNNABLE
            at java.net.PlainSocketImpl.socketAccept(Native Method)
            at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:439)
            - locked <7f32ea7c8> (a java.net.SocksSocketImpl)
            at java.net.ServerSocket.implAccept(ServerSocket.java:468)
            at java.net.ServerSocket.accept(ServerSocket.java:436)
            at org.apache.catalina.core.StandardServer.await(StandardServer.java:452)
            at org.apache.catalina.startup.Catalina.await(Catalina.java:779)
            at org.apache.catalina.startup.Catalina.start(Catalina.java:725)
            at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
            at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
            at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
            at java.lang.reflect.Method.invoke(Method.java:597)
            at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:322)
            at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:456)
            at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
            at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
            at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
            at java.lang.reflect.Method.invoke(Method.java:597)
            at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
    

    上面的截图我已经取消了JVM自己本生的线程,从上图中我们可以清楚的看到,有6条线程,其中ajp-bio-8009-AsyncTimeoutajp-bio-8009-Acceptor-0是在Ajp的Connector启动的时候启动的,http-bio-8080-AsyncTimeouthttp-bio-8080-Acceptor-0是http的Connector启动的时候启动的,ContainerBackgroundProcessor[StandardEngine[Catalina]]是在StandardEngine启动的时候启动的,而main线程就是我们的主线程。这里还需要注意一点就是除了Main线程以外,其它的线程都是Dameon线程,相关的内容在下篇Tomcat的关闭我们再来详细说明。




    展开全文
    jiaomingliang 2015-08-10 17:49:35
  • 看了源码后对它的启动过程有了一定的了解,特来记录一下。 首先先介绍一下Tomcat的架构图:  Tomcat主要有两个核心的组件,一个是Connector(连接器)和容器。所谓连接器就是当有HTTP请求到来时,连接器负责接收这...

          一直使用Tomcat确不知道它的运行原理。看了源码后对它的启动过程有了一定的了解,特来记录一下。

          首先先介绍一下Tomcat的架构图:
    这里写图片描述

          Tomcat主要有两个核心的组件,一个是Connector(连接器)和容器。所谓连接器就是当有HTTP请求到来时,连接器负责接收这个请求,然后将该请求转发到容器。容器有Engine,Host,Context,Wrapper。Engine:表示整个Catalina servlet引擎;Host:表示包含一个或多个Context容器的虚拟主机;Context:表示一个Web应用程序。一个Context可以有多个Wrapper;Wrapper:表示一个独立的servlet。一个容器可以有0个或多个低层级的子容器。例如,一般情况下,一个Context实例会有一个或多个Wrapper实例。一个Host实例中会有0个或多个Context实例。但是,Wrapper类型处于层级结构的最底层,因此,它无法再包含子容器了。

          一般启动Tomcat会是运行startup.bat或者startup.sh文件,这两个文件最后都会调用,org.apache.catalina.startup包下面Bootstrap类的main方法。main方法具体实现如下:
    这里写图片描述

    main方法先实例化了一个Bootstrap实例,接着调用了init方法。init方法是生命周期方法,以后不再累述。接着看init的具体实现。
    这里写图片描述
    init方法,先初始化了类加载器。initClassLoaders方法具体实现如下:
    这里写图片描述
    createClassLoader具体实现细节不再显示,有感兴趣的读者可以具体查看。该方法的具体签名为:

    private ClassLoader createClassLoader(String name, ClassLoader parent)  throws Exception

    从方法签名中可以看出createClassLoader需要传入一个父加载器。从具体的代码中可以看出,commonLoader类加载器是catalinaLoader类加载器和sharedLoader类加载器的父加载器。初始化完类加载器后,使用反射机制调用org.apache.catalina.startup.Catalina类下的setParentClassLoader方法。具体代码是:

    这里写图片描述

    因为Tomcat执行的是start操作,调用完init方法后,会执行load方法。

    这里写图片描述
    load方法通过反射调用Catalina类的load方法。

    这里写图片描述
    Catalina类的load方法:

    这里写图片描述

    load方法中比较重要的方法是createStartDigester(),createStartDigester方法主要的作用就是帮我们实例化了所有的服务组件包括server,service和connect。具体的实例化方法,
    这里写图片描述

    初始化操作完成后,接下来会执行catalina实例的start方法。

    从上面加载的组件中,Tomcat会默认加载org.apache.catalina.core.StandardServer作为Server的实例类。

    这里写图片描述

    在Server的start的方法里面会执行service的start方法。在createStartDigester()方法里面,会默认加载org.apache.catalina.core.StandardService类。会接着调用Service的start方法。
    这里写图片描述
    service中会调用connector的start方法。至此Tomcat启动完毕

    展开全文
    c275046758 2016-04-26 16:52:09
  • java程序的入口应该是main()函数,可是基于xml配置开发的web项目,我们从来没有写...下面我们通过跟踪调试tomcat源码,分析一下web项目的启动过程源码下载地址: http://mirrors.hust.edu.cn/apache/tomcat/tomc...

    平常开发中只需要把开发好的war包上传到服务器,启动服务器,web项目就跟着启动运行了,这是为什么?服务器都做了哪些事情?下面我们通过跟踪调试tomcat源码,分析一下web项目的启动过程。
    源码下载地址:
    http://mirrors.hust.edu.cn/apache/tomcat/tomcat-8/v8.5.34/src/apache-tomcat-8.5.34-src.zip
    下载完成解压后删除webapps目录下的examples目录,不删除启动可能会报错(我的机器上是这样的),然后在根目录下创建pom.xml的maven依赖文件,内容如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>cn.linxdcn</groupId>
        <artifactId>Tomcat8.0</artifactId>
        <name>Tomcat8</name>
        <version>8.0</version>
        <build>
            <finalName>Tomcat8</finalName>
            <sourceDirectory>java</sourceDirectory>
            <resources>
                <resource>
                    <directory>java</directory>
                </resource>
            </resources>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>2.3</version>
                    <configuration>
                        <encoding>UTF-8</encoding>
                        <source>1.8</source>
                        <target>1.8</target>
                    </configuration>
                </plugin>
            </plugins>
        </build>
        <dependencies>
            <dependency>
                <groupId>org.apache.ant</groupId>
                <artifactId>ant</artifactId>
                <version>1.9.5</version>
            </dependency>
            <dependency>
                <groupId>org.apache.ant</groupId>
                <artifactId>ant-apache-log4j</artifactId>
                <version>1.9.5</version>
            </dependency>
            <dependency>
                <groupId>org.apache.ant</groupId>
                <artifactId>ant-commons-logging</artifactId>
                <version>1.9.5</version>
            </dependency>
            <dependency>
                <groupId>javax.xml.rpc</groupId>
                <artifactId>javax.xml.rpc-api</artifactId>
                <version>1.1</version>
            </dependency>
            <dependency>
                <groupId>wsdl4j</groupId>
                <artifactId>wsdl4j</artifactId>
                <version>1.6.2</version>
            </dependency>
            <dependency>
                <groupId>org.eclipse.jdt.core.compiler</groupId>
                <artifactId>ecj</artifactId>
                <version>4.4</version>
            </dependency>
        </dependencies>
    </project>
    

    把文件保存好之后,就可以导入源码到开发工具中查看了。
    查看源码之前,首先我们看一下tomcat的结构模型
    在这里插入图片描述

    1. Server:代表整个服务器,一个服务器中可以有多个Service;
    2. Service: 由一个或者多个Connector组成,以及一个Engine,负责处理所有Connector所获得的客户请求;
    3. Connector: 一个Connector将在某个指定端口上侦听客户请求,并将获得的请求交给Engine来处理,从Engine处获得回应并返回客户;
    4. Engine:Engine下可以配置多个虚拟主机Virtual Host,每个虚拟主机都有一个域名
      当Engine获得一个请求时,它把该请求匹配到某个Host上,然后把该请求交给该Host来处理,Engine有一个默认虚拟主机,当请求无法匹配到任何一个Host上的时候,将交给该默认Host来处理
    5. Host: 代表一个Virtual Host,虚拟主机,每个虚拟主机和某个网络域名Domain Name相匹配,每个虚拟主机下都可以部署(deploy)一个或者多个Web App,每个Web App对应于一个Context,有一个Context path当Host获得一个请求时,将把该请求匹配到某个Context上,然后把该请求交给该Context来处理,匹配的方法是“最长匹配”,所以一个path==”"的Context将成为该Host的默认Context,所有无法和其它Context的路径名匹配的请求都将最终和该默认Context匹配
    6. Context:一个Context对应于一个Web Application,一个Web Application由一个或者多个Servlet组成,Context在创建的时候将根据配置文件CATALINA_HOME/conf/web.xml和$WEBAPP_HOME/WEB-INF/web.xml载入Servlet类,当Context获得请求时,将在自己的映射表(mapping table)中寻找相匹配的Servlet类,如果找到,则执行该类,获得请求的回应,并返回

    在源码中与这6个组件对应的类如下:
    Connector :org.apache.catalina.connector.Connector.java
    Server: org.apache.catalina.core.StandardServer.java
    Service: org.apache.catalina.core.StandardService.java
    Engine: org.apache.catalina.core.StandardEngine.java
    Host: org.apache.catalina.core.StandardHost.java
    Context: org.apache.catalina.core.StandardContext.java

    组件的生命周期:Connector、StandardServer、StandardService、StandardEngine、StandardHost、StandardContext这6个组件类通过继承它们的父类都间接实现了生命周期接口Lifecycle。该接口定义组件在初始化、启动、停止、销毁的时候所要做的处理。初始化调用接口的init()方法,启动调用接口的start()方法,停止调用接口的stop()方法,销毁调用接口的destroy()方法。调用这些方法的前后通常会伴随着组件状态(LifecycleState)的改变,当组件状态改变的时候会发布相应的事件(LifecycleEvent),该事件将由接口注册的事件监听器(LifecycleListener)来处理。
    Lifecycle接口部分内容:
    在这里插入图片描述
    源码跟踪:
    tomcat启动入口在Bootstrap类下的main()函数
    源码中的类路径:org.apache.catalina.startup.Bootstrap.java
    main函数如下(代码过长的以后只列出核心代码)
    在这里插入图片描述
    进入Bootstrap的init()方法:
    在这里插入图片描述
    init()方法的核心是创建Catalina类的一个实例catalinaDaemon,回到main()函数中接着往下走
    在这里插入图片描述
    进入Bootstrap的load()方法:
    在这里插入图片描述
    在daemon.load(args)方法中反射调用了catalinaDaemon.load(args)方法,进入Catalina.load()方法中
    在这里插入图片描述
    digester这个对象中定义了在接下来的启动过程中要创建的对象以及创建对象的规则
    file是读取conf/server.xml生成的文件
    在这里插入图片描述
    在这里插入图片描述
    在Catalina.load()方法中接着往下走:
    在这里插入图片描述
    调用digester.parse()之后,在之前我们在createStartDigester()函数中添加的对象都会按照给定的规则实例化。
    (Connector、StandardServer、StandardService、StandardEngine、StandardHost就是在这一步实例化的,StandardContext的实例化不在这个过程中,后面讲)
    创建的过程参见Digester.startElement()方法和Rule.begin()方法。

    在Catalina.load()方法中接着往下走:
    在这里插入图片描述
    getServer().init()执行的是StandardServer.init(),StandardServer中的init()方法来自于它父类的父类LifecycleBase,该类是一个实现生命周期接口Lifecycle的抽象类,LifecycleBase.init()方法的实现如下:
    在这里插入图片描述
    StandardServer的初始化主要是在StandardServer.initInternal()中完成的,其他几个组件跟这类似,初始化也是主要在自己的initInternal()方法中完成。

    进入到StandardServer.initInternal()
    在这里插入图片描述
    在这里插入图片描述
    StandardServer.initInternal()最后用循环调用StandardService.init()方法。init()方法的执行流程都是类似的,不再进入查看了。StandardService的初始化方法中又调用了StandardEngine的初始化方法。StandardEngine初始化完成后,整个的初始化过程完成(StandardHost和StandardContext的初始化不在这个过程中)。
    getServer.init()方法执行完,load()方法执行完之后,接着执行Bootstrap的start()方法
    在这里插入图片描述
    进入Bootstrap的start()方法中:
    在这里插入图片描述
    Bootstrap.start()方法中反射调用了Catalina.start()方法,进入该方法中:
    在这里插入图片描述
    getServer().start()执行的是StandardServer.start()方法,与StandardServer.init()类似,StandardServer.start()最终执行的是StandardServer.startInternal()方法,进入该方法:
    在这里插入图片描述
    StandardService.start()最终执行的是 StandardService.startInternal()方法,进入该方法:
    在这里插入图片描述
    StandardEngine、StandardHost、StandardContext都继承了ContainerBase类,ContainerBase实现了容器接口Container。StandardHost是StandardEngine的子容器(child),StandardContext是StandardHost的子容器。
    StandardEngine.start()方法最终调用的是ContainerBase.startInternal(),进入该方法中:
    在这里插入图片描述
    该方法的核心是自己启动的同时找到自己的子容器,并以多线程的方式启动它们。StandardEngine启动之后,会接着启动自己的子容器StandardHost。

    StandardHost在启动的时候会发布一个启动事件给自己的监听器来处理,StandardHost的事件监听器的实现类是HostConfig,进入它的事件处理函数:
    在这里插入图片描述
    进入start()方法:
    在这里插入图片描述
    deployApps()就是部署Host中项目的操作,进入该函数:
    在这里插入图片描述
    这个函数就是根据不同的情况来部署我们的项目的,其中deployWARs()就是用来部署war包的,进入该函数:
    在这里插入图片描述
    该函数的核心是利用多线程来部署每一个war包,最总的部署代码是HostConfig.deployWAR(),进入该方法中:
    在这里插入图片描述
    在这里插入图片描述
    该方法的核心是创建StandardContext的实例(每部署一个项目都会生成一个StandardContext的实例),并将其作为子容器添加到StandardHost中。StandardHost启动完成后启动它的子容器StandardContext。进入StandardContext.startInternal()方法中:
    在这里插入图片描述
    在该方法执行过程中发布了一个事件,进入StandardContext的事件监听器ContextConfig的事件处理函数中查看对该事件的处理:
    在这里插入图片描述
    进入configureStart()函数:
    在这里插入图片描述
    进入webConfig()函数:
    在这里插入图片描述
    这个函数的核心是对我们项目中web.xml文件的解析处理,在这个函数中接着往下走:
    在这里插入图片描述
    进入configureContext()中:
    对
    在这里插入图片描述
    从web.xml中解析出Filter、Listener、Servlet定义的代码。
    从web.xml中解析的所有内容都会添加到对应项目的StandardContext实例中。解析完成之后继续回到
    StandardContext.startInternal()中往下执行:
    在这里插入图片描述
    如果我们的项目全部是基于注解开发的,没有web.xml文件,这一步的作用十分重要,想要了解的请去看
    org.springframework.web.SpringServletContainerInitializer.class这个类。如果我们的项目是基于xml配置开发的
    这一步貌似没什么卵用。接着往下执行:
    在这里插入图片描述
    进入listenerStart()函数:
    在这里插入图片描述
    listener是我们在web.xml中配置的,用于加载spring配置文件的,有兴趣的可以去看下源码contextInitialized()的实现,常见配置如下:
    在这里插入图片描述
    listener.contextInitialized()执行之后,我们web项目中的spring容器才真正启动。
    回到StandardContext.startInternal()中往下执行:
    在这里插入图片描述
    loadOnStartup()方法的作用是初始化servlet,我们知道servlet有个init()方法,这个地方就是调用init()方法。
    DispatcherServlet就是在这儿初始化的,它父类的初始化方法如下,有兴趣的可以去看下完整的初始化过程:
    在这里插入图片描述

    当StandardContext.start()执行完成之后,一个项目的基本启动流程算是执行完了。
    分析的不是很细,建议大家下载源码自己跟一下启动过程。

    展开全文
    qq_21441857 2018-10-10 19:11:40
  • Tomcat运行入口类BootStrap的入口方法,即启动方法;将Tomcat启动分为了两部分 init 和 start init:初始化阶段,即后面要依次调用的是各组件的初始化方法 InitInternal:Standard~,Connector;因为他们继承了...

    Tomcat运行入口类BootStrap的入口方法,即启动方法;将Tomcat的启动分为了两部分 init 和 start

    • init:初始化阶段,即后面要依次调用的是各组件的初始化方法
      • InitInternal:Standard~,Connector;因为他们继承了LifeCycleBase,里面用模板模式实现了init方法
      • init:ProtocolHandler是enum,Endpoint是抽象类;他们都自己定义了init,而不是LifeCycle的init
    • start:运行阶段,即后面要依次调用各组件的start方法
      • startInternal:Standard~,Connector;因为他们继承了LifeCycleBase,里面用模板模式实现了start方法
      • start:ProtocolHandler是enum,Endpoint是抽象类;他们都自己定义了start,而不是LifeCycle的start
    public static void main(String args[]) {
        	//1.初始化阶段  init()
            if (daemon == null) {
                // Don't set daemon until init() has completed
                Bootstrap bootstrap = new Bootstrap();
                try {
                    // 调用init()初始化BootStrap
                    bootstrap.init();
                } catch (Throwable t) {
                    handleThrowable(t);
                    t.printStackTrace();
                    return;
                }
                // 将守护线程bootstop赋给守护线程daemon
                daemon = bootstrap;
            } else {
                Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
            }
        	//2.运行阶段  start()
            try {
                String command = "start";
                if (args.length > 0) {
                    command = args[args.length - 1];
                }
    
                if (command.equals("startd")) {
                    args[args.length - 1] = "start";
                    // deamon.load(args),实际上会去调用Catalina#load(args)方法,
                    // 会去初始化一些资源,优先加载conf/server.xml,找不到再去加载server-embed.xml;
                    // 此外,load方法还会初始化Server
                    daemon.load(args);
                    // daemon.start(),实例上是调用Catalina.start()
                    daemon.start();
                  // 输入stop命令就停止
                } else if (command.equals("stopd")) {
                    args[args.length - 1] = "stop";
                    daemon.stop();
                } else if (command.equals("start")) {
                    daemon.setAwait(true);
                    daemon.load(args);
                    daemon.start();
                } else if (command.equals("stop")) {
                    daemon.stopServer(args);
                } else if (command.equals("configtest")) {
                    daemon.load(args);
                    if (null==daemon.getServer()) {
                        System.exit(1);
                    }
                    System.exit(0);
                } else {
                    log.warn("Bootstrap: command \"" + command + "\" does not exist.");
                }
            } catch (Throwable t) {
                // Unwrap the Exception for clearer error reporting
                if (t instanceof InvocationTargetException &&
                        t.getCause() != null) {
                    t = t.getCause();
                }
                handleThrowable(t);
                t.printStackTrace();
                System.exit(1);
            }
    }
    
    展开全文
    weixin_43935927 2020-09-18 21:44:06
  • zhangcongyi420 2020-11-25 23:29:12
  • lantian0802 2013-03-08 18:21:25
  • xujingyiss 2021-08-23 20:23:05
  • wabiaozia 2019-07-14 15:36:39
  • qq_34037358 2021-03-12 15:43:30
  • chenpengjia006 2018-08-20 15:22:10
  • weixin_43935927 2020-09-19 00:51:37
  • darendu 2019-05-04 17:11:08
  • z1790424577 2020-04-14 17:11:17

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 46,127
精华内容 18,450
关键字:

tomcat启动流程源码