工程结构
catalina 包 是所有的servlet容器的实现 , Tomcat本质就是一个Servlet容器.
coyote包 所有的连接器的名称,主要是实现网络编程的,NIO啥的
el包 就是所谓的Java表达式
jasper 包 就是jsp引擎
juli 包 就是日志
naming包 JNDI
tomcat包 提供外部接口,比如说不需要tomcat复杂的启动,可以用到这个tomcat包下的api.
Tomcat组件及架构
一个service只会有一个Engine引擎,一个Engine可以有多个域名,也就是你可以玩多个Host,在每一个Host里面可以去部署一个项目(context),也可以去部署多个项目,每一个项目里面对应的Servlet实例,比如说aservlet.class实例会放在Wrapper里面.wrapper是处理单个Servlet实例的
组件名称 | 说明 |
---|---|
Server | 整个Servlet容器,一个Server可以有多个Service |
Service | 一个Service维护多个Connector和一个Container |
Connector | 链接器:监听转换Socket请求,将请求交给Container处理,支持不同协议以及不同的I/O方式 |
Container | 表示能够执行客户端请求并返回响应的一类对象,其中有不同级别的容器:Engine、Host、Context、Wrapper |
Engine | 整个Servler引擎,最高级的容器对象 |
Host | 表示Servlet引擎中的虚拟机,主要与域名有关,一个服务器有多个域名是可以使用多个Host |
Context | 用于表示ServletContext,一个ServletContext表示一个独立的Web应用 |
Wrapper | 用于表示Web应用中定义的Servlet |
Executor | Tomcat组件间可以共享的线程池 |
1.Server
Server是最顶级的组件,它代表Tomcat的运行实例,它掌管着整个Tomcat的生死大权;
Ø 提供了监听器机制,用于在Tomcat整个生命周期中对不同时间进行处理
Ø 提供Tomcat容器全局的命名资源实现,JNDI
Ø 监听某个端口以接受SHUTDOWN命令,用于关闭Tomcat
2.Service
一个概念,一个Service维护多个Connector和一个Container
3.Connector组件
链接器:监听转换Socket请求,将请求交给Container处理,支持不同协议以及不同的I/O方式
4.Container
表示能够执行客户端请求并返回响应的一类对象,其中有不同级别的容器:Engine、Host、Context、Wrapper
Connector里面有三种传输协议
HTTP: HTTP/1.1协议
AJP协议:主要与Apache HTTP Server 集成
HTTP2:HTTP/2.0协议,下一代HTTP协议
三种 I/O方式(Tomcat8.5方式移除了BIO)
NIO:采用JDK的NIO类库实现
NIO2(AIO):采用JDK1.7的NIO2类库实现
APR:采用APR(Apache可移植运行库)
高并发的场景的话建议使用NIO2和APR,不过一般场景的话NIO也够用了.
5.Engine
6.Host
表示Servlet引擎中的虚拟机,主要与域名有关,一个服务器有多个域名是可以使用多个Host
7.Context
用于表示ServletContext,一个ServletContext表示一个独立的Web应用
8.Wrapper
9.Executor
Tomcat组件间可以共享的线程池,线程池可以在server.xml里面配置.
(二)Tomcat的核心组件
解耦:网络协议与容器的解耦。
Connector链接器封装了底层的网络请求(Socket请求及相应处理),提供了统一的接口,使Container容器与具体的请求协议以及I/O方式解耦。
Connector将Socket输入转换成Request对象,交给Container容器进行处理,处理请求后,Container通过Connector提供的Response对象将结果写入输出流。
因为无论是Request对象还是Response对象都没有实现Servlet规范对应的接口,Container会将它们进一步分装成ServletRequest和ServletResponse.
(三)Tomcat的链接器
AJP主要是用于Web服务器与Tomcat服务器集成,AJP采用二进制传输可读性文本,使用保持持久性的TCP链接,使得AJP占用更少的带宽,并且链接开销要小得多,但是由于AJP采用持久化链接,因此有效的连接数较HTTP要更多。
HTTP2.0目前市场不成熟,这个技术点后续我们的三期、四期如果市面上协议很普遍了会考虑加入。
对于I/0选择,要根据业务场景来定,一般高并发场景下,APR和NIO2的性能要优于NIO和BIO,(linux操作系统支持的NIO2由于是一个假的,并没有真正实现AIO,所以一般linux上推荐使用NIO,如果是APR的话,需要安装APR库,而Windows上默认安装了),所以在8.5的版本中默认是NIO。
启动原理
在Tomcat项目包的bin目录下,不管你运行 startup.sh 还是运行 setclasspath.sh 还是configtest.sh,最终都会调用到catalina.sh.
在catalina.sh里面会设置一些VM的参数,并且最终会调用到org.apache.catalina.startup.Bootstrap的Main方法
启动Bootstrap的Main方法之后会
1.执行 init方法 ,在init方法会初始化Tomcat类加载器,然后调用load方法,在load方法内部通过反射调用Catalina方法的load方法,
在Catalina的load方法内部会读取conf/server.xml文件配置内容等等一系列的配置之后会调用Server的init方法等等,然后在StandardServer#initInternal循环遍历所有的Service子类的init方法
2.会反射调用start方法
一次请求的过程
当用户访问 localhost:8080/xxxd的时候,Tomcat是怎么玩的呢?
首先http请求走Connector(连接器),Connector是专门处理Http\IO的,
Connector会给你这次的http请求进行一次包装,毕竟Tomcat是一个Servlet容器,Servlet会处理request和response,所以Connector会给http请求包装成一个request对象
request对象在去Engine(引擎)的过程中会进一步封装成ServletRequest,封装成ServletRequest以后就会整体进入到Engine里面了.
Engine处理完了以后就会返回ServletResponse对象,ServletResponse对象拆解成response对象之后再通过Connector转成http协议发送给用户浏览器
生命周期
Tomcat的架构设计是清晰的、模块化、它拥有很多组件,加入在启动Tomcat时一个一个组件启动,很容易遗漏组件,同时还会对后面的动态组件拓展带来麻烦。如果采用我们传统的方式的话,组件在启动过程中如果发生异常,会很难管理,比如你的下一个组件调用了start方法,但是如果它的上级组件还没有start甚至还没有init的话,Tomcat的启动会非常难管理,因此,Tomcat的设计者提出一个解决方案:用Lifecycle管理启动,停止、关闭。
Tomcat内部架构中各个核心组件有包含与被包含关系,例如:Server包含了Service.Service又包含了Container和Connector,这个结构有一点像数据结构中的树,树的根结点没有父节点,其他节点有且仅有一个父节点,每一个父节点有0至多个子节点。所以,我们可以通过父容器启动它的子容器,这样只要启动根容器,就可以把其他所有的容器都启动,从而达到了统一的启动,停止、关闭的效果。
所有所有组件有一个统一的接口——Lifecycle,把所有的启动、停止、关闭、生命周期相关的方法都组织到一起,就可以很方便管理Tomcat各个容器组件的生命周期。
Lifecycle其实就是定义了一些状态常量和几个方法,主要方法是init,start,stop三个方法。
例如:Tomcat的Server组件的init负责遍历调用其包含所有的Service组件的init方法。
注意:Server只是一个接口,实现类为StandardServer,有意思的是,StandardServer没有init方法,init方法是在哪里,其实是在它的父类LifecycleBase中,这个类就是统一的生命周期管理。
所以StandardServer最终只会调用到initInternal方法,这个方法会初始化子容器Service的init方法
为什么LifecycleBase这么玩,其实很多架构源码都是这么玩的,包括JDK的容器源码都是这么玩的,一个类,有一个接口,同时抽象一个抽象骨架类,把通用的实现放在抽象骨架类中,这样设计就方便组件的管理,使用LifecycleBase骨架抽象类,在抽象方法中就可以进行统一的处理,具体的内容见下面。
抽象类LifecycleBase统一管理组件生命周期
具体实现类StandardXXX类调用initInternal方法实现具体的业务处理。