前言

Tomcat是Apache基金组织下的开源项目,性质是一个Web服务器。下面这种情况很普遍:在eclipse床架一个web项目并部署到Tomcat中,启动tomcat,在浏览器中输入一个类似http://localhost:8080/webproject/anyname.jsp的url,然后就可以看到我们写好的jsp页面的内容了。一切都是那么自然和顺理成章,然而这一切都是源于tomcat带给我们的,那么在tomcat背后,这一切又是怎么样发生的呢?带着对tomcat工作原理的好奇心,我决定研究一下tomcat的源码,然而部署源码环境的过程却让我心灰意冷,本着搞不定我还真不信的热情,折腾了一个晚上+一个早上,终于把源码源码环境搭建好了。

为了让文章显得更有条理性,我将从以下几个方面说明Tomcat的工作流程:

  • 搭建Tomcat源码环境指导
  • Tomcat的系统架构
  • Tomcat中的核心组件说明
  • Servlet工作原理
  • 一个例子

Tomcat的系统架构

首先我们从一个宏观的角度来看一下Tomcat的系统的架构:

从这张图中可以看到,Tomcat的核心组件就两个Connector和Container(后面还有详细说明),一个Connector+一个Container构成一个Service,Service就是对外提供服务的组件,有了Service组件Tomcat就可以对外提供服务了,但是光有服务还不行,还得有环境让你提供服务才行,所以最外层的Server就为Service提供了生存的土壤。那么这些个组件到底是干嘛用的呢?Connector是一个连接器,主要负责接收请求并把请求交给Container,Container就是一个容器,主要装的是具体处理请求的组件。Service主要是为了关联Container与Connector,一个单独的Container或者一个单独的Connector都不能完整处理一个请求,只有两个结合在一起才能完成一个请求的处理。Server这是负责管理Service集合,从图中我们看到一个Tomcat可以提供多种服务,那么这些Serice就是由Server来管理的,具体的工作包括:对外提供一个接口访问Service,对内维护Service集合,维护Service集合又包括管理Service的生命周期、寻找一个请求的Service、结束一个Service等。以上就是对Tomcat的核心组件的简要说明,下面我们详细看看每一个组件的执行流程:

Server

上面说Server是管理Service接口的,Server是Tomcat的顶级容器,是一个接口,Server接口的标准实现类是StandardServer类,在Server接口中有许多方法,我们重点关注两个方法:addService()和findService(String)。我们先来看看Server接口的全貌:

接着看看addService()和findService(String)的实现代码:

代码清单1-1:

/**
* Add a new Service to the set of defined Services.
*
* @param service The Service to be added
*/
@Override
public void addService(Service service) { service.setServer(this); synchronized (services) {
Service results[] = new Service[services.length + 1];
System.arraycopy(services, 0, results, 0, services.length);
results[services.length] = service;
services = results; if (getState().isAvailable()) {
try {
service.start();
} catch (LifecycleException e) {
// Ignore
}
} // Report this property change to interested listeners
support.firePropertyChange("service", null, service);
} }

可以看到,Server使用一个数组来管理Service的,每添加一个Service就把原来的Service拷贝到一个新的数组中,再把新的Service放入Service数组中。所以Server与Service是关联在一起的,那么后面的getState().isAvailable()是干嘛的呢?判断状态是否无效,从而决定是否执行service方法。这里说到了状态,就不得不说Tomcat管理各组件生命周期的Lifecycle接口了:

Lifecycle接口

Tomcat中的组件都交给这个接口管理,但是具体组件的生命周期是由包含组件的父容器来管理的,Tomcat中顶级容器管理着Service的生命周期,Service容器又是Connector和Container的父容器,所以这两个组件的生命周期是由Service管理的,Container也有子容器,所以管理着这些子容器的生命周期。这样,只要所有组件都实现了Lifecycle接口,从顶层容器Server开始,就可以控制所有容器的生命周期了。Lifecycle接口中定义了很多状态,在api中详细说明了调用不同方法后的状态转变,同时定义了不同的方法,这些方法在执行后状态会发生相应的改变,在Lifecycle接口中定义了如下方法:

在StandServer中实现了startInernal()方法,就是循环启动StandServer管理的Service的过程,Tomcat的Service都实现了Lifecycle接口,所以被管理的Service都将被通知到,从而执行start()方法,startIntenal()方法是这样的:

代码清单1-2:

/**
* Start nested components ({@link Service}s) and implement the requirements
* of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
*
* @exception LifecycleException if this component detects a fatal error
* that prevents this component from being used
*/
@Override
protected void startInternal() throws LifecycleException { fireLifecycleEvent(CONFIGURE_START_EVENT, null);
setState(LifecycleState.STARTING); globalNamingResources.start(); // Start our defined Services
synchronized (services) {
for (int i = 0; i < services.length; i++) {
services[i].start();
}
}
}

现在所有的Service就会收到通知继而执行start方法。如果一个Service不允许被使用将会抛出一个LifecycleException异常。

stopIntenal()会通知所有Service执行stop方法,具体处理流程与startIntenal()方法类似。这个执行过程涉及一个非常重要的设计模式,就是观察者模式

现在我们已经能够知道了容器通过Lifecycle接口管理容器的生命周期,那么在父容器的状态改变具体是怎么样通知给子容器的呢?回到代码清单1-2,我们注意到有一个fireLifecycleEvent()方法,fireLifecycleEvent()的执行流程如下:

  1. 调用LifecycleBase的fireLifecycleEvent(LifecycleListener listener)方法,LifecycleBase是一个抽象类,实现了Lifecycle接口
  2. 继续调用LifecycleSupport(是一个辅助完成对已经注册监听器的事件通知类,不可被继承,使用final)的fireLifecycleEvent(String type, Object data)方法
  3. 完成事件通知

fireLifecycleEvent(String type, Object data)的方法如下:

代码清单1-3:

/**
* Notify all lifecycle event listeners that a particular event has
* occurred for this Container. The default implementation performs
* this notification synchronously using the calling thread.
*
* @param type Event type
* @param data Event data
*/
public void fireLifecycleEvent(String type, Object data) { LifecycleEvent event = new LifecycleEvent(lifecycle, type, data);
LifecycleListener interested[] = listeners;
for (int i = 0; i < interested.length; i++)
interested[i].lifecycleEvent(event); }

所以,具体事件的通知是由LifecycleListener接口的lifecycleEvent方法完成的,各实现类可以根据不同的情况实现不同的事件监听逻辑

Service

Service是具体提供服务的接口,一个Service包装了Connector和一个Container,在Tomcat中这点是如何实现的呢?Service是一个接口,其标准实现类是StandardService,下面是这两个类的鸟瞰图:

 

这里,我们只关心与Connector和Container最紧密的方法:setContainer()和addConnector()方法,先看一下setContainer()方法的源码:

代码清单2-1:

/**
* Set the <code>Container</code> that handles requests for all
* <code>Connectors</code> associated with this Service.
*
* @param container The new Container
*/
@Override
public void setContainer(Container container) { Container oldContainer = this.container;
if ((oldContainer != null) && (oldContainer instanceof Engine))
((Engine) oldContainer).setService(null);
this.container = container;
if ((this.container != null) && (this.container instanceof Engine))
((Engine) this.container).setService(this);
if (getState().isAvailable() && (this.container != null)) {
try {
this.container.start();
} catch (LifecycleException e) {
// Ignore
}
}
if (getState().isAvailable() && (oldContainer != null)) {
try {
oldContainer.stop();
} catch (LifecycleException e) {
// Ignore
}
} // Report this property change to interested listeners
support.firePropertyChange("container", oldContainer, this.container); }

从代码中可以看到这个方法主要的任务是设置一个Container容器来处理一个或者多个Connector传送过来的请求。首先判断当前的Service是否已经关联了Container容器,如果已经关联了就去除这个关联关系。如果原来的Container容器已经启动了就终止其生命周期,结束运行并设置新的关联关系,这个新的Container容器开始新的生命周期。最后把这个过程通知给感兴趣的事件监听程序。

下面看看addConnector的方法:

代码清单2-2:

/**
* Add a new Connector to the set of defined Connectors, and associate it
* with this Service's Container.
*
* @param connector The Connector to be added
*/
@Override
public void addConnector(Connector connector) { synchronized (connectors) {
connector.setService(this);
Connector results[] = new Connector[connectors.length + 1];
System.arraycopy(connectors, 0, results, 0, connectors.length);
results[connectors.length] = connector;
connectors = results; if (getState().isAvailable()) {
try {
connector.start();
} catch (LifecycleException e) {
log.error(sm.getString(
"standardService.connector.startFailed",
connector), e);
}
} // Report this property change to interested listeners
support.firePropertyChange("connector", null, connector);
} }

执行过程也比较清楚:用一个同步代码块包住connectors数组,首先设置connector与container和service的关联关系,然后让connector开始新的生命周期,最后通知感兴趣的事件监听程序。注意到Connector的管理和Server管理Service一样都使用了数组拷贝并把新的数组赋给当前的数组,从而间接实现了动态数组。之所以使用数组我想可能是出于性能的考虑吧。

http://blog.csdn.net/u011116672/article/details/50993980

深入理解Tomcat系列之一:系统架构(转)的更多相关文章

  1. 【Tomcat】Tomcat原理与系统架构

    目录 版本: 一,目录说明 二,浏览器访问服务器的流程 三,Tomcat系统总体架构 3.1 Tomcat请求的大致流程 3.2 Servlet容器处理请求流程 3.3 Tomcat系统总体架构 四, ...

  2. 深入理解Tomcat系列之二:源码调试环境搭建(转)

    前言 最近对Tomcat的源码比较感兴趣,于是折腾了一番.要调试源码首先需要搭建环境,由于参考了几篇帖子发现都不怎么靠谱,最后还是折腾出来了,然而却花了足足一天的时间去搭建这个环境.发现都不是帖子的问 ...

  3. 深入理解Tomcat系列之五:Context容器和Wrapper容器

    前言 Context容器是一个Web项目的代表,主要管理Servlet实例,在Tomcat中Servlet实例是以Wrapper出现的.如今问题是怎样才干通过Context容器找到详细的Servlet ...

  4. Tomcat 系统架构与设计模式,第 2 部分: 设计模式分析(转载)

    简介: 这个分为两个部分的系列文章研究了 Apache Tomcat 服务器的系统架构以及其运用的很多经典设计模式.第 1 部分 分析了 Tomcat 的工作原理,第 2 部分将分析 Tomcat 中 ...

  5. tomcat 系统架构与设计模式 第二部分 设计模式 转

    Tomcat 系统架构与设计模式,第 2 部分: 设计模式分析 许 令波, Java 开发工程师, 淘宝网 许令波,现就职于淘宝网,是一名 Java 开发工程师.对大型互联网架构设计颇感兴趣,并对一些 ...

  6. 【Tomcat】Tomcat 系统架构与设计模式,第 2 部分: 设计模式分析

    这个分为两个部分的系列文章研究了 Apache Tomcat 服务器的系统架构以及其运用的很多经典设计模式.第 1 部分 分析了 Tomcat 的工作原理,第 2 部分将分析 Tomcat 中运用的许 ...

  7. Tomcat 系统架构与设计模式之二

    Tomcat 系统架构与设计模式,第 2 部分: 设计模式分析 来自:http://www.ibm.com/developerworks/cn/java/j-lo-tomcat2/ 这个分为两个部分的 ...

  8. 从需求的角度去理解Linux系列:总线、设备和驱动

    笔者成为博客专家后整理以前原创的嵌入式Linux系列博文,现推出以让更多的读者受益. <从需求的角度去理解linux系列:总线.设备和驱动>是一篇有关如何学习嵌入式Linux系统的方法论文 ...

  9. Tomcat详解系列(2) - 理解Tomcat架构设计

    Tomcat - 理解Tomcat架构设计 前文我们已经介绍了一个简单的Servlet容器是如何设计出来,我们就可以开始正式学习Tomcat了,在学习开始,我们有必要站在高点去看看Tomcat的架构设 ...

随机推荐

  1. ios基础-编程规范

    养成良好的编程习惯,是開始一门新语言的首要前提. (一)文档结构规范 1.建立Libs目录,存放第三方库 2.建立Tools目录,存放自己封装的类库 3.建立ViewControllers目录,存放全 ...

  2. MYSQL 命令行导入导出

    1.导出mysql数据库 mysqldump -h[服务器地址本机可以忽略] -u[用户] -p[密码] -P[myql数据库端口]>导出文件位置 mysqldump -h192. -uroot ...

  3. perl 获取文件内容里第一个AAA和最后一个AAA

    <pre name="code" class="html">[root@wx03 ~]# cat -n aaa 1 3`13 2 edqae 3 d ...

  4. 【Demo 0006】Android 组件(Activity)

    本章学习要点:        1.  了解Activity基本概念;        2.  掌握Activity生命周期:        3.  掌握 Activity之间跳转:  

  5. 让程序在崩溃时体面的退出之Dump文件

             在我的那篇<让程序在崩溃时体面的退出之CallStack>中提供了一个在程序崩溃时得到CallStack的方法.但是要想得到CallStack,必须有pdb文件的支持.但 ...

  6. Python中字符串的方法及注释

    先整理到这里:用的时候便于查询.相当于自己的资料库吧. capitalize()   把字符串的第一个字符改为大写 casefold()   把整个字符串的所有字符改为小写 center(width) ...

  7. c#1所搭建的核心基础之类型系统的特征

    类型系统的特征简介 几乎每种编程语言都有某种形式的一个类型系统.类型系统大致被分为:强/弱,安全/不安全,静态/动态,显式/隐式等类型. c#在类型系统世界中的位置 c#1的类型系统是静态的.显式的和 ...

  8. 【Cocos2d-X游戏实战开发】捕鱼达人之开发前准备工作(一)

    本系列学习教程使用的是cocos2d-x-2.1.4(最新版为cocos2d-x-2.1.5) 博主发现前两个系列的学习教程被严重抄袭,在这里呼吁大家请尊重开发者的劳动成果, 转载的时候请务必注明出处 ...

  9. CAS (1) —— Mac下配置CAS到Tomcat(服务端)(转)

    tomcat版本: tomcat-8.0.29 jdk版本: jdk1.8.0_65 cas版本: cas4.1.2cas-client-3.4.1 参考来源: CAS实现单点登录(SSO)经典完整教 ...

  10. xvfb 初步探究

    有时候我们不关注程序是否有界面(比如自动化测试),只要程序在运行就可以了 很感谢 xvfb 这个工具给我们提供了相关的功能 比如在没有 X server 的机器上运行 gedit, 可以用下面的命令 ...