螺 丝 钉 阅读(204) 评论(0)

 

Tomcat是一个Web容器,用于接收HTTP请求并作出响应。我们都知道它是使用ServerSocket、Socket使用TCP链接达到通信的目的。但这个过程是如何做到的呢?我们在webapps下放的那些Web应用又是如何被监听起来的呢?配置webApp时有多种配置方式,如何正确的使用它们呢?web.xml为什么要那么配置呢,我们是否可以自定义一些元素呢?

    这些都是接下来,我要研究的课题。在此之前,我们还是先让Tomcat能够启动起来吧。

 

在了解Tomcat启动过程之前,最好还是上网查查Tomcat有哪些顶层的接口,也就是Tomcat的设计架构,不然晕着头就去调试代码,是看不出什么来的。

通过网上的了解,知道了Tomcat主要的接口有:

·Server、Service、Container、Connector、Lifecycle、Executor、Engine

、Host、Context、Wrapper、Value以及他们之间的关系。

其他的:

Realm、MBean等等。

 

紧接着大致的浏览了一下这些顶级接口,以及他们之间的关系,画了一张类图,如下:

 

 

 

通过这个类图,就可以快速的了解这些主要接口之间的关系了。

 

接下来,开始Tomcat启动过程的源码调试,如何启动调试,上一篇博文已经说的很清楚了。

调试时,入口在Bootstrap#main()

 

public static void main(String args[]) {

        if (daemon == null) {

            daemon = new Bootstrap();

            try {

// 初始化守护进程,其实就是初始化类加载器

                daemon.init();

            } catch (Throwable t) {

                t.printStackTrace();

                return;

            }

        }

 

        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")) {

                daemon.setAwait(true);

// 加载相关配置文件,初始化几个主要的顶层接口实例

                daemon.load(args);

// 启动那些有生命周期的顶层实例,监听用户请求

                daemon.start();

            } else if (command.equals("stop")) {

                daemon.stopServer(args);

            } else {

                log.warn("Bootstrap: command \"" + command + "\" does not exist.");

            }

        } catch (Throwable t) {

            t.printStackTrace();

        }

 

    }
View Code

 

从这个方法看,Tomcat启动过程可以简化为3个步骤:

1)初始化守护进程,其实就是初始化类加载器

2)加载相关配置文件,初始化几个主要的顶层接口实例,简单了说,就是服务器初始化。

3)启动那些有生命周期的顶层实例,监听用户请求,简单了说,就是启动服务器。

 

接下来,就针对这三个过程分别说明:

 

 

1、初始化类加载器

 

public void init()

        throws Exception

    {

 

        // Set Catalina path

// 根据环境变量CATALINA_HOME来初始化Tomcat的安装路径,相关配置文件路径

        setCatalinaHome();

        setCatalinaBase();

// 初始化类加载器,用于加载tomcat/lib目录下的jar包和class文件,或者从网络上加载class文件。

        initClassLoaders();

        Thread.currentThread().setContextClassLoader(catalinaLoader);

 

// 从tomcat/lib目录下加载org.apache.catalina.startup.Catalina类

// 用于开启Tomcat的真正的启动过程

        SecurityClassLoad.securityClassLoad(catalinaLoader);

 

        // Load our startup class and call its process() method

        if (log.isDebugEnabled())

            log.debug("Loading startup class");

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

        method.invoke(startupInstance, paramValues);

 

        catalinaDaemon = startupInstance;

 

    }
View Code

Tomcat官方描述这个过程 

类加载器初始化过程到这里也介绍完毕了,这里贴出来Tomcat官方文档中是如何介绍这个过程的:

 

 

 

 

2、服务器初始化

 

在调试这个过程之前,最好先了解一下配置文件server.xml如何配置,各个部分代表什么,下面是讲tomcat默认的server.xml写下来了。

<?xml version='1.0' encoding='utf-8'?>

 

<!-- Note:  A "Server" is not itself a "Container", so you may not

     define subcomponents such as "Valves" at this level.

     Documentation at /docs/config/server.html

 -->

<Server port="8005" shutdown="SHUTDOWN">

 

  <!--APR library loader. Documentation at /docs/apr.html -->

  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />

  <!--Initialize Jasper prior to webapps are loaded. Documentation at /docs/jasper-howto.html -->

  <Listener className="org.apache.catalina.core.JasperListener" />

  <!-- Prevent memory leaks due to use of particular java/javax APIs-->

  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />

  <!-- JMX Support for the Tomcat server. Documentation at /docs/non-existent.html -->

  <Listener className="org.apache.catalina.mbeans.ServerLifecycleListener" />

  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />

 

  <!-- Global JNDI resources

       Documentation at /docs/jndi-resources-howto.html

  -->

  <GlobalNamingResources>

    <!-- Editable user database that can also be used by

         UserDatabaseRealm to authenticate users

    -->

    <Resource name="UserDatabase" auth="Container"

              type="org.apache.catalina.UserDatabase"

              description="User database that can be updated and saved"

              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"

              pathname="conf/tomcat-users.xml" />

  </GlobalNamingResources>

 

  <!-- A "Service" is a collection of one or more "Connectors" that share

       a single "Container" Note:  A "Service" is not itself a "Container",

       so you may not define subcomponents such as "Valves" at this level.

       Documentation at /docs/config/service.html

   -->

  <Service name="Catalina">

 

    <!--The connectors can use a shared executor, you can define one or more named thread pools-->

    <!--

    <Executor name="tomcatThreadPool" namePrefix="catalina-exec-"

        maxThreads="150" minSpareThreads="4"/>

    -->

   

   

    <!-- A "Connector" represents an endpoint by which requests are received

         and responses are returned. Documentation at :

         Java HTTP Connector: /docs/config/http.html (blocking & non-blocking)

         Java AJP  Connector: /docs/config/ajp.html

         APR (HTTP/AJP) Connector: /docs/apr.html

         Define a non-SSL HTTP/1.1 Connector on port 8080

    -->

    <Connector port="8080" protocol="HTTP/1.1"

               connectionTimeout="20000"

               redirectPort="8443" />

    <!-- A "Connector" using the shared thread pool-->

    <!--

    <Connector executor="tomcatThreadPool"

               port="8080" protocol="HTTP/1.1"

               connectionTimeout="20000"

               redirectPort="8443" />

    -->          

    <!-- Define a SSL HTTP/1.1 Connector on port 8443

         This connector uses the JSSE configuration, when using APR, the

         connector should be using the OpenSSL style configuration

         described in the APR documentation -->

    <!--

    <Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"

               maxThreads="150" scheme="https" secure="true"

               clientAuth="false" sslProtocol="TLS" />

    -->

 

    <!-- Define an AJP 1.3 Connector on port 8009 -->

    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

 

 

    <!-- An Engine represents the entry point (within Catalina) that processes

         every request.  The Engine implementation for Tomcat stand alone

         analyzes the HTTP headers included with the request, and passes them

         on to the appropriate Host (virtual host).

         Documentation at /docs/config/engine.html -->

 

    <!-- You should set jvmRoute to support load-balancing via AJP ie :

    <Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm1">        

    -->

    <Engine name="Catalina" defaultHost="localhost">

 

      <!--For clustering, please take a look at documentation at:

          /docs/cluster-howto.html  (simple how to)

          /docs/config/cluster.html (reference documentation) -->

      <!--

      <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>

      -->       

 

      <!-- The request dumper valve dumps useful debugging information about

           the request and response data received and sent by Tomcat.

           Documentation at: /docs/config/valve.html -->

      <!--

      <Valve className="org.apache.catalina.valves.RequestDumperValve"/>

      -->

 

      <!-- This Realm uses the UserDatabase configured in the global JNDI

           resources under the key "UserDatabase".  Any edits

           that are performed against this UserDatabase are immediately

           available for use by the Realm.  -->

      <Realm className="org.apache.catalina.realm.UserDatabaseRealm"

             resourceName="UserDatabase"/>

 

      <!-- Define the default virtual host

           Note: XML Schema validation will not work with Xerces 2.2.

       -->

      <Host name="localhost"  appBase="webapps"

            unpackWARs="true" autoDeploy="true"

            xmlValidation="false" xmlNamespaceAware="false">

 

        <!-- SingleSignOn valve, share authentication between web applications

             Documentation at: /docs/config/valve.html -->

        <!--

        <Valve className="org.apache.catalina.authenticator.SingleSignOn" />

        -->

 

        <!-- Access log processes all example.

             Documentation at: /docs/config/valve.html -->

        <!--

        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" 

               prefix="localhost_access_log." suffix=".txt" pattern="common" resolveHosts="false"/>

        -->

 

      </Host>

    </Engine>

  </Service>

</Server>
View Code

代码调试:

1)加载server.xml,并创建出那些顶层对象

public void load() {

// 设置初始化开始时间,用于最后统计初始化过程用了多长时间

        long t1 = System.nanoTime();

// 这一步是再次初始化Tomcat的安装目录等

        initDirs();

 

        // Before digester - it may be needed

 

        initNaming();

// 这是一个XML文档解析器,用于解析server.xml、server-embed.xml

        // Create and execute our Digester

        Digester digester = createStartDigester();

 

        InputSource inputSource = null;

        InputStream inputStream = null;

        File file = null;

        try {

// file就是server.xml

            file = configFile();

            inputStream = new FileInputStream(file);

            inputSource = new InputSource("file://" + file.getAbsolutePath());

        } catch (Exception e) {

            ;

        }

        if (inputStream == null) {

            try {

                inputStream = getClass().getClassLoader()

                    .getResourceAsStream(getConfigFile());

                inputSource = new InputSource

                    (getClass().getClassLoader()

                     .getResource(getConfigFile()).toString());

            } catch (Exception e) {

                ;

            }

        }

 

        // This should be included in catalina.jar

        // Alternative: don't bother with xml, just create it manually.

// 当tomcat作为一个独立的应用服务器是解析server.xml

// 当tomcat嵌入到其他应用中时,解析server-embed.xml

       

if( inputStream==null ) {

            try {

                inputStream = getClass().getClassLoader()

                .getResourceAsStream("server-embed.xml");

                inputSource = new InputSource

                (getClass().getClassLoader()

                        .getResource("server-embed.xml").toString());

            } catch (Exception e) {

                ;

            }

        }

       

 

        if ((inputStream == null) && (file != null)) {

            log.warn("Can't load server.xml from " + file.getAbsolutePath());

            if (file.exists() && !file.canRead()) {

                log.warn("Permissions incorrect, read permission is not allowed on the file.");

            }

            return;

        }

// 配置文件解析,解析的过程中,就会根据配置文件中的<Server />配置创建一个Server对象。

        try {

            inputSource.setByteStream(inputStream);

            digester.push(this);

            digester.parse(inputSource);

            inputStream.close();

        } catch (Exception e) {

            log.warn("Catalina.start using "

                               + getConfigFile() + ": " , e);

            return;

        }

 

        // Stream redirection

        initStreams();

 

        // Start the new server

// 初始化Server

        if (getServer() instanceof Lifecycle) {

            try {

                getServer().initialize();

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

               

            }

        }

 

        long t2 = System.nanoTime();

        if(log.isInfoEnabled())

            log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms");

 

    }

例如server.xml中有<server />、<service />、<connector>、<Engine />、<Host />这个过程是使用digester解析server.xml,并创建出相应的顶层对象。

与这些元素相关的顶层对象都会被创建。这些对象创建完毕,并且也设置依赖完毕,但是Tomcat并不知道一样,需要将他们都注册一下。

 

 

2)初始化Server对象:注册Server对象

接下来看看Server真正的初始化过程。

StandardServer#initialize():

public void initialize()

        throws LifecycleException

    {

        if (initialized) {

                log.info(sm.getString("standardServer.initialize.initialized"));

            return;

        }

        lifecycle.fireLifecycleEvent(INIT_EVENT, null);

        initialized = true;

// Server对象已经创建完毕了,现在将作为Tomcat组件其注册

        if( oname==null ) {

            try {

                oname=new ObjectName( "Catalina:type=Server");

                Registry.getRegistry(null, null)

                    .registerComponent(this, oname, null );

            } catch (Exception e) {

                log.error("Error registering ",e);

            }

        }

       

        // Register global String cache

        try {

            ObjectName oname2 =

                new ObjectName(oname.getDomain() + ":type=StringCache");

            Registry.getRegistry(null, null)

                .registerComponent(new StringCache(), oname2, null );

        } catch (Exception e) {

            log.error("Error registering ",e);

        }

 

        // Initialize our defined Services

// 在Server下设置初始化多个service,这个依据server.xml中的<service />配置

        for (int i = 0; i < services.length; i++) {

            services[i].initialize();

        }

    }

  

3)初始化Service对象:注册 Services、executors对象

StandardService#initialize():

public void initialize()

            throws LifecycleException

    {

        // Service shouldn't be used with embeded, so it doesn't matter

        if (initialized) {

            if(log.isInfoEnabled())

                log.info(sm.getString("standardService.initialize.initialized"));

            return;

        }

        initialized = true;

        if( oname==null ) {

            try {

                // Hack - Server should be deprecated...

                Container engine=this.getContainer();

                domain=engine.getName();

// 注册Service

                oname=new ObjectName(domain + ":type=Service,serviceName="+name);

                this.controller=oname;

                Registry.getRegistry(null, null)

                    .registerComponent(this, oname, null);

            

// 设置service的executors属性并注册executors

                Executor[] executors = findExecutors();

                for (int i = 0; i < executors.length; i++) {

                    ObjectName executorObjectName =

                        new ObjectName(domain + ":type=Executor,name=" + executors[i].getName());

                    Registry.getRegistry(null, null)

                        .registerComponent(executors[i], executorObjectName, null);

                }

               

            } catch (Exception e) {

                log.error(sm.getString("standardService.register.failed",domain),e);

            }

           

           

        }

        if( server==null ) {

            // Register with the server

            // HACK: ServerFactory should be removed...

           

            ServerFactory.getServer().addService(this);

        }

              

 

        // Initialize our defined Connectors

// 在service下设置多个connector

// 这个要依据service.xml的<connector>

        synchronized (connectors) {

            for (int i = 0; i < connectors.length; i++) {

                try {

                    connectors[i].initialize();

                } catch (Exception e) {

                    String message = sm.getString(

                            "standardService.connector.initFailed",

                            connectors[i]);

                    log.error(message, e);

 

                    if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"))

                        throw new LifecycleException(message);

                }

            }

        }

    }

 

4)初始化Connector对象 

 

public void initialize()

        throws LifecycleException

    {

        if (initialized) {

            if(log.isInfoEnabled())

                log.info(sm.getString("coyoteConnector.alreadyInitialized"));

           return;

        }

 

        this.initialized = true;

 

        if( oname == null && (container instanceof StandardEngine)) {

            try {

                // 注册连接器Connector

                StandardEngine cb=(StandardEngine)container;

                oname = createObjectName(cb.getName(), "Connector");

                Registry.getRegistry(null, null)

                    .registerComponent(this, oname, null);

                controller=oname;

            } catch (Exception e) {

                log.error( "Error registering connector ", e);

            }

            if(log.isDebugEnabled())

                log.debug("Creating name for connector " + oname);

        }

 

        // 为当前Connector创建一个CoyoteAdapter适配器

        adapter = new CoyoteAdapter(this);

        protocolHandler.setAdapter(adapter);

 

        // Make sure parseBodyMethodsSet has a default

        if( null == parseBodyMethodsSet )

            setParseBodyMethods(getParseBodyMethods());

 

        IntrospectionUtils.setProperty(protocolHandler, "jkHome",

                                       System.getProperty("catalina.base"));

 

        try {

// 协议初始化

            protocolHandler.init();

        } catch (Exception e) {

            throw new LifecycleException

                (sm.getString

                 ("coyoteConnector.protocolHandlerInitializationFailed", e));

        }

    }

 

  

5)ProtocolHandler初始化

 

协议初始化,根据<Connector>指定的协议类型来进行初始化。

 

public void init() throws Exception {

        endpoint.setName(getName());

        endpoint.setHandler(cHandler);

 

        // Verify the validity of the configured socket factory

        try {

            if (isSSLEnabled()) {

                sslImplementation =

                    SSLImplementation.getInstance(sslImplementationName);

                socketFactory = sslImplementation.getServerSocketFactory();

                endpoint.setServerSocketFactory(socketFactory);

            } else if (socketFactoryName != null) {

                socketFactory = (ServerSocketFactory) Class.forName(socketFactoryName).newInstance();

                endpoint.setServerSocketFactory(socketFactory);

            }

        } catch (Exception ex) {

            log.error(sm.getString("http11protocol.socketfactory.initerror"),

                      ex);

            throw ex;

        }

 

        if (socketFactory!=null) {

            Iterator<String> attE = attributes.keySet().iterator();

            while( attE.hasNext() ) {

                String key = attE.next();

                Object v=attributes.get(key);

                socketFactory.setAttribute(key, v);

            }

        }

       

// endpoint初始化,根据上面的类图就知道,当协议确定时,Endpoint的类型也就确定了。

        try {

            endpoint.init();

        } catch (Exception ex) {

            log.error(sm.getString("http11protocol.endpoint.initerror"), ex);

            throw ex;

        }

        if (log.isInfoEnabled())

            log.info(sm.getString("http11protocol.init", getName()));

 

    }

 

 


6)Endpoint初始化
 

public void init()

        throws Exception {

 

        if (initialized)

            return;

       

        // Initialize thread count defaults for acceptor

        if (acceptorThreadCount == 0) {

            acceptorThreadCount = 1;

        }

        if (serverSocketFactory == null) {

            serverSocketFactory = ServerSocketFactory.getDefault();

        }

        if (serverSocket == null) {

            try {

                if (address == null) {

                    serverSocket = serverSocketFactory.createSocket(port, backlog);

                } else {

                    serverSocket = serverSocketFactory.createSocket(port, backlog, address);

                }

            } catch (BindException orig) {

                String msg;

                if (address == null)

                    msg = orig.getMessage() + " <null>:" + port;

                else

                    msg = orig.getMessage() + " " +

                            address.toString() + ":" + port;

                BindException be = new BindException(msg);

                be.initCause(orig);

                throw be;

            }

        }

        //if( serverTimeout >= 0 )

        //    serverSocket.setSoTimeout( serverTimeout );

       

        initialized = true;

       

    }

从这段代码很清楚的看出,Endpoint就代表了服务端的一个Socket端点,endpoint的初始化其实就是创建ServerSocket对象。 

 

上述6个步骤中,不论采用什么协议,前3步都是一样的,至于5)、6)两步要也是需要的,但是由于协议的不同,每一步的内部是如何实现的,这点并不一样。

 

 

Tomcat官方描述这个过程

到这里,服务器初始化也就介绍的差不多了。这里贴出来tomcat官方文档时如何描述这一过程的:

 

 

 

3、服务器开启

 

StandardService启动过程说明

 

可以通过查看StandardServer#start()来了解服务器开启的过程。

如果跟踪代码就会发现:

Catalina#start()--àStandardServer#start()--àStandardService#start()

也就是Catalina对象执行start()期间,会调用StandardServer对象的start()方法。

StandardServer对象执行start()期间,会调用Server下的所有的StandardService对象的start()。

 

下面就看看StandardService对象的start方法:

 

public void start() throws LifecycleException {

 

        // Validate and update our current component state

        if (started) {

            if (log.isInfoEnabled()) {

                log.info(sm.getString("standardService.start.started"));

            }

            return;

        }

       

        if( ! initialized )

            init();

 

        // Notify our interested LifecycleListeners

        lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);

        if(log.isInfoEnabled())

            log.info(sm.getString("standardService.start.name", this.name));

        lifecycle.fireLifecycleEvent(START_EVENT, null);

        started = true;

 

        // 启动定义的所有的容器

// Engine、Host、Context都是容器,只要在server.xml中定义了,都会在这里启动

        if (container != null) {

            synchronized (container) {

                if (container instanceof Lifecycle) {

                    ((Lifecycle) container).start();

                }

            }

        }

// 启动执行器Executor

        synchronized (executors) {

            for ( int i=0; i<executors.size(); i++ ) {

                executors.get(i).start();

            }

        }

 

        // 启动定义的连接器Connector

        synchronized (connectors) {

            for (int i = 0; i < connectors.length; i++) {

                try {

                    ((Lifecycle) connectors[i]).start();

                } catch (Exception e) {

                    log.error(sm.getString(

                            "standardService.connector.startFailed",

                            connectors[i]), e);

                }

            }

        }

       

        // Notify our interested LifecycleListeners

        lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);

 

    }

 


 
 

Service下的Container的启动过程说明

Service下的容器有Engine,Engine下的容器有Host,Host下的容器有Context。

每个容器下都有Pipeline、Value、Realm等等配置。

 

接下来就看看如何启动这些各个容器的。调试代码的过程中,就会发现,每个容器都会使用到的一个方法是在ContainerBase中定义的start():

 

public synchronized void start() throws LifecycleException {

 

        // Validate and update our current component state

        if (started) {

            if(log.isInfoEnabled())

                log.info(sm.getString("containerBase.alreadyStarted", logName()));

            return;

        }

       

        // Notify our interested LifecycleListeners

        lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);

 

        started = true;

 

        // Start our subordinate components, if any

        if ((loader != null) && (loader instanceof Lifecycle))

            ((Lifecycle) loader).start();

        logger = null;

        getLogger();

        if ((logger != null) && (logger instanceof Lifecycle))

            ((Lifecycle) logger).start();

        if ((manager != null) && (manager instanceof Lifecycle))

            ((Lifecycle) manager).start();

        if ((cluster != null) && (cluster instanceof Lifecycle))

            ((Lifecycle) cluster).start();

        if ((realm != null) && (realm instanceof Lifecycle))

            ((Lifecycle) realm).start();

        if ((resources != null) && (resources instanceof Lifecycle))

            ((Lifecycle) resources).start();

 

        // Start our child containers, if any

// 启动子容器,也就是说:

如果this是Engine对象,就会启动Engine下的所有的host来启动。

// 当host执行start()过程,也会执行这个方法,来启动host下的context。

// 也就是说,这里会引起递归调用

        Container children[] = findChildren();

        for (int i = 0; i < children.length; i++) {

            if (children[i] instanceof Lifecycle)

                ((Lifecycle) children[i]).start();

        }

 

        // Start the Valves in our pipeline (including the basic), if any

// 启动Container下的在pipeline中的value

        if (pipeline instanceof Lifecycle)

            ((Lifecycle) pipeline).start();

 

        // Notify our interested LifecycleListeners

        lifecycle.fireLifecycleEvent(START_EVENT, null);

// 启动守护线程,用于周期性的检查Session是否超时

        // Start our thread

        threadStart();

 

        // Notify our interested LifecycleListeners

        lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);

 

    }
View Code

 

1)StandardEngine启动过程

 

public void start() throws LifecycleException {

        if( started ) {

            return;

        }

// 在初始化阶段,并没有初始化容器,所以在启动容器前要先初始化

        if( !initialized ) {

// 容器的初始化和其他组件的初始化类似,都是将其注册为MBean

            init();

        }

 

        // Look for a realm - that may have been configured earlier.

        // If the realm is added after context - it'll set itself.

        if( realm == null ) {

            ObjectName realmName=null;

            try {

                realmName=new ObjectName( domain + ":type=Realm");

                if( mserver.isRegistered(realmName ) ) {

                    mserver.invoke(realmName, "init",

                            new Object[] {},

                            new String[] {}

                    );           

                }

            } catch( Throwable t ) {

                log.debug("No realm for this engine " + realmName);

            }

        }

           

        // Log our server identification information

        //System.out.println(ServerInfo.getServerInfo());

        if(log.isInfoEnabled())

            log.info( "Starting Servlet Engine: " + ServerInfo.getServerInfo());

        if( mbeans != null ) {

            try {

                Registry.getRegistry(null, null)

                    .invoke(mbeans, "start", false);

            } catch (Exception e) {

                log.error("Error in start() for " + mbeansFile, e);

            }

        }

 

        // Standard container startup

// 这才是真正的操作。而这个操作是在ContainerBase中定义的,也就是说,所有的Container都可以使用。这个方法上面已经说明。

        super.start();

 

    }
View Code

 

 

2)StandardHost启动过程

 

public synchronized void start() throws LifecycleException {

        if( started ) {

            return;

        }

// 初始化host

        if( ! initialized )

            init();

 

        // Look for a realm - that may have been configured earlier.

        // If the realm is added after context - it'll set itself.

        if( realm == null ) {

            ObjectName realmName=null;

            try {

                realmName=new ObjectName( domain + ":type=Realm,host=" + getName());

                if( mserver.isRegistered(realmName ) ) {

                    mserver.invoke(realmName, "init",

                            new Object[] {},

                            new String[] {}

                    );           

                }

            } catch( Throwable t ) {

                log.debug("No realm for this host " + realmName);

            }

        }

           

        // Set error report valve, 用于报请求HTML错误码

        if ((errorReportValveClass != null)

            && (!errorReportValveClass.equals(""))) {

            try {

                boolean found = false;

                if(errorReportValveObjectName != null) {

                    ObjectName[] names =

// 添加Value,然后在下面的代码super.start()使用;也就是在调用ContainerBase#start()中使用。             

          ((StandardPipeline)pipeline).getValveObjectNames();

                    for (int i=0; !found && i<names.length; i++)

                        if(errorReportValveObjectName.equals(names[i]))

                            found = true ;

                    }

                    if(!found) {         

                        Valve valve = (Valve) Class.forName(errorReportValveClass)

                        .newInstance();

                        addValve(valve);

                        errorReportValveObjectName = ((ValveBase)valve).getObjectName() ;

                    }

            } catch (Throwable t) {

                log.error(sm.getString

                    ("standardHost.invalidErrorReportValveClass",

                     errorReportValveClass), t);

            }

        }

        if(log.isDebugEnabled()) {

            if (xmlValidation)

                log.debug(sm.getString("standardHost.validationEnabled"));

            else

                log.debug(sm.getString("standardHost.validationDisabled"));

        }

        super.start();

 

    }
View Code

 


2.1)Host启动过程中的管道pipeline启动
 

 

上面说了容器启动过程中都会执行的方法:ContainerBase#start(),代码在上面。代码中有一个pipeline.start()语句。

 

public synchronized void start() throws LifecycleException {

 

        // Validate and update our current component state

        if (started)

            throw new LifecycleException

                (sm.getString("standardPipeline.alreadyStarted"));

 

        // Notify our interested LifecycleListeners

        lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);

 

        started = true;

 

        // Start the Valves in our pipeline (including the basic), if any

        Valve current = first;

        if (current == null) {

        current = basic;

        }

        while (current != null) {

            if (current instanceof Lifecycle)

                ((Lifecycle) current).start();

            registerValve(current);

        current = current.getNext();

        }

 

        // Notify our interested LifecycleListeners

        lifecycle.fireLifecycleEvent(START_EVENT, null);

 

        // Notify our interested LifecycleListeners

// 通知监听器执行相关处理

        lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);

 

    }
View Code

 

2.2)通过事件处理进入到应用程序部署阶段

在fireLifecycleEvent方法调用期间,会调用到HostConfig的处理。

 

 

从这里就进入了应用程序部署的阶段了。

这个方法中的setDeployXML用于设置部署时是否部署XML,

也就是否部署通过XML方式描述的上下文:

 

 

 

setUnpackWars()是设置是否部署webapps下的war包。

HostConfig#start():

 

部署Host下所有的Web应用。

 

2.3)开始部署应用程序

 

从这个方法来看,部署时:

1)要部署tomcat/conf下当前Engine(catalina)下的当前host(localhost)目录下的所有的xml文件。这里的XML文件就是一个context描述符。

2)会部署tomcat/webapps下的war包和目录。

 

而每种不同类型的应用是如何部署到host下的,这里就先不看了。

 

至此,Tomcat启动的整个流程,算是了解的差不多了。

 

Tomcat官方描述这个过程

在这里,也贴出来Tomcat官方文档时如何描述这一过程的:

 

 

我在调试时写的流程是和官方的文档是基本上吻合的。通过这个调试,很容易就知道了Tomcat的各个组件的关系。