Tomcat6源码学习
在上一篇博文中说到因为电脑风扇坏了2天于是看完了《How tomcat works》译本,不过这本书是针对Tomcat4和5来讲的比较老了,然后再结合Tomcat6.0.26的源代码调试学习了总共一周的时间,算是大致将Tomcat6.0的一部分工作机制(JSP、JMX、安全管理等模块没研究)给搞清楚了。大概是因为平时基于Java的Web程序写的太多,对Tomcat这一块源码研究特别感兴趣,有时候为了想通一个问题兴奋地吃饭睡觉都在一直想,非常有动力并且也有效率,好久没有这种感觉了。
列几点觉得Tomcat设计上比较有意思的地方。
Connector模块
Tomcat主要分为Connector模块和Container模块,前者就是处理Socket和HTTP请求,后者是就是常听到的Servlet容器。
Connector模块由于功能比较独立,然后Tomcat内部接口的设计允许第三方组件的使用,就使用了Coyote组件,虽然也是apache下的。有空可以再好好来学习下Coyote的源代码,因为这块涉及了多线程和线程同步、资源优化、效率优化等方面,很值得学习。
Tomcat默认一共占用了3个端口,8080(HTTP/1.1)、8009(AJP/1.3,接受其他服务器转发的请求)和8005(等待SHUTDOWN命令,由Server类在主线程中开启)。其中8080和8009由Connector模块管理,所以默认有2个Connector在运作。
Container模块
由Engine、Host、Context、Wrapper四个容器组合而成,分别表示整个引擎、虚拟主机、Web应用、Servlet,通过父子关系连接,然后配套相应的Pipeline和Valve实现过程流。
Lifecycle接口
包括启动类(Catalina)、Server、Service、Connector、Container、Executor、Loader、Manager、Realm、Resources、Pipeline都实现了这个接口,通过简单的start()和stop()来递归实现生命周期的管理。
并且这一模块中还用到了LifecycleListener和LifecycleEvent这套事件驱动模型,虽然觉得Tomcat的事件驱动做的比较一般性,注册在某个Lifecycle的LifecycleListener会收到这个Object的所有事件通知,要靠自己去辨别事件类型并作出相应反应,效率较低且定制性弱。
ClassLoader的设计
Tomcat为了使类库的私有性,有commonLoader用来加载lib下的jar包,catalinaLoader和sharedLoader分别用来加载Tomcat类库和应用程序共享类库且都以commonLoader为parent。但是在Tomcat6的配置中,默认没有开启catalinaLoader和sharedLoader也没有相应的server和shared目录,导致这3个loader就是同一个commonLoader。这3个loader是StandardClassLoader的实例。
然后对于每一个Web应用程序,都有自己的一个WebappClassLoader,其parent为sharedLoader。这样可以防止不同Web应用程序之间加载互相类库。WebappClassLoader和前面提到的StandardClassLoader都继承自java.net.URLClassLoader,直接从指定目录中读取jar包或class文件。WebappClassLoader直接通过管理Servlet类的字节流来实现Servlet的加载然后缓存。
一般的Servlet只有一个实例,若是实现了STM(SingleThreadModel接口,已Deprecated)的Servlet则会有多个实例缓存着。
但似乎无论怎么设计,都无法避免Web应用程序中的Servlet可以获取到Tomcat启动库中的Bootstrap类(这个包由JVM自带的AppClassLoader加载),默认情况下还可以获取到Tomcat主类库中的所有类(比如Catalina类,因为都是由commonLoader加载,而它又是WebappClassLoader的parent)。尝试过将Tomcat的lib目录中关于Tomcat的类库放到server目录下并配置server.loader属性值(conf/catalina.properties)启动catalinaLoader,这样可以避免Servlet访问到Tomcat主类库,但是这样勉强的同时也阻止了比如ContainerServlet接口(使Servlet能访问Wrapper容器)的运作。
这样的一个坏处就是,可以在Web应用程序的Servlet中获取到Catalina类或者Bootstrap类,再启动一次Tomcat。Servlet中,如果在service方法中这么干会被Tomcat的安全机制给发现使得Tomcat整个关闭,若是配置了loadOnStartup后在init方法中这么干则会引发无限循环导致死机…
不过通过对Tomcat的ClassLoader的学习,对Java的ClassLoader机制有了大大的实践的了解,无论Tomcat是否真正实现了它设计上的初衷,整个设计思想是值得学习的。
backgroundProcessor()和后台线程
Engine容器和Context容器都会通过启动一个后台线程间断执行backgroundProcessor方法,比如重新加载Web应用程序的类文件和资源、扫描Session过期等。
静态资源
向Tomcat请求静态资源,和请求Servlet没有太大区别,仍然通过Connector和Container这一流程后,交到Tomcat内部的DefaultServlet来处理读取文件内容并写回客户端,下一次请求会返回304 Not Modified。
所以Tomcat对静态资源的管理比较差速度比较慢,才有apache + tomcat结合的说法吧。