自己最近在学习Java Web编程,先是读了Sun公司的官方文档Java EE 5的tutorial(http://java.sun.com/javaee/5/docs/tutorial/doc/)中的关键章节,然后学了一阵Java Passion里的相关在线课程(见:http://www.javapassion.com/j2ee/),前几天还看完了一本电子书<<Struts2 in Action>>,但总觉得缺少了什么。这两天突然想起为什么不把tomcat的源码下载下来仔细研究研究呢?记得以前自己一直想学习著名的Java Build工具Ant(http://ant.apache.org/)的用法,但怎么看文档都理解不深,试了几次效果都不好。最后终于把源码下载下来读了其中的主要源码,顿时感觉不仅轻松学会了Ant的用法,而且知道为什么要那么设计。这给我很大的启发:对于开源软件来说,比书籍更好的是在线文档,比在线文档更好的源码本身。其实tomcat和Ant有很深的内在联系,tomcat的早期开发者James Duncan Davidson就是Ant工具的发明人,为了解决开发tomcat过程中各种复杂的build流程他专门创造了Ant。显然,了解Ant对读懂tomcat会有极大的帮助。tomcat现在的架构师Craig McClanahan又是另一个著名的MVC框架struts的发明人。
步入正题吧。
首先到tomcat.apache.org里下载最新的源码包,现在最新的版本是6.0.20,下载后解压缩,发现它的根目录里有一个build.xml,这是Ant的默认输入文件。tomcat源码本身是用Eclipse写的,所以我们要做的第一步是在Eclipse里把tomcat工程部署好,这其实很容易,在Eclipse里选择File>New>Project,选择Java project with an existing Ant buildfile,选择那个build.xml就可以了。
然后是下载tomcat的依赖包,这一步也很容易,只要执行ant download(可以打开Eclipse的Ant的view以方便操作)就可以了。它会把下载的依赖包放在一个名称为base.path的目录,最好自己配置一下,它默认配置在build.xml.properties.default文件里。下载完依赖包后再ant deploy后就在output/build目录里得到了可以使用的tomcat。Ant真方便!
Eclipse里要想使得即时编译没有任何错误提示,需要在build path里配置两个变量:一个是ANT_HOME,另一个是保存依赖包的TOMCAT_LIBS_BASE,就是保存依赖包的那个目录。
看一下tomcat的目录结构,真的很简单,启动tomcat的脚本放在bin目录里,分别有对应windows和unix的两种脚本。我在Ubuntu环境下,所以先来看一下它的unix脚本。启动脚本是startup.sh,它实际上是另一个脚本catalina.sh的封装,所以直接跳到catalina.sh,它会调用另一个脚本setclasspath.sh以设置java,catalina.sh设置好各种环境后会调用tomcat的启动类:org.apache.catalina.startup.Bootstrap的start方法。
跟踪一下代码,Bootstrap类里主要是设置几个不同的ClassLoader和JMX Server,然后通过反射技术把任务移交给真正的启动类:org.apache.catalina.startup.Catalina。
怎么样,这是不是很像启动脚本一样,先有个包装,最后都调的是catalina,不管是shell还是Java的类。
Catalina里的方法最主要是这个方法:protected Digester createStartDigester();它是读取conf/server.xml配置文件进行动态加载配置组件的过程,用的是apache的另一个子项目:Digester,它也是struts读取配置文件动态加载的机制,别忘了tomcat的架构师可是struts项目的作者啊。
加载完server的层次化组件后,通过调用 ((Lifecycle) server).start();整个server就自顶向下启动起来了。另外,代码中大量运用了observer这个设计模式,把各种状态的变化通知给相应的listener。
在Bootstrap类的源码中发现一处小的bug:
if (replace && log.isDebugEnabled())
log.debug("Expanded " + before + " to " + replace);
应该是:
if (replace && log.isDebugEnabled())
log.debug("Expanded " + before + " to " + repository);