tomcate 5.5.26 源码分析思考

本文从源代码入手, 分析Tomcat的启动过程,共两篇文章。这是第一篇。

main方法
第一阶段
第二阶段
第三阶段
启动参数的区别
一个Bug
configtest启动参数
一般,我们直接运行startup.sh 来 启动Tomcat 。最终执行的命令是:

view sourceprint?1 java [options] org.apache.catalina.startup.Bootstrap start

options是JVM启动参数,这里忽略。

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

view sourceprint?01 public static void main(String args[]) { 

02     try { 

03        
// Attempt to load JMX class 

04         new ObjectName("test:foo=bar"); 

05     } catch (Throwable t) { 

06         System.out.println(JMX_ERROR_MESSAGE); 

07         try { 

08            
// Give users some time to read the message before exiting 

09             Thread.sleep(5000); 

10         } catch (Exception ex) { 

11         } 

12         return; 

13     } 

14   

15     if (daemon == null) { 

16         daemon = new Bootstrap(); 

17         try { 

18             daemon.init(); 

19         } catch (Throwable t) { 

20             t.printStackTrace(); 

21             return; 

22         } 

23     } 

24   

25     try { 

26         String command = "start"; 

27         if (args.length > 0) { 

28             command = args[args.length - 1]; 

29         } 

30         if (command.equals("startd")) { 

31             args[0] = "start"; 

32             daemon.load(args); 

33             daemon.start(); 

34         } else if (command.equals("stopd")) { 

35             args[0] = "stop"; 

36             daemon.stop(); 

37         } else if (command.equals("start")) { 

38             daemon.setAwait(true); 

39             daemon.load(args); 

40             daemon.start(); 

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

42             daemon.stopServer(args); 

43         } else { 

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

45         } 

46     } catch (Throwable t) { 

47         t.printStackTrace(); 

48     } 

49 }

上述代码主要分为三段:

第一段是检查JMX 支持
第二段创建Boostrap 实 例并初始化
第三段根据启动参数,执行进一步操作
第一阶段
乍一看,第一段似乎可有可无。它只是创 建了一个不被使用的ObjectName 对象,如果创建失败,就打印一条错误信息。而 且在Tomcat 6 中,已经移除了这段代码。

view sourceprint?01 try { 

02        
// Attempt to load JMX class 

03         new ObjectName("test:foo=bar"); 

04     } catch (Throwable t) { 

05         System.out.println(JMX_ERROR_MESSAGE); 

06         try { 

07            
// Give users some time to read the message before exiting 

08             Thread.sleep(5000); 

09         } catch (Exception ex) { 

10         } 

11         return; 

12     }

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

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

从Tomcat 的官方下载 页面下载兼容包,参见http://tomcat.apache.org/download-55.cgi
从Tomcat 的源代码构建兼容包,参见http://jarfield.iteye.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 类声明该变量的代码:

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 ,看完了第一段,我们进入正题,看看第二段代码。

第二阶段
view sourceprint?1 if (daemon == null) { 

2     daemon = new Bootstrap(); 

3     try { 

4         daemon.init(); 

5     } catch (Throwable t) { 

6         t.printStackTrace(); 

7         return; 

8     } 

9 }

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

设 置Catalina (Tomcat Servlet 容器的代号)的路径:CATALINA_HOME 和CATALINA_BASE
初始化Tomcat 的类加载器体系
创建org.apache.catalina.startup.Catalina 对 象(启动阶段剩余的工作由Catalina类 完成)
为了节省篇幅,init 方法的代码另文表述 。下面我们看看第三段代码。

第三阶段
view sourceprint?01 try { 

02     String command = "start"; 

03     if (args.length > 0) { 

04         command = args[args.length - 1]; 

05     } 

06     if (command.equals("startd")) { 

07         args[0] = "start"; 

08         daemon.load(args); 

09         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 }

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

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

启动参数的区别
我们先看看start 和startd 。处理start 的 代码比startd 仅多了一行:

daemon.setAwait(true);

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

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

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

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

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

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

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

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

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

args[0] = “start”; 应该是 args[args.length - 1 ] = “start”;
args[0] = “stop”; 应该是 args[args.length - 1 ] = “stop”;
这 个Bug 已经在Tomcat 6 中解决了。



Tomcat 7中引入的configtest启动参数
在Tomcat 7中,新增了configtest启动参数。

view sourceprint?01 public static void main(String args[]) { 

02    
// 省略部分代码 

03     if (command.equals("startd")) { 

04         args[args.length - 1] = "start"; 

05         daemon.load(args); 

06         daemon.start(); 

07        
// 省略部分代码 

08     } else if (command.equals("configtest")) { 

09         daemon.load(args); 

10         if (null==daemon.getServer()) { 

11              System.exit(1); 

12         } 

13         System.exit(0); 

14     } else { 

15        
// 省略部分代码 

16     } 

17 }

顾名思义,configtest是为了检测配置参数是否正确,配置参数的主要来源就是conf/server.xml。

从代码可以看出,configtest启动参数就是把load方法给执行了一遍,然后无条件退出。load方法的代码,另文表述 。这里只要知道,load的主要工作之一就是解析conf/server.xml,从而起到检测配置参数是否正确的作用。

configtest通过exit status来返回检测结果。1表示检测到错误,0表示检测结果正确。如果load方法执行成功,就会创建server实例,通过 daemon.getServer方法返回;反之,如果配置参数不正确,load方法执行失败,那么就不创建server实 例,deamon.getServer方法就返回null。

你可能感兴趣的:(tomcat)