Tomat6源代码学习(整体架构)[转]
最近学习Tomcat源代码,下面是认为比较好的一篇文章。
原文地址:http://carllgc.blog.ccidnet.com/blog-htm-do-showone-uid-4092-type-blog-itemid-272088.html
我们已经成功将Tomcat6.0的源代码导入到IDE中。现在我们就开始学习Tomcat源码。Tomcat源代码共有1000多个java类,代码行数大约28万到30万行左右。从项目规模上说,可算得上是一个中型项目。
要学习理解Tomcat源代码,有多种办法可行。最原始的一种办法就是,打开Debugger,逐行跟踪,看看Tomcat如何启动,如何处理客户端请求,如何编译动态jsp页面。第二种办法是利用逆向工程,把Tomcat的总体类图先描绘出来,然后再结合sequence diagram,来学习理解它。我们在这里采取从顶到底的阅读方法,先了解整体架构,然后逐步细化。所谓“纲举目张”,说的就是这个道理。
首先,我们可以从功能的角度将Tomcat源代码分成5个子模块,它们分别是:
1) ? Jsper子模块
这个子模块负责jsp页面的解析,jsp属性的验证,同时也负责将jsp页面动态转换为java代码并编译成class文件。在Tomcat源代码中,凡是属于org.apache.jasper包及其子包中的源代码都属于这个子模块;
2) ? Servlet和Jsp规范的实现模块
这个子模块的源代码属于javax.servlet包及其子包,如我们非常熟悉的javax.servlet.Servlet接口、javax.servet.http.HttpServlet类及javax.servlet.jsp.HttpJspPage就位于这个子模块中;
3) ? Catalina子模块
这个子模块包含了所有以org.apache.catalina开头的java源代码。该子模块的任务是规范了Tomcat的总体架构,定义了Server、Service、Host、Connector、Context、Session及Cluster等关键组件及这些组件的实现,这个子模块大量运用了Composite设计模式。同时也规范了Catalina的启动及停止等事件的执行流程。从代码阅读的角度看,这个子模块是我们阅读和学习的重点。
4) ? Connectors子模块
如果说上面三个子模块实现了Tomcat应用服务器的话,那么这个子模块就是Web服务器的实现。所谓连接器(Connector)就是一个连接客户和应用服务器的桥梁,它接收用户的请求,并把用户请求包装成标准的Http请求(包含协议名称,请求头Head,请求方法是Get还是Post等等)。同时,这个子模块还按照标准的Http协议,负责给客户端发送响应页面,比如在请求页面未发现时,connector就会给客户端浏览器发送标准的Http 404错误响应页面。
Tomcat实现了两类连接器,除了上述实现了Http1.1协议的Coyote连接器外,还有一种JK连接器,JK连接器是将Tomcat和第三方Web服务器(如Apache或IIS Web服务器)连接起来, Tomcat此时充当应用服务器的角色,负责处理和解释Jsp及Servlet请求。
Coyote连接器的源代码位于以org.apache.coyote开头的包中,JK连接器的代码位于以org.apache.jk开头的包中。
另外,Tomcat虽然实现了Web服务器的功能,但是其实现不是非常完美,效率不高,所以在生产环境中,我们通常要将Tomcat和Apache Web Server配合使用,尽量利用它们各自的优势。
5) ? Resource子模块
这个子模块包含一些资源文件,如Server.xml及Web.xml配置文件。严格说来,这个子模块不包含java源代码,但是它还是Tomcat编译运行所必需的。
![Tomat6源代码学习(通体架构)[转]](http://img.reader8.net/uploadfile/jiaocheng/20140188/2908/2014012900084535746.jpg)
从上面的Tomcat子模块示意图中,我们可以看到,来自客户端的请求首先由Connector子模块进行处理,然后根据情况或者发送到第三方的Web服务器,或者转发到Jsper模块进行处理,或者转发到Jsp/Servlet子模块处理。总体说来,Tomcat通过下面三种方式处理来自客户端的请求:
(1) ? 如果客户端发出静态页面请求,如果没有配置第三方Web服务器,此时客户端的请求直接交由Coyote Connector子模块处理,然后返回结果;如果配有第三方应用服务器,那么客户的请求直接由第三方应用服务器响应,然后返回静态记过页面。客户端请求的执行过程如图中绿线所示。
(2) ? 如果客户端请求Jsp页面,该请求首先转发到发送Coyote连接器(在没有配置第三方Web服务器的情况下),或者经过第三方Web服务器将客户请求转发到JK连接器;然后该Jsp请求将交给Jsper子模块处理,Jsper将根据情况验证编译该Jsp页面,最后由Jsp/Servlet模块对客户请求进行处理。Jsp请求处理完毕,服务器首先把响应结果发送给连接器子模块,连接器子模块根据情况或将响应结果页面发送到第三方Web服务器,或者直接发送响应结果页面到客户端。
(3) ? 如果客户请求Servlet,Tomcat的处理流程和Jsp页面的请求执行流程基本类似,只不过少了一个Jsper子模块处理罢了。
下面,我们重点针对Catalina子模块,熟悉Tomcat的几个关键组件。
(1) ? 服务器(Server) ?
在Tomcat中,服务器代表整个J2EE容器,所有的服务及服务上下文均包含在服务器内。我们打开Tomcat源代码,可以看到org.apache.catalina.Server这个接口,其中比较重要的方法有initialize(负责Tomcat启动前的初始化工作),还有一些服务(Services)管理方法,比如removeService()、addService()、findService()之类的方法。
在Tomcat运行时,我们永远只有一个Server实例,这不由让我们联想到单例模式(Singleton Pattern)。不错,在Tomcat中,Server的实例化工作正是由一个叫ServerFactory工厂类完成的,这个工厂类实现了单例设计模式。
比较有意思的是,这个工厂类的产品创建方法名为getServer()而不是标准的createServer()方法,并且没有加synchronized或synchronized(this)保护,这是为什么呢?我们知道,在应用单例模式时,需要注意的一个关键点就是多线程的调用问题,如果我们的工厂类在创建单例对象时,这个工厂类有可能被多个线程并发调用的话,那么最好给这个工厂方法加上synchronized以避免产生两个不同的产品类实例。如果您想避免synchronized的锁机制造成的性能损失,请使用双重检查机制(double-checked locking)。所以,如果考虑多线程,这个工厂类的getServer()方法应该写成:(红色代码是作者另加上的,源代码中没有)。
/**
* Return the singleton <code>Server</code> instance for this JVM.
*/
public static Server getServer() {
if( server==null ){
synchronized (ServerFactory.class) {
if(server==null){
? server=new StandardServer();
}
}
}
return (server);
}
为什么Tomcat在实现时没有加上面的红色代码呢?这是因为,Tomcat启动时创建Server对象,不可能出现多线程情况,所以就免掉了双重检查。如果我们确信没有多线程调用我们的单例工厂类,我们也可以这样做。
另外,如果您对ServerFactory进行调试,您会发现一个非常有趣的现象,这个工厂先执行的不是create方法(此处为getServer方法),而是setServer方法。这意味着这个工厂方法其实并不生产实际产品,实际产品是从别处产生,然后通过setServer方法注册到这个Factory。当下次有客户请求产品时,这个工厂方法只是简单的把现成的单例产品传给客户。所以这个类其实只需一个单例类足矣,根本没有必要使用工厂模式,所以Tomcat的开发者也觉得不好意思使用标准的工厂方法createProduct,杀鸡焉用宰牛刀,对吗?
在Tomcat6.0中,服务器(Server)接口的实现类只有一个,那就是org.apache.catalina.core. StandardServer类。这是一个标准的服务器实现类,这个类不但实现了Server接口,而且还实现了Lifecycle和MBeanRegistration接口,Lifecycle主要提供了服务器的生命周期管理功能,比如说启动、停止等方法,而MBeanRegistration接口是为了将server注册到MBean服务器,以便在Tomat运行时,我们能通过JMX来管理服务器。
从Tomcat5.0开始,Tomcat的开发人员在JMX管理上着实下了一番功夫,争取做到让Tomcat具有JBoss那样非常强大的管理功能。
(2) ? 服务(Service)
在上述的标准服务器(StanderServer.java)实现代码中,我们可以看到其中有一个services的数组,这个数组就是用来存储服务(Service)的。所以,我们可以这样理解,一个服务器可能有一至多个服务组成。所谓服务,就是包含一至多个连接器的组件,能够对用户请求作出响应的组件。打开org.apache.catalina.Service.java的源代码,我们可以看到其中含有一个连接器数组(Connector[]),这表明一个Service有可能包含一个到多个连接器。但所有这些连接器都属于一个引擎(Engine或Container)。在Tomcat6中,org.apache.catalina.Service接口由org.apache.catalina.core. StandardService类来实现的。
(3) ? 引擎(Engine)
对一个具体的服务(service)来说,引擎是一个用户请求的处理管道,这个管道很特别,因为它只处理Servlet请求,在Tomcat中,引擎其实就是指Servlet引擎。引擎从这些连接器那里接收到Servlet请求,然后处理它们,并将响应的结果传回到适当的连接器,从而将响应发送到客户端。简单地说,引擎的功能就是如何处理用户的Servlet请求。
org.apache.catalina.Engine这个接口继承自org.apache.catalina.Container,说明引擎是一种特殊的Container,是一种专门用来处理servlet请求的容器。
(4) ? 主机(Host)
对Tomcat服务器来说,主机是Tomcats所在机器的网络名(域名)。一个引擎可能包含多个主机,主机支持网络别名。例如,用户通过配置config.xml里面的主机(Host)元素,让www.abc.com和abc.com 指向同一台Tomcat应用服务器。
(5) ? 连接器(Connector)
在Tomcat中,连接器负责和客户端进行请求响应的交流。Tomcat中有两种连接器(Coyote和JK连接器),Coyote连接器实现了Http1.1协议,我们可以将它理解为Tomcat的Web服务器部分。JK连接器负责处理来自第三方Web服务器的请求,并将请求结果发送给第三方Web服务器。针对Apache Httpd Web服务器,JK连接器实现了AJP协议。
在Tomcat6.0中,实现Coyote连接器的类是org.apache.catalina.connector.Connector。
(6) ? 上下文(Context)
上下文代表某一具体的Web应用,一个主机可包含多个Web应用,所以可有多个Web应用上下文,不同的上下文可用不同路径来表示。上下文里含有一些关于该Web应用的一些具体信息,比如欢迎页面的文件名,web.xml文件的位置等等信息。
上下文在Tomcat的源码中对应org.apache.catalina.Context接口,其具体实现为org.apache.catalina.core.StandardContext。
至此为止,我们熟悉了Tomcat架构中一些重要组件。下面我们用UML类图(Class Diagram)来总结一下。 ![Tomat6源代码学习(通体架构)[转]](http://img.reader8.net/uploadfile/jiaocheng/20140188/2908/2014012900084535747.jpg)
在上面的类图中,我们先撇开Tomcat组件不谈,首先给我们印象最深刻的一点是:针对接口编程,而非针对具体实现编程(Program to interface, not implementation)。人家老外这点确实值得我们学习。上面的类图中,共有7个类,其余均为接口,这些类无一例外地调用了接口,而非具体的实现类。ServerFactory调用了Server接口,而非StandServer的实现类;Connector类调用了Service接口和Container接口,而没有调用它们的实现类;StandardService类调用了Container接口和Server接口,也同样没有调用它们的实现类。所以我们在编程时,也要贯彻这条原则。
在<<Head First Design Patterns>>一书里,作者举了个非常生动的例子,请看下面三段代码:
a) ? 代码片段一
Dog d=new Dog();
d.bark();
b) ? 代码片段二
Animal animal=new Dog();
animal.makeSound();
c) ? 代码片段三
Animal animal = getAnimal();
animal.makeSound();
作者详细解释了上面第三段代码为什么是最好的,而第二段又为什么比第一段好的道理。东扯西拉这么多,现在我们切入正题。
从上面的类图中,我们可以非常清晰地理解Tomcat的总体架构:
a) ? Server(服务器)是Tomcat构成的顶级构成元素,所有一切均包含在Server中,Server的实现类StandardServer可以包含一个到多个Services;
b) ? 次顶级元素Service的实现类为StandardService调用了容器(Container)接口,其实是调用了Servlet Engine(引擎),而且StandardService类中也指明了该Service归属的Server;
c) ? 接下来次级的构成元素就是容器(Container),主机(Host)、上下文(Context)和引擎(Engine)均继承自Container接口,所以它们都是容器。但是,它们是有父子关系的,在主机(Host)、上下文(Context)和引擎(Engine)这三类容器中,引擎是顶级容器,直接包含是主机容器,而主机容器又包含上下文容器,所以引擎、主机和上下文从大小上来说又构成父子关系,虽然它们都继承自Container接口。
d) ? 连接器(Connector)没有接口(这可是违反了面向接口编程的原则哟!),它直接实现了Http1.1协议。连接器将Service和Container连接起来,首先它需要注册到一个Service,它的作用就是把来自客户端的请求转发到Container(容器),这就是它为什么称作连接器的原因。
下面我们来小结一下,Tomcat的架构从功能的角度,可以分成5个子模块,它们分别是Connector子模块,Jsper子模块,Servlet子模块,Catalina子模块和Resource子模块,每个子模块负责一定的功能;从组件的角度,我们可以看到Tomcat中至少有7个关键组件,它们Server组件、Service组件、Container组件、Connector组件及继承自Container组件的Host组件、Engine组件和Container组件,从UML Class Diagram中,我们可以非常明确地理解它们的包容关系。到此为止,希望我们能对Tomcat的架构有一个比较清晰的认识。