tomcat源码学习

来源:互联网 发布:考研真相与黄皮书知乎 编辑:程序博客网 时间:2024/06/06 08:26

本文仅为自己学习tomcat7源码过程中理解的一些笔记,并不是详细分析,仅供参考


读源码时首先要大致理解各个核心模块之间的关系,核心架构如下图,一图胜千言



下面是tomcat的运行流程的概括,需要编译tomcat源码后debug一步步配合源码来看。

org.apache.catalina.startup.Bootstrap是tomcat的启动类,里面的main方法是整个项目的入口,main方法主要做了三件事:

1.初始化:bootstrap.init()。其中,setCatalinaHome()和setCatalinaBase()分别设置catalina.home和catalina.base的系统属性,然后initClassLoaders()初始化三个classLoader:commonLoader用于加载common/lib目录下的jar包和class文件;catalinaLoader用于加载server/lib目录下的jar包和class文件;sharedLoader用于加载shared/lib目录下的jar包和class文件。commonLoader是catalinaLoader和sharedLoader的parent classloader,Tomcat6以后,查看catalina.properties文件可以看见server.loader和shared.loader都是为空的。接下来创建org.apache.catalina.startup.Catalina这个类的实例并赋给catalinaDaemon。

2.装配容器:daemon.load(args)。利用反射调用org.apache.catalina.startup.Catalina的load方法来实现。load方法首先initDirs()初始化目录和命名规则,为digester解析XML做准备。createStartDigester()创建一个digester对象,设置xml的解析规则,具体见Digester解析xml文件。然后将server.xml转化成org.xml.sax.InputSource后调用digester.parse(inputSource)进行解析。server.xml文件解析完后,会生成org.apache.catalina.core包中的各种StandardXXX类的实例,比如StandardServer、StandardService、StandardEngine等等。最后getServer().init()实际上调用了StandardServer类的initInternal方法,对StandardServer对象进行初始化,这个方法先是注册了MBean,让JMX管理tomcat容器,再遍历Server中所有的Service调用services[i].init()初始化Service,Service的初始化也是在StandardService类的initInternal方法中完成的,其中先初始化了容器container.init(),这里的容器指的是StandardEngine,即会调用StandardEngine的initInternal方法,再初始化Executor,最后初始化两个连接器[Connector[HTTP/1.1-8080], Connector[AJP/1.3-8009]]。

3.启动容器:daemon.start()。利用反射调用org.apache.catalina.startup.Catalina的start方法来实现。start方法直接条用getServer().start()方法,按照装配容器的初始化顺序,依次调用StandardServer、StandardService、StandardEngine、StandardHost、StandardContext、StandardWrapper的start或者startInternal方法来完成启动过程,最后会循环等待,在关闭命令到来之前一直运行。

注:1、tomcat中大量引入了模板方法模式,org.apache.catalina.util.LifecycleBase被所有容器继承,其中initInternal和startInternal方法是抽象方法,在初始化和启动时被每个容器会调用其init和start方法,这两个方法会去调用对应抽象方法***Internal,这些抽象方法都是在子类中实现的。2、tomcat中大量使用了类的反射,基本上都是通过配置文件获取类名,然后利用反射得到class对象,再通过newInstance获取对象。以下是模块间的关系图:


下面说明以下最上图中的pipeline和valve对象。四大容器类StandardEngine,StandardHost,StandardContext及StandardWrapper都有各自缺省的标准valve实现。容器类生成对象时,都会生成一个pipeline对象,同时,生成一个缺省的valve实现,并将这个标准的valve对象绑定在其pipeline对象上。Pipeline就像是每个容器的逻辑总线。在pipeline上按照配置的顺序,加载各个valve。通过pipeline完成各个valve之间的调用,各个valve实现具体的应用逻辑。从StandardPipeline 的addValve方法中可以看到,每添加一个valve,就将其添加到Valve链表中,并且每个容器的标准valve在链表的尾端。下图是四大容器的标准valve的调用逻辑图,此图只是在缺省配置下的状态,也就是说每个pipeline只包含一个标准valve的情况:


容器Valve的调用方式如:host.getPipeline().getFirst().invoke(request, response),从StandardEngineValve开始,一直到StandardWrapperValve,完成整个消息处理过程。注意每一个上层的valve都是在调用下一层的valve返回后再返回的,这样每个上层valve不仅具有request对象,同时还能拿到response对象。


最后要理一理的是Connector,连接器主要是接收、解析http请求,然后封装请求传递给容器处理。整个connector实现了从接收socket到调用servlet的全部过程。先来看一下connector的功能逻辑:1.接收socket。2.从socket获取数据包,并解析成HttpServletRequest对象。3.从engine容器开始走调用流程,经过各层valve,最后调用servlet完成业务逻辑。4.返回response,关闭socket。

可以看出,整个connector组件是tomcat运行主干,之前介绍的各个模块都是tomcat启动时,静态创建好的,通过connector将这些模块串了起来。 通常在实际运行中,特别是对于一些互联网应用而言,网络吞吐一直是整个服务的瓶颈所在,因此,connector的运行效率在一定程度上影响了tomcat的整体性能。相对来说,tomcat在处理静态页面方面一直有一些瓶颈,因此通常的服务架构都是前端类似nginx的web服务器,后端挂上tomcat作为应用服务器(当然还有些其他原因,例如负载均衡等)。Tomcat在connector的优化上做了一些特殊的处理,这些都是可选的,通过部署,配置方便完成,例如APR(Apache Portable Runtime),BIO,NIO等。 目前connector支持的协议是HTTP和AJP。AJP是Apache与其他服务器之间的通信协议。通常在集群环境中,例如前端web服务器和后端应用服务器或servlet容器,使用AJP会比HTTP有更好的性能。

下面主要介绍缺省状态(BIO)下HTTP connector的架构及其消息流,看下图:


可见connector分为三大模块:Http11Protocol、Mapper、CoyoteAdapter。

Http11Protocol类全路径为org.apache.coyote.http11.Http11Protocol,这是支持http的BIO实现。 Http11Protocol包含了JIoEndpoint对象及Http11ConnectionHandler对象。 Http11ConnectionHandler对象维护了一个Http11Processor对象池,Http11Processor对象会调用CoyoteAdapter完成http request的解析和分派。 JIoEndpoint维护了两个线程池,Acceptor及Worker。Acceptor是接收socket,然后从Worker线程池中找出空闲的线程处理socket,如果worker线程池没有空闲线程,则Acceptor将阻塞。Worker是典型的线程池实现。Worker线程拿到socket后,就从Http11Processor对象池中获取Http11Processor对象,进一步处理。

CoyoteAdapter全路径org.apache.catalina.connector.CoyoteAdapter,此对象负责将http request解析成HttpServletRequest对象,之后绑定相应的容器,然后从engine开始逐层调用valve直至该servlet。

Mapper类全路径org.apache.tomcat.util.http.mapper.Mapper,此对象维护了一个从Host到Wrapper的各级容器的快照。它主要是为了,当http request被解析后,能够将http request绑定到相应的servlet进行业务处理。在加载各层容器时,会将它们注册到JMX中。所以当connector组件启动的时候,会从JMX中查询出各层容器,然后再创建这个Mapper对象中的快照。Mapper对象在tomcat中存在于两个地方:1、每个context容器对象中,它只记录了此context内部的访问资源与相对应的wrapper子容器的映射;2、connector模块中,这是tomcat全局的变量,它记录了一个完整的映射对应关系,即根据访问的完整URL如何定位到哪个host下的哪个context的哪个wrapper容器。Servlet中forward跳转会用到第一种mapper,也就是说forward是服务器内部的重定向,其他对servlet的访问都用第二种mapper。如下图:



在前面Digester解析xml文件的时候,即构造Connector实例时就会调用Connector的构造方法,这个构造方法首先设置protocol,然后实例化protocolhandler(org.apache.coyote.http11.Http11Protocol)。到后面连接器的初始化则是调用Connector的initInternal方法,该方法首先为protocolHandler设置一个adapter,然后初始化protocolHandler,最后初始化mapperListener,mapperListener在初始化的时候会调用mapper对象的addXXX方法。由于Tomcat的生命周期控制,连接器的启动过程和初始化过程几乎一样,也是由Catalina的start方法开始,Server启动,Service启动,Container启动,Connector启动。Connector启动调用了它的startInternal方法,这个方法只做了两件事:启动protocolHandler和mapperListener。protocolHandler的初始化是在其父类AbstractProtocol的start方法中完成的,其中调用了endpoint.start(),由于默认使用的是Http11Protocol,因此调用的是JioEndpoint的startInternal方法,该方法启动Acceptor及Worker线程池。mapperListener也继承了LifecycleMBeanBase类,也是受生命周期控制的,所以它的启动是在startInternal方法中完成的。mapperListener会从Engine开始,为各层容器添加监听器,以及为各层容器建立映射关系。


参考:http://gearever.iteye.com/blog/1545250、http://blog.csdn.net/aesop_wubo/article/category/1156753

0 0
原创粉丝点击