tomcat work 启动过程一

一般,我们直接运行startup.sh 来启动Tomcat 。最终执行的命令是:

Java代码 复制代码
  1. java [options] org.apache.catalina.startup.Bootstrap start   
java [options] org.apache.catalina.startup.Bootstrap start 

main方法

可见,Tomcat 的启动类是org.apache.catalina.startup.Bootstrap ,启动参数是start 。我们从该类的main 方法看起。

Java代码 复制代码
  1. public static void main(String args[]) {   
  2.   
  3.     try {   
  4.         // Attempt to load JMX class   
  5.         new ObjectName("test:foo=bar");   
  6.     } catch (Throwable t) {   
  7.         System.out.println(JMX_ERROR_MESSAGE);   
  8.         try {   
  9.             // Give users some time to read the message before exiting   
  10.             Thread.sleep(5000);   
  11.         } catch (Exception ex) {   
  12.         }   
  13.         return;   
  14.     }   
  15.   
  16.     if (daemon == null) {   
  17.         daemon = new Bootstrap();   
  18.         try {   
  19.             daemon.init();   
  20.         } catch (Throwable t) {   
  21.             t.printStackTrace();   
  22.             return;   
  23.         }   
  24.     }   
  25.   
  26.     try {   
  27.         String command = "start";   
  28.         if (args.length > 0) {   
  29.             command = args[args.length - 1];   
  30.         }   
  31.         if (command.equals("startd")) {   
  32.             args[0] = "start";   
  33.             daemon.load(args);   
  34.             daemon.start();   
  35.         } else if (command.equals("stopd")) {   
  36.             args[0] = "stop";   
  37.             daemon.stop();   
  38.         } else if (command.equals("start")) {   
  39.             daemon.setAwait(true);   
  40.             daemon.load(args);   
  41.             daemon.start();   
  42.         } else if (command.equals("stop")) {   
  43.             daemon.stopServer(args);   
  44.         } else {   
  45.             log.warn("Bootstrap: command /"" + command + "/" does not exist.");   
  46.         }   
  47.     } catch (Throwable t) {   
  48.         t.printStackTrace();   
  49.     }   
  50. }  
public static void main(String args[]) {

    try {
        // Attempt to load JMX class
        new ObjectName("test:foo=bar");
    } catch (Throwable t) {
        System.out.println(JMX_ERROR_MESSAGE);
        try {
            // Give users some time to read the message before exiting
            Thread.sleep(5000);
        } catch (Exception ex) {
        }
        return;
    }

    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[0] = "start";
            daemon.load(args);
            daemon.start();
        } else if (command.equals("stopd")) {
            args[0] = "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();
    }
}


上述代码主要分为三段:

  1. 第一段是检查JMX 支持
  2. 第二段创建Boostrap 实例并初始化
  3. 第三段根据启动参数,执行进一步操作

第一阶段

乍一看,第一段似乎可有可无。它只是创建了一个不被使用的ObjectName 对象,如果创建失败,就打印一条错误信息。而且在Tomcat 6 中,已经移除了这段代码。

Java代码 复制代码
  1. try {   
  2.     // Attempt to load JMX class   
  3.     new ObjectName("test:foo=bar");   
  4. catch (Throwable t) {   
  5.     System.out.println(JMX_ERROR_MESSAGE);   
  6.     try {   
  7.         // Give users some time to read the message before exiting   
  8.         Thread.sleep(5000);   
  9.     } catch (Exception ex) {   
  10.     }   
  11.     return;   
  12. }  
    try {
        // Attempt to load JMX class
        new ObjectName("test:foo=bar");
    } catch (Throwable t) {
        System.out.println(JMX_ERROR_MESSAGE);
        try {
            // Give users some time to read the message before exiting
            Thread.sleep(5000);
        } catch (Exception ex) {
        }
        return;
    }


这段代码的作用是什么呢?这其实和J2SE 1.4 的兼容性有关。Tomcat 5 使用JMX 作为管理和监控机制,但是J2SE 1.4 本身并不支持JMX ,像ObjectName 这些JMX 类并不包括J2SE 1.4API 中。因此,Tomcat 5 不能直接运行在J2SE 1.4 及之前版本上。

但是Tomcat 5 的设计目标是支持运行在J2SE 1.4 上的。因此,要达到这个目标,Tomcat 5 须将JMX 类作为兼容包(compatibility package )额外添加到Tomcat 启动类路径中。
我们有两种方法获得兼容包:

  1. Tomcat 的官方下载页面下载兼容包,参见http://tomcat.apache.org/download-55.cgi
  2. Tomcat 的源代码构建兼容包,参见http://jarfield.javaeye.com/blog/604198


不管怎样,Tomcat 5 不能保证用户安装了兼容包,因此在启动时,它首先检查能够加载ObjectName 类,以此判断兼容包是否安装。如果没有安装,则向标准输出打印一条错误信息:
This release of Apache Tomcat was packaged to run on J2SE 5.0 or later. It can be run on earlier JVMs by downloading and installing a compatibility package from the Apache Tomcat binary download page.

这条信息也就是第一段代码中JMX_ERROR_MESSAGE 变量的值。下面是Bootstrap 类声明该变量的代码:

Java代码 复制代码
  1. private static final String JMX_ERROR_MESSAGE =   
  2.         "This release of Apache Tomcat was packaged to run on J2SE 5.0 /n"  
  3.         + "or later. It can be run on earlier JVMs by downloading and /n"  
  4.         + "installing a compatibility package from the Apache Tomcat /n"  
  5.         + "binary download page.";  
private static final String JMX_ERROR_MESSAGE =
        "This release of Apache Tomcat was packaged to run on J2SE 5.0 /n"
        + "or later. It can be run on earlier JVMs by downloading and /n"
        + "installing a compatibility package from the Apache Tomcat /n"
        + "binary download page.";

 
那为什么Tomcat 6中移除了第一段代码,不检查兼容包是否安装了呢?原因很简单,Tomcat 6的设计目标并不包括J2SE 1.4及之前版本。

OK ,看完了第一段,我们进入正题,看看第二段代码。

 

第二阶段

Java代码 复制代码
  1. if (daemon == null) {   
  2.     daemon = new Bootstrap();   
  3.     try {   
  4.         daemon.init();   
  5.     } catch (Throwable t) {   
  6.         t.printStackTrace();   
  7.         return;   
  8.     }   
  9. }  
    if (daemon == null) {
        daemon = new Bootstrap();
        try {
            daemon.init();
        } catch (Throwable t) {
            t.printStackTrace();
            return;
        }
    }


可见,这段代码的主要逻辑在Bootstrapinit 方法。该方法的工作包括:

  1. 设置CatalinaTomcat Servlet 容器的代号)的路径:CATALINA_HOMECATALINA_BASE
  2. 初始化Tomcat 的类加载器体系
  3. 创建org.apache.catalina.startup.Catalina 对象(启动阶段剩余的工作由Catalina类 完成)


为了节省篇幅,init 方法的代码另文表述。下面我们看看第三段代码。

 

第三阶段

Java代码 复制代码
  1. try {   
  2.         String command = "start";   
  3.         if (args.length > 0) {   
  4.             command = args[args.length - 1];   
  5.         }   
  6.         if (command.equals("startd")) {   
  7.             args[0] = "start";   
  8.             daemon.load(args);   
  9.             daemon.start();   
  10.         } else if (command.equals("stopd")) {   
  11.             args[0] = "stop";   
  12.             daemon.stop();   
  13.         } else if (command.equals("start")) {   
  14.             daemon.setAwait(true);   
  15.             daemon.load(args);   
  16.             daemon.start();   
  17.         } else if (command.equals("stop")) {   
  18.             daemon.stopServer(args);   
  19.         } else {   
  20.             log.warn("Bootstrap: command /"" + command + "/" does not exist.");   
  21.         }   
  22.     } catch (Throwable t) {   
  23.         t.printStackTrace();   
  24.     }  
try {
        String command = "start";
        if (args.length > 0) {
            command = args[args.length - 1];
        }
        if (command.equals("startd")) {
            args[0] = "start";
            daemon.load(args);
            daemon.start();
        } else if (command.equals("stopd")) {
            args[0] = "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();
    }


容易看出,这段代码的主要逻辑就是根据不同的启动参数,执行不同的工作。可以接受的启动参数包括4 种:startd stopd start stop

启动参数决定了启动还是停止Tomcat 。如何启动,如何停止,另文表述。本文问想讨论的问题是:除了startstop ,为什么还有startdstopd ?它们有什么区别呢?

 

启动参数的区别

我们先看看startstartd 。处理start 的代码比startd 仅多了一行:

Java代码 复制代码
  1. daemon.setAwait(true);  
daemon.setAwait(true);

 

这行代码执行后,主线程(即main 函数所在的线程)在Tomcat 启动过程结束时并不会退出,而是监听SHUTDOWN 端口(默认端口是8005 )。该端口如果接收SHUTDOWN 命令,就停止Tomcat ;如果收到的是其他命令,则忽略,继续监听。这样,我们就可以在Tomcat 进程之外通过网络停止Tomcat

如果以startd 参数启动Tomcat ,主线程会在启动结束时退出,只剩下主线程创建的其他线程(HTTP 监听线程、HTTP 请求线程、Tomcat 后台线程等)。这样,我们并不能通过网络停止Tomcat

如果启动参数是stop ,那么将调用Bootstrap类的stopServer 方法。该方法通过SHUTDOWN 端口向Tomcat 发送SHUTDOWN 命令,从而停止Tomcatshutdown.bat 就是通过这种方式关闭Tomcat ,它最终执行的命令是:
java [options] org.apache.catalina.startup.Bootstrap stop

如果启动参数是stopd,那么 Bootstrap将直接调用Catalina的stop方法,直接停止Tomcat。

综上所述,以start 启动,就以stop 停止;以startd 启动,就以stopd 停止。

如果将Tomcat 作为独立的进程运行,那么应该使用startstop ,这样我们就可以通过网络停止Tomcat

如果将Tomcat 以嵌入到应用进程的方式运行(例如Eclipse 中运行Tomcat ),那么应该使用startdstopd 。这样,宿主程序通过普通的方法调用,来启动和停止Tomcat

 

一个Bug

关于这4 个启动参数的讨论,可以参见Bug 47881 。这个Bug 的本意是要指出第三段代码的一个问题 ,不过恰好讨论了各种启动参数的区别。

至于这个Bug ,也比较明显:

  1. args[0] = "start"; 应该是 args[args.length - 1 ] = "start";
  2. args[0] = "stop"; 应该是 args[args.length - 1 ] = "stop";

这个Bug 已经在Tomcat 6 中解决了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值