ITPub博客

首页 > 应用开发 > Java > 由远到近看Tomcat系统架构分析

由远到近看Tomcat系统架构分析

原创 Java 作者:忙碌的程序员 时间:2018-09-27 22:39:16 0 删除 编辑

Tomcat的结构很复杂,但是Tomcat也非常的模块化,找到了Tomcat最核心的模块,就抓住了Tomcat的 七寸

整体结构

Tomcat 总体结构图


从上图中可以看出Tomcat的心脏是两个组件:Connector 和 Container,关于这两个组件将在后面详细介绍。Connector 组件是可以被替换,这样可以提供给服务器设计者更多的选择,因为这个组件是如此重要,不仅跟服务器的设计的本身,而且和不同的应用场景也十分相关,所以一个Container 可以选择对应多个Connector。多个Connector和一个Container 就形成了一个Service,Service 的概念大家都很熟悉了,有了Service 就可以对外提供服务了,但是Service还要一个生存的环境,必须要有人能够给她生命、掌握其生死大权,那就非Server莫属了。所以整个Tomcat的生命周期由Server控制。

以Service  作为“婚姻”

我们将 Tomcat 中 Connector、Container 作为一个整体比作一对情 侣的话,Connector主要负责对外交流,可以比作为 Boy,Container 主要处理 Connector 接受的请求,主要是处理内部事务,可以比作为 Girl。那么这个 Service就是连接这对男女的结婚证了。是Service将它们连接在一起,共同组成一个家庭。当然要组成一个家庭还要很多其它的元素。

说白了,Service 只是在Connector 和 Container外面多包一层,把它们组装在一起,向外面提供服务,一个Service可以设置多个Connector,但是只能有一个 Container 容器。这个 Service 接口的 方法列表如下:

①Service接口

从 Service接口中定义的方法中可以看出,它主要是为了关联Connector和 Container,同时会初始化它下面的其它组件,注意接 口中它并没有规定一定要控制它下面的组件的生命周期。所有组件的 生命周期在一个 Lifecycle 的接口中控制,这里用到了一个重要的设 计模式,关于这个接口将在后面介绍。

Tomcat 中 Service接口的标准实现类是StandardService它不仅实现了 Service 借口同时还实现了 Lifecycle 接口,这样它就可以控 制它下面的组件的生命周期了。StandardService 类结构图如下:

②StandardService的类结构图

从上图中可以看出除了 Service接口的方法的实现以及控制组件生命周期的 Lifecycle 接口的实现,还有几个方法是用于在事件监听的 方法的实现,不仅是这个 Service 组件,Tomcat 中其它组件也同样 有这几个方法,这也是一个典型的 ,将在后面介绍。

下面看一下 StandardService 中主要的几个方法实现的代码,下面是setContainer和addConnector 方法的源码:

③StandardService. SetContainer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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 (started && ( this .container != null ) && ( this .container instanceof Lifecycle))
 
{
 
try {
 
((Lifecycle) this .container).start();
 
} catch (LifecycleException e) {
 
;
 
}
 
}
 
synchronized (connectors) {
 
for ( int i = ; i < connectors.length; i++)
 
connectors[i].setContainer( this .container);
 
}
 
if (started && (oldContainer != null ) && (oldContainer instanceof Lifecycle)) {
 
try {
 
((Lifecycle)  oldContainer).stop();
 
} catch (LifecycleException e) {
 
;
 
}
 
}
 
support.firePropertyChange( "container" , oldContainer, this .container);
—————————————————————————————
}

这段代码很简单,其实就是先判断当前的这个 Service 有没有已经关 联了 Container,如果已经关联了,那么去掉这个关联关系 —— oldContainer.setService(null)。如果这个oldContainer 已经被启动 了,结束它的生命周期。然后再替换新的关联、再初始化并开始这个新的 Container 的生命周期。最后将这个过程通知感兴趣的事件监听程序。这里值得注意的地方就是,修改Container 时要将新的 Container关联到每个Connector,还好Container 和 Connector 没有双向关联,不然这个关联关系将会很难维护。

④StandardService. addConnector

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public void addConnector(Connector connector) {
 
synchronized (connectors) {
 
connector.setContainer( this .container);
 
connector.setService( this );
 
Connector results[] = new Connector[connectors.length + 1 ];
 
System.arraycopy(connectors, , results, , connectors.length);
 
results[connectors.length] = connector;
 
connectors = results;
 
if (initialized) {
 
try {
 
connector.initialize();
 
} catch (LifecycleException e) {
 
e.printStackTrace(System.err);
 
}
 
}
 
if (started && (connector instanceof Lifecycle)) {
 
try {
 
((Lifecycle) connector).start();
 
} catch (LifecycleException e) {
 
;
 
}
 
}
 
support.firePropertyChange( "connector" , null , connector);
 
}
 
}

上面是 addConnector 方法,这个方法也很简单,首先是设置关联关 系,然后是初始化工作,开始新的生命周期。这里值得一提的是,注 意 Connector 用的是数组而不是 List集合,这个从性能角度考虑可 以理解,有趣的是这里用了数组但是并没有向我们平常那样,一开始 就分配一个固定大小的数组,它这里的实现机制是:重新创建一个当 前大小的数组对象,然后将原来的数组对象 copy 到新的数组中,这 种方式实现了类似的动态数组的功能,这种实现方式,值得我们以后 拿来借鉴。

最新的 Tomcat6 中 StandardService也基本没有变化,但是从Tomcat5 开始Service、Server 和容器类都继承了MBeanRegistration接口,Mbeans 的管理更加合理。

以 Server  为“居”

前面说一对情侣因为 Service 而成为一对夫妻,有了能够组成一个家 庭的基本条件,但是它们还要有个实体的家,这是它们在社会上生存 之本,有了家它们就可以安心的为人民服务了,一起为社会创造财富。

Server要完成的任务很简单,就是要能够提供一个接口让其它程序能够访问到这个Service 集合、同时要维护它所包含的所有 Service 的生命周期,包括如何初始化、如何结束服务、如何找到别人要访问的 Service。还有其它的一些次要的任务,如您住在这个地方要向当 地政府去登记啊、可能还有要配合当地公安机关日常的安全检查什么 的。

Server的类结构图如下:

①Server的类结构图

它的标准实现类 StandardServer 实现了上面这些方法,同时也实现 了Lifecycle、MbeanRegistration 两个接口的所有方法,下面主要看 一下 StandardServer重要的一个方法 addService的实现:

②StandardServer.addService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public void addService(Service service) {
 
service.setServer( this );
 
synchronized (services) {
 
Service results[] = new Service[services.length + 1 ];
 
System.arraycopy(services, , results, , services.length);
 
results[services.length] = service;
 
services = results;
 
if (initialized) {
 
try {
 
service.initialize();
 
} catch (LifecycleException e) {
 
e.printStackTrace(System.err);
 
}
 
}
 
if (started && (service instanceof Lifecycle)) {
 
try {
 
((Lifecycle) service).start();
 
} catch (LifecycleException e) {
 
;
 
}
 
}
 
support.firePropertyChange( "service" , null , service);
 
}
 
}

从上面第一句就知道了 Service和 Server是相互关联的,Server也是和 Service 管理 Connector 一样管理它,也是将 Service 放在 一个数组中,后面部分的代码也是管理这个新加进来的 Service 的生 命周期。Tomcat6 中也是没有什么变化的。

组件的生命线“Lifecycle”

前面一直在说 Service 和 Server 管理它下面组件的生命周期,那它 们是如何管理的呢?

  程序员学习交流群: ,欢迎一到五年的工程师加入,合理利用自己每一分每一秒的时间来学习提升自己,不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代!

Tomcat 中组件的生命周期是通过Lifecycle 接口来控制的,组件只 要继承这个接口并实现其中的方法就可以统一被拥有它的组件控制 了,这样一层一层的直到一个最高级的组件就可以控制 Tomcat 中 所有组件的生命周期,这个最高的组件就是 Server,而控制Server的是 Startup,也就是您启动和关闭Tomcat。

下面是 Lifecycle 接口的类结构图:

①Lifecycle类结构图

除了控制生命周期的 Start 和 Stop 方法外还有一个监听机制,在生命周期开始和结束的时候做一些额外的操作。这个机制在其它的框架中也被使用,如在Spring 中。关于这个设计模式会在后面介绍。

Lifecycle接口的方法的实现都在其它组件中,就像前面中说的,组件的生命周期由包含它的父组件控制,所以它的 Start 方法自然就是调用它下面的组件的 Start 方法,Stop 方法也是一样。如在 Server 中 Start 方法就会调用Service组件的 Start方法,Server 的 Start方法代码如下:

②StandardServer.Start

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public void start() throws LifecycleException {
 
if (started) {
 
log.debug(sm.getString( "standardServer.start.started" ));
 
return ;
 
}
 
lifecycle.fireLifecycleEvent(BEFORE_START_EVENT,  null );
 
lifecycle.fireLifecycleEvent(START_EVENT,  null );
 
started = true ;
 
synchronized (services) {
 
for ( int i = ; i < services.length; i++) {
 
if (services[i] instanceof Lifecycle)
 
((Lifecycle) services[i]).start();
 
}
 
}
 
lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null );
 
}

监听的代码会包围Service组件的启动过程,就是简单的循环启动所有Service组件的Start方法,但是所有Service必须要实现Lifecycle接口,这样做会更加灵活。

Server的 Stop 方法代码如下:

③StandardServer.Stop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public void stop() throws LifecycleException {
 
if (!started)
 
return ;
 
lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null );
 
lifecycle.fireLifecycleEvent(STOP_EVENT,  null );
 
started = false ;
 
for ( int i = ; i < services.length; i++) {
 
if (services[i] instanceof Lifecycle)
 
((Lifecycle) services[i]).stop();
 
}
 
lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null );
 
}

它所要做的事情也和Start方法差不多。

Connector组件

Connector组件是Tomcat中两个核心组件之一,它的主要任务是负责接收浏览器的发过来的tcp连接请求,创建个Request 和处理这个请求并把产生的Request 和 Response对象传给处理这个请求的线程,处理这个请求的线程就是Container 组件要做的事了。

由于这个过程比较复杂,大体的流程可以用下面的顺序图来解释:

①Connector处理一次请求顺序图

Tomcat5 中默认的 Connector 是 Coyote,这个 Connector 是可以选择替换的。Connector 最重要的功能就是接收连接请求然后分配线 程让 Container 来处理这个请求,所以这必然是多线程的,多线程的处理是 Connector 设计的核心。Tomcat5将这个过程更加细化,它将 Connector划分成 Connector、Processor、Protocol, 另外Coyote也定义自己的Request 和 Response对象。

下面主要看一下 Tomcat 中如何处理多线程的连接请求,先看一下Connector的主要类图:

② Connector的主要类图

看一下HttpConnector的Start 方法:

③HttpConnector.Start

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public void start() throws LifecycleException {
 
if (started)
 
throw new LifecycleException
 
(sm.getString( "httpConnector.alreadyStarted" ));
 
threadName = "HttpConnector[" + port + "]" ;
 
lifecycle.fireLifecycleEvent(START_EVENT,  null );
 
started = true ;
 
threadStart();
 
while (curProcessors < minProcessors) {
 
if ((maxProcessors > ) && (curProcessors >= maxProcessors))
 
break ;
 
HttpProcessor processor = newProcessor();
 
recycle(processor);
 
}
 
}

threadStart()执行就会进入等待请求的状态,直到一个新的请求到来才会激活它继续执行,这个激活是在HttpProcessor 的 assign 方法中,这个方法是代码如下 :

④ HttpProcessor.assign

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
synchronized void assign(Socket socket) {
 
while (available) {
 
try {
 
wait();
 
} catch (InterruptedException e) {
 
—————————————————————————————
}
 
}
 
this .socket = socket;
 
available = true ;
 
notifyAll();
 
if ((debug >= 1 ) && (socket != null ))
 
log( " An incoming request is being assigned" );
 
}

创建 HttpProcessor 对象是会把 available 设为 false,所以当请求 到来时不会进入 while循环,将请求的socket 赋给当期处理的 socket,并将 available设为true,当 available设为true 是 HttpProcessor的 run方法将被激活,接下去将会处理这次请求。

Run方法代码如下:

⑤HttpProcessor.Run

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public void run() {
 
while (!stopped) {
 
Socket socket = await();
 
if (socket == null )
 
continue ;
 
try {
 
process(socket);
 
} catch (Throwable t) {
 
log( "process.invoke" , t);
 
}
 
connector.recycle( this );
 
}
 
—————————————————————————————
synchronized (threadSync) {
 
threadSync.notifyAll();
 
}
 
}

解析 socket 的过程在 process 方法中,process 方法的代码片段如 下:

⑥HttpProcessor.process

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31