本文基本全部借鉴了这篇大佬的文章 一万字深度剖析Tomcat源码
- 当我们使用浏览器向摸一个网站发起一个HTTP格式的请求,name作为HTTP服务器收到这个请求之后,会调用具体的程序(Java类)进行处理 那么问题来了,当一个请求过来,HTTP服务器怎么知道要调用那个Java类的哪个方法呢。 当然,我们可以直接在HTTP服务器代码里直接进行处理,也就是根据请求路径等信息进行一堆的 if else 判断,但是这样做缺陷和明显, 因为HTTP服务器的代码跟后端业务处理逻辑耦合在一起了,不符合软件开发设计的原则,并且不利于系统的扩展性
Servlet容器
- 针对这种耦合问题最好的结局办法是面向接口编程,于是我们定义一个Servlet接口,所有的业务类都必须实现这个接口 但是我们还是没有解决一个请求到来时HTTP服务器如何知道有哪个Servletl来处理,并且这些自定义的Servlet也需要进行加载和管理 于是Servlet容器就被发明出来了,Servlet容器作为HTTP服务器和具体业务类进行交互的桥梁,HTTP服务器将请求交由Servlet容器去 处理,而Servlet容器则负责将请求转发到具体的Servelt,并且调用Servlet的方法进行业务处理,他们之间的调用通过Servlet接口 进行解耦
- 其实Servlet接口和Servlet容器并不是Tomcat发明的,而是在JAVAEE API中定义的,我们也把这一整套内容叫做Servlet规范。 有了这套规范之后,如果我们要实现新的业务功能,只需要实现一个Servlet,并且把它注册到Servlet容器中,剩下的事情就需要 由Tomcat帮我们处理
Tomcat Servlet 容器工作流程
- 当用户请求某个URL资源时: 1.HTTP服务器会把请求信息使用ServletRequest对象封装起来 2.进一步去调用Servlet容器中某个具体的Servlet 3.在第二部中当Servlet容器拿到请求后,会根据URL和Servlet的映射关系,找到相应的Servlet 4.如果Servlet还没有被加载,就使用反射机制创建这个Servlet,并调用Servlet的init()方法来完成初始化 5.接着调用这个具体的Servlet的service方法来处理请求,请求处理结构使用ServletResponse对象封装 6.把ServletResponse对象返回给HTTP服务器,HTTP服务器会把响应发送给客户端
Web 服务器
- 作为一个Web服务器,Tomcat要实现两个非常核心的功能:
- HTTP服务器功能: 进行Socket通信(基于TCP/IP),解析HTTP报文
- Servlet容器功能: 加载和管理Servlet,由于Servlet具体负责处理Request请求
Tomcat组件设计
- 为了完成上述两个功能,我们设计两个核心组件连接器(Connector)和容器(Container)来分别做这两件事情 连接器负责对外交流(完成Http服务器功能),容器负责内部处理(完成Servlet容器功能) 连接器既然负责外部交流,就免不了进行socket通信,说到socket通信,就涉及到了网络IO模型,那么 网络IO模型是可变的、多种多样的,因此一个容器可能对接多个连接器,这就好比一个城市有有高铁站,火车站,飞机场,码头。 你可以坐火车、高铁、飞机、轮船来进入这座城市,我们将连接器或者容器组装起来之后的这个整体命名为Service组件 连接器和容器两者之间是通过标准的ServletRequest和ServletResponse通信,这样连接器对Servlet容器就屏蔽了网络协议 以及I/O模型等的区别
连接器的设计
- 首先我们要分析出连接器需要完成一下三个核心功能:
- socket通信
- 解析处理应用层协议,封装成一个Request对象
- 将Request转换为ServletRequest 将Response转换为ServletResponse
- 我们设计三个组件EndPoint、Processor、Adapter来对应完成上述三项功能 这三个组件之间通过抽象接口进行交互,从一个请求的正向流程来看, Endpoint 负责提供请求字节流给Processor, Processor负责提供Tomcat定义的的Request对象给Adapter Adapter负责提供标准的ServletRequest对象给Servlet容器
- 首先来说一下Adapter组件,连接器需要对接的是标准的Servlet容器,既然是Servlet容器 那就应该遵循Servlet规范,也就是说在Servlet的service方法中只能接受标准的ServletRequest 和ServletResponse,而连接器负责对外交流,只能将基础的请求信息封装成一个Request对象,这是我们就需要 一个转换器,遇到这种需求我们通常会采用适配器模式。因此我们设计了一个CoyoteAdapter类 并提供一个service方法工连接器调用,内部则调用容器的service
- 然后是EndPoint组件和Processor组件,这两个一个负责对接I/O模型,一个负责对接应用层协议,都是会变化的 并且可以自由组合
- 针对这样的组合的场景我们可以这么来设计,首先这两者可以作为一个整体,
最终的目的就是将请求信息转为一个统一的Request对象,因此我们可以设计
一个ProtocolHandler接口来封装这两种变化点
容器的设计
容器部分我们设计了4种容器,分别是Engine、Host、Context、Wrapper。这四种容器是父子管理,整体形成一个分层结构,如下图所示
首先说明一下这四种容器的作用:
-
Engine
表示整个Catalina的Servlet引擎,用来管理多个虚拟站点,一个Service最多只能有一个Engine,但是一个引擎可包含多个Host
-
Host
代表一个虚拟主机,或者说一个站点,可以给 Tomcat 配置多个虚拟主机地址,而一个虚拟主机下可包含多个 Context
- Context
表示一个 Web 应用程序,一个Web应用可包含多个 Wrapper
- Wrapper
表示一个Servlet,负责管理整个Servlet的生命周期,包括装载、初始化、资源回收等
Catalina
Catalina是Tomcat中的一个组件,它负责的是解析Tomcat的配置文件,以此来创建服务器Server组件并进行管理
因此也可以认为整个 Tomcat 就是一个 Catalina 实例,Tomcat 启动的时候会初始化这个实例,Catalina 实例通过加载server.xml 完成其他实例的创建,创建并管理一个 Server,Server 创建并管理多个服务, 每个服务又可以有多个Connector 和一个 Container。