深入理解Tomcat

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解Tomcat相关的知识,希望对你有一定的参考价值。

参考技术A

1、先大概介绍Tomcat,再介绍两个核心组件连接器和容器。
2、连接器方面,可以通过三个组件:Endpoint、Processor、Adapter去讲连接器是如何屏蔽底层网络通信细节。
3、容器方面,可以通过 Engine、Host、Context、Wrapper四种容器去讲如何运用了组合设计模式的思想,实现自由添加节点。

Tomcat 的本质其实就是 一个 WEB 服务器 + 一个 Servlet 容器 ,那么它必然需要处理 网络的连接与 Servlet 的管理

因此,Tomcat 设计了两个核心组件来实现这两个功能,分别是 连接器和容器 ,连接器用来处理外部网络连接,容器用来处理内部 Servlet,我用一张图来表示它们的关系:

一个 Tomcat 代表一个 Server 服务器,一个 Server 服务器可以包含多个 Service 服务,Tomcat 默认的 Service 服务是 Catalina,而 一个 Service 服务可以包含多个连接器,因为 Tomcat 支持多种网络协议,包括 HTTP/1.1、HTTP/2、AJP 等等,一个 Service 服务还会包括一个容器,容器外部会有一层 Engine 引擎所包裹,负责与处理连接器的请求与响应,连接器与容器之间通过 ServletRequest 和 ServletResponse 对象进行交流。

连接器负责将各种网络协议封装起来,对外部屏蔽了网络连接与 IO 处理的细节,将处理得到的 Request 对象传递给容器处理。

Tomcat 将处理请求的细节封装到 ProtocolHandler,ProtocolHandler 是一个接口类型,通过实现 ProtocolHandler 来实现各种协议的处理

ProtocolHandler 采用组件模式的设计,将处理网络连接,字节流封装成 Request 对象,再将 Request 适配成 Servlet 处理 ServletRequest 对象这几个动作,用组件封装起来了,

以上连接器的各个组件,我用一张图说明它们直接的关系:

在 Tomcat 中一共设计了 4 种容器,它们分别为 Engine、Host、Context、Wrapper,它们的关系如下图所示:

各个容器组件之间的关系是由大到小,即父子关系,它们之间关系形成一个树状的结构。

Tomcat 的这种容器设计思想,其实是运用了组合设计模式的思想,组合设计模式最大的优点是可以自由添加节点,这样也就使得 Tomcat 的容器组件非常地容易进行扩展,符合设计模式中的开闭原则。

从容器的组合关系可以看出,它们调用顺序必定是:

定位 Servlet 的流程图:

[图片上传失败...(image-8a1b99-1576409021968)]

Mapper 最主要的核心功能是保存容器组件之间访问路径的映射关系

深入理解Tomcat系列之一:系统架构

前言:

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:

/**

}

可以看到,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:

/**

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

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

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

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

/**

}

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

下面看看addConnector的方法:

代码清单2-2:

/**

}

总结:

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





以上是关于深入理解Tomcat的主要内容,如果未能解决你的问题,请参考以下文章

深入理解Tomcat

深入理解Tomcat系列之一:系统架构

深入分析理解Tomcat体系结构-读书笔记

深入理解Tomcat整体架构和处理请求流程

通过一次生产case深入理解tomcat线程池

深入理解NIO