Tomcat分析系列针对于Tomcat版本:9.0.38
本文目标:
从整体了解Tomcat源码包结构,Tomcat对各个模块如何解耦的,及其各模块怎么组装的,也就是高内聚低耦合。
知道各个模块对上层模块提供获取数据的接口API,知道模块内部的入口或者说启动类,知道上层是如何对下层数据做处理的
Tomcat源码包结构
自下而上分析包功能,主要3大模块:
tomcat.util.net包:
封装底层对Socket的网络连接和数据传输,封装了对底层Socket的数据的读、写。
Java实现IO进行网络数据传输的方式有3种方式:
- BIO:同步阻塞IO。原生的Socket代码编写
- NIO:同步非阻塞IO。基于Channel和Buffer的设计
- NIO2(AIO):异步非阻塞IO
coyote包:
- Tomcat连接器(对应用层协议的封装,比如HTTP、HTTP/2、HTTPS),同样提供了对外的接口用于获取应用层协议数据。
- coyote包是Tomcat服务器提供的供客户端访问的外部接口。客户端通过Coyote与服务器建立链接、发送请求并且接收响应。
catalina包:Servlet容器实现
其他:
- juli包:Tomcat日志的实现
下面分析各个模块
net包
1.功能
与底层Socket打交道,隐藏底层对Socket的实现(NIO、NIO2、BIO)
2.net本包内对传输层的抽象入口API:AbstractEndpoint
也就是上层模块如果依赖于net包实现Socket的处理,需要提供 AbstractEndpoint的实现类。
3.对外提供获取底层Socket数据入口的接口API:指的是net包提供对Socket信息的封装,提供一个接口供外部通过该接口获取到Socket底层操作。(这里的外部指的是coyote包,coyote来对接下游net,对接上游catalina)
org.apache.tomcat.util.net.AbstractEndpoint.Handler
- 主要方法:
process方法:对底层的Socket封装了(SocketWrapperBase,还有一个SocketEvent),使用SocketWrapperBase对外提供对底层Socket操作的封装。
tomcat提供对Handler的实现类:
coyote包:org.apache.coyote.AbstractProtocol.ConnectionHandler(其内部又委托coyote包下的org.apache.coyote.AbstractProtocol),在ConnectionHandler的内部,又将处理交给org.apache.coyote.Processor处理。
在哪里组装Handler?
在AbstractEndpoint内部有个属性:
private Handler<S> handler = null;
Coyote包
1.功能
封装应用层协议的处理
2.coyote本包内部功能模块的设计入口API:org.apache.coyote.ProtocolHandler,应用层协议处理的抽象
coyote包都是围绕ProtocolHandler接口设计的。
ProtocolHandler内部聚合一个Adapter来对上层提供服务。
3.对上层(catalina)提供的数据入口API:org.apache.coyote.Adapter
注意:这里的Request、Response是coyote对应用层协议(HTTP)数据的封装,提供对外的一致性访问API。
- org.apache.coyote.Request
- org.apache.coyote.Response
prepare方法:返回true,则继续执行后面的操作,比如执行service方法。返回false,则直接通过org.apache.catalina.connector.Response返回异常,比如调用org.apache.catalina.connector.Response的sendError直接返回用户。
service:该方法就是上层模块处理请求逻辑的入口了。
Tomcat提供org.apache.coyote.Adapter的实现类:
在catalina包下:org.apache.catalina.connector.CoyoteAdapter
在CoyoteAdapter内部浮点数
catalina包
通过Connector类来组装下游接口API,同时组装上游的API(Service接口),也就是Connector是Tomcat容器和底层数据读写的解耦。
在构造函数中根据应用层协议类型创建了具体的ProtocolHandler实现类:
public Connector() {
this("HTTP/1.1");
}
public Connector(String protocol) {
boolean apr = AprStatus.isAprAvailable() &&
AprStatus.getUseAprConnector();
ProtocolHandler p = null;
try {
p = ProtocolHandler.create(protocol, apr);
} catch (Exception e) {
log.error(**.getString(
"coyoteConnector.protocolHandlerInstantiationFailed"), e);
}
if (p != null) {
protocolHandler = p;
protocolHandlerClassName = protocolHandler.getClass().getName();
} else {
protocolHandler = null;
protocolHandlerClassName = protocol;
}
// Default for Connector depends on this system property
setThrowOnFailure(Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"));
}
org.apache.coyote.ProtocolHandler#create方法:
public static ProtocolHandler create(String protocol, boolean apr)
throws ClassNotFoundException, InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
if (protocol == null || "HTTP/1.1".equals(protocol)
|| (!apr && org.apache.coyote.http11.Http11NioProtocol.class.getName().equals(protocol))
|| (apr && org.apache.coyote.http11.Http11AprProtocol.class.getName().equals(protocol))) {
if (apr) {
return new org.apache.coyote.http11.Http11AprProtocol();
} else {
return new org.apache.coyote.http11.Http11NioProtocol();
}
} else if ("AJP/1.3".equals(protocol)
|| (!apr && org.apache.coyote.ajp.AjpNioProtocol.class.getName().equals(protocol))
|| (apr && org.apache.coyote.ajp.AjpAprProtocol.class.getName().equals(protocol))) {
if (apr) {
return new org.apache.coyote.ajp.AjpAprProtocol();
} else {
return new org.apache.coyote.ajp.AjpNioProtocol();
}
} else {
// Instantiate protocol handler
Class<?> clazz = Class.forName(protocol);
return (ProtocolHandler) clazz.getConstructor().newInstance();
}
}
问题:对于HTTP协议,创建的类是Http11NioProtocol,为什么带有Nio标识呢?因为需要创建具体的AbstractEndpoint实现类,通过类名就可以知道内部提供了什么样的AbstractEndpoint实现类。
public Http11NioProtocol() {
//new 了一个NioEndpoint
super(new NioEndpoint());
}
总结
用图形化表示3个模块是如何组装的:
模块代码设计提示:
通常一个模块的设计入口类或者说启动类,都不是真正干活的人,启动类有两个作用:
- 封装内部实现细节
- 起到承上作用。也就是其抽象类内部不仅包含了其当前模块的各个功能实现的抽象接口,还包含服务于上层模块的抽象API,这个抽象API通常就是调用当前模块返回的数据。
比如net包的AbstractEndpoint:呈上:private Handler<S> handler = null; 封装内部实现细节:protected Acceptor<U> acceptor,还有其他组件共同完成内部实现。
源码分析下HTTP请求处理的整个大致过程:
入口:catalina包下的:从Connector的构造函数开始,Connector前面我们先不讲,本篇文章主要讲catalina容器之外的与底层数据有关联的流程
以后几篇先分析net包内部的功能设计,主要关注以下问题
net包:
线程模型:
- reactor线程模型,与netty的不同点在哪里?为什么需要这样设计?
- tomcat默认基于阻塞的accept,而不是像netty基于Selector的accept事件触发的,tomcat为什么这样设计?
net包如何基于NIO、NIO2实现底层数据通信的,既然支持两种IO模型,那么net包如何屏蔽掉这种差异的而使用统一的流程去处理逻辑的?
net包内部是如何进行状态自动切换的,比如accpet线程、Poller线程、Executor线程执行器,知道这些可以让我们知道该如何根据内部状态码的切换自动应对各种逻辑的处理
对底层Socket的读写有哪些优化?
超时处理:Tomcat在哪里触发超时逻辑。tomcat的超时分为两大类:异步Servlet超时、Socket相关的超时(读写超时、连接超时)。tomcat如何实现超时的?
配置:对底层tcp的配置参数有哪些