Tomcat主要结构和启动流程

Tomcat最重要的两部分——连接器(Connector)、容器(Container)。连接器负责对外交流,容器负责内部处理。

单独的连接器或者容器都不能对外提供服务,需要把它们组装起来才能工作,组装后这个整体叫作Service组件。Service本身没有做什么重要的事情,只是在连接器和容器外面多包了一层,把它们组装在一起。Tomcat内可能有多个Service,通过在配置多个Service,可以实现通过不同的端口号来访问同一台机器上部署的不同应用。Tomcat为了实现支持多种I/O模型和应用层协议,一个容器可能对接多个连接器。

连接器负责外部交流,容器负责内部处理,连接器与容器之间通过标准的ServletRequest和ServletResponse通信。连接器处理Socket通信和应用层协议的解析,得到Servlet请求,而容器则负责处理Servlet请求。

1.连接器内部细节

1)连接器对Servlet容器屏蔽了协议及I/O模型等的区别,无论是HTTP还是AJP,在容器中获取到的都是一个标准的ServletRequest对象。连接器模块用三个核心接口:EndPoint(负责网络通信,提供字节流给Processor)、Processor(负责应用层协议解析,提供Tomcat Request对象给Adaptor)、Adaptor(负责提供ServletRequest对象给容器)

2)由于I/O模型和应用层协议可以自由组合,比如NIO + HTTP或者NIO2 + AJP。设计者将网络通信和应用层协议解析放在一起考虑,设计了ProtocolHandler接口来封装这两种变化点。各种协议和通信模型的组合有相应的具体实现类,比如:Http11NioProtocol和AjpNioProtocol。

EndPoint

EndPoint是一个接口,它的抽象实现类AbstractEndpoint里面定义了两个内部类:Acceptor和SocketProcessor。

其中Acceptor用于监听Socket连接请求。SocketProcessor用于处理接收到的Socket请求,它实现Runnable接口,在Run方法里调用协议处理组件Processor进行处理。为了提高处理能力,SocketProcessor被提交到线程池来执行。而这个线程池叫作执行器(Executor),我在后面的专栏会详细介绍Tomcat如何扩展原生的Java线程池。

Processor

如果说EndPoint是用来实现TCP/IP协议的,那么Processor用来实现HTTP协议,Processor接收来自EndPoint的Socket,读取字节流解析成Tomcat Request和Response对象,并通过Adapter将其提交到容器处理,Processor是对应用层协议的抽象。

Processor是一个接口,定义了请求的处理等方法。它的抽象实现类AbstractProcessor对一些协议共有的属性进行封装,没有对方法进行实现。具体的实现有AJPProcessor、HTTP11Processor等,这些具体实现类实现了特定协议的解析方法和请求处理方式。

Adaptor

由于协议不同,客户端发过来的请求信息也不尽相同,Tomcat定义了自己的Request类来“存放”这些请求信息。ProtocolHandler接口负责解析请求并生成Tomcat Request类。但是这个Request对象不是标准的ServletRequest,也就意味着,不能用Tomcat Request作为参数来调用容器。Tomcat设计者的解决方案是引入CoyoteAdapter,这是适配器模式的经典运用,连接器调用CoyoteAdapter的Sevice方法,传入的是Tomcat Request对象,CoyoteAdapter负责将Tomcat Request转成ServletRequest,再调用容器的Service方法。

 

2.容器内部细节

Tomcat设计了4种容器,分别是Engine、Host、Context和Wrapper,所有容器组件都实现了Container接口。这4种容器不是平行关系,而是父子关系,形成一个树形结构。

Context表示一个Web应用程序;

Wrapper表示一个Servlet,一个Web应用程序中可能会有多个Servlet;

Host代表的是一个虚拟主机,或者说一个站点,可以给Tomcat配置多个虚拟主机地址,而一个虚拟主机下可以部署多个Web应用程序;

Engine表示引擎,用来管理多个虚拟站点,一个Service最多只能有一个Engine;

Mapper

Tomcat是怎么确定请求是由哪个Wrapper容器里的Servlet来处理的呢——答案是Mapper组件

Mapper组件的功能就是将用户请求的URL定位到一个Servlet,它的工作原理是:Mapper组件里保存了容器组件与访问路径的映射关系,比如Host容器里配置的域名、Context容器里配置的Web应用路径,以及Wrapper容器里Servlet映射的路径,这些配置信息就是一个多层次的Map。当一个请求到来时,Mapper组件通过解析请求URL里的域名和路径,再到自己保存的Map里去查找,就能定位到一个Servlet。一个请求URL最后只会定位到一个Wrapper容器,也就是一个Servlet。

比如请求http://test.company.com/myweb/hello 

1)首先,根据协议和端口号选定Service和Engine。上面例子中的URL访问的是8080端口,Tomcat默认的HTTP连接器监听8080端口,因此这个请求会被HTTP连接器接收,而一个连接器是属于一个Service组件的,这样Service组件就确定了。一个Service组件里除了有多个连接器,还有一个Engine容器,因此Service确定了也就意味着Engine也确定了。

2)然后,根据域名选定Host。Service和Engine确定后,Mapper组件通过URL中的域名去查找相应的Host容器,比如例子中的URL访问的域名是test.company.com,因此Mapper会找到Host1这个容器。

3)接着,根据URL路径找到Context组件。Host确定以后,Mapper根据URL的路径来匹配相应的Web应用的路径,比如例子中访问的是/myweb,因此找到了Context1这个Context容器。

4)最后,根据URL路径找到Wrapper(Servlet)。Context确定后,Mapper再根据web.xml中配置的Servlet映射路径来找到具体的Wrapper和Servlet。

 

Pipeline-Valve

容器是如何往下调用的——Pipeline-Valve责任链模式

每一个容器都有一个Pipeline对象,Pipeline中维护了Valve链表,Valve可以插入到Pipeline中,对请求做某些处理。Valve完成自己的处理后,调用getNext.invoke()来触发下一个Valve调用。只要触发这个Pipeline的第一个Valve,这个容器里Pipeline中的Valve就都会被调用到。

1)不同容器的Pipeline是怎么链式触发的呢?

通过BasicValve,比如Engine中Pipeline需要调用下层容器Host中的Pipeline,因为Pipeline中有个getBasic方法。这个BasicValve处于Valve链表的末端,它是Pipeline中必不可少的一个Valve,负责调用下层容器的Pipeline里的第一个Valve。

2)第一次Pipeline的调用是哪里发起的?

整个调用过程由连接器中的Adapter触发的,它会调用Engine的第一个Valve:connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);

3)Pipeline最后一个Value如何调用servlet?

Wrapper容器的最后一个Valve会创建一个Filter链,并调用doFilter()方法,最终会调到Servlet的service方法

 

扩展机制(Filter和Listener)

Filter是过滤器,这个接口允许你对请求和响应做一些统一的定制化处理,比如你可以根据请求的频率来限制访问,或者根据国家地区的不同来修改响应内容。Web应用部署完成后,Servlet容器需要实例化Filter并把Filter链接成一个FilterChain。当请求进来时,获取第一个Filter并调用doFilter方法,doFilter方法负责调用这个FilterChain中的下一个Filter。

Listener是监听器,这是另一种扩展机制。当Web应用在Servlet容器中运行时,Servlet容器内部会不断的发生各种事件,如Web应用的启动和停止、用户请求到达等。 Servlet容器提供了一些默认的监听器来监听这些事件,当事件发生时,Servlet容器会负责调用监听器的方法。你可以定义自己的监听器去监听你感兴趣的事件,将监听器配置在web.xml中。比如Spring就实现了自己的监听器,来监听ServletContext的启动事件。

Filter和Listener的区别——Filter是干预过程的,它是过程的一部分,是基于过程行为的;Listener是基于状态的,任何行为改变同一个状态,触发的事件是一致的。

3.Tomcat启动流程

组件生命周期管理(Lifecycle)

Tomcat定义了Lifecycle接口来管理组件的生命周期,让整个启动和关闭过程按照一定规则进行。父组件在启动调用init和start时,会调用子组件的对应方法。因此,只要调用最顶层组件也就是Server组件的init()和start()方法,整个Tomcat就被启动起来了。

Lifecycle中还加入了事件监听机制,在组件生命周期变化中,会触发对应的状态变化消息(LifecycleState),通知关心该消息的监听者处理相应逻辑,从而保证组件启动逻辑不变的情况下提供扩展性。

 

启动过程

1)执行startup.sh,启动JVM运行启动类Bootstrap

2)Bootstrap负责初始化Tomcat的类加载器,并且创建Catalina

3)Catalina是一个启动类,它负责解析server.xml、创建相应的组件,并调用Server的start方法

4)Server组件的职责就是管理Service组件,它会调用Service的start方法

5)Service组件启动和管理连接器与顶层容器Engine

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值