主要分四部分:
功能是从需求的角度进行描述的,架构是从设计的角度进行描述的,源码是从实现的角度进行描述的
什么是Tomcat架构: 为了实现上述的功能,Tomcat进行了很多的封装设计,封装出了很多的组件(组件在源代码中的体现就是Java类),组件与组件之间的关系就构成了所谓的Tomcat架构。
一个Service内部可以有多个Connector组件,因为一个Connector绑定一个端口进行监听,多个Connector可以监听多个端口,但是一个Service内部的多个Connector只能对应一个Servlet容器。
除了Connector组件和Container组件,Tomcat其实还定义了很多其他组件来工作(server-service-connector/container-engine-host-context-wrapper)。这些组件采用一层套一层的设计方式(套娃式),如果一个组件包含了其他组件,那么这个组件也称之为容器。
剖析源代码需要讲究一些原则,注意一些方法和技巧,否则很容易就在浩瀚的源代码海洋中迷失自己
好处:提高我们的架构思维、深入认识代码、深入理解一个项目/框架
原则:
方法和技巧
说明:
- 基于Tomcat8.5.54的源码进行说明
- 添加了一个web_demo应用在webapps中用于演示,该项目只有一个名为resumeservlet的servlet
- Tomcat本身就是Java开发的软件,直接使用Tomcat的时候,Tomcat需要读取server.xml以及其他的配置文件,同时还需要找到它要去部署的工程项目。使用源码方式,依然如此
操作步骤:
步骤一:解压源码压缩包,得到目录apache-tomcat-8.5.54-src
下载地址:https://tomcat.apache.org/download-80.cgi
下载最下面的Source Code Distributions源码版本,我这里下载的是zip的
步骤二:进入apache-tomcat-8.5.54-src目录,创建一个pom.xml文件,文件内容如下:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>org.apache.tomcatgroupId>
<artifactId>apache-tomcat-8.5.50-srcartifactId>
<name>Tomcat8.5name>
<version>8.5version>
<build>
<finalName>Tomcat8.5finalName>
<sourceDirectory>javasourceDirectory>
<resources>
<resource>
<directory>javadirectory>
resource>
resources>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.1version>
<configuration>
<encoding>UTF-8encoding>
<source>11source>
<target>11target>
configuration>
plugin>
plugins>
build>
<dependencies>
<dependency>
<groupId>javax.xmlgroupId>
<artifactId>jaxrpcartifactId>
<version>1.1version>
dependency>
<dependency>
<groupId>javax.xml.soapgroupId>
<artifactId>javax.xml.soap-apiartifactId>
<version>1.4.0version>
dependency>
<dependency>
<groupId>wsdl4jgroupId>
<artifactId>wsdl4jartifactId>
<version>1.6.2version>
dependency>
<dependency>
<groupId>org.eclipse.jdt.core.compilergroupId>
<artifactId>ecjartifactId>
<version>4.5.1version>
dependency>
<dependency>
<groupId>antgroupId>
<artifactId>antartifactId>
<version>1.7.0version>
dependency>
<dependency>
<groupId>org.easymockgroupId>
<artifactId>easymockartifactId>
<version>3.4version>
dependency>
dependencies>
project>
步骤三:在apache-tomcat-8.5.54-src目录中创建source文件夹
步骤四:将conf、webapps目录移动到刚刚创建的source文件夹中
步骤五:将源码工程导入到IDEA中
IDEA中应当已经配置好Maven
步骤六:给tomcat的源码程序配置Run/Debug Configurations
注意这里要使用Java11,因为pom.xml中配置使用的是Java11
这里要给tomcat的源码程序启动类Bootstrap配置VM参数,因为tomcat源码运行也需要加载配置文件等
-Dcatalina.home=E:\Code\IdeaProjects\apache-tomcat-8.5.54-src/source
-Dcatalina.base=E:\Code\IdeaProjects\apache-tomcat-8.5.54-src/source
-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
-Djava.util.logging.config.file=E:\Code\IdeaProjects\apache-tomcat-8.5.54-src/source/conf/logging.properties
步骤七:Build项目,此时会报错
找到错误位置,使用Alt+Enter第一条即可修复该问题,然后重新Build即可
步骤八:运行项目就启动了tomcat,启动时会加载所配置的conf目录下的server.xml等配置文件,所以访问8080端口即可,但此时会遇到如下的一个错误:
原因是Tomcat源码中Jsp引擎Jasper没有被初始化,从而无法编译处理Jsp(因为Jsp是需要被转换成servlet进一步编译处理的),只需要在tomcat的源码ContextConfig类中的configureStart方法中增加一行代码将Jsp引擎初始化,如下
步骤九:重启Tomcat,正常访问即可。至此,Tomcat源码构建完毕。
Tomcat要启动,肯定要把架构中提到的组件进行实例化(实例化创建–>销毁等:生命周期)。Tomcat中那么多组件,为了统一规范他们的生命周期,Tomcat抽象出了LifeCycle生命周期接口
LifeCycle生命周期接口方法:
LifeCycle生命周期接口的继承体系:
(1)启动入口分析
startup.sh --> catalina.sh start --> java xxxx.jar org.apache.catalina.startup.Bootstrap(main) start(参数)
① Bootstrap的main方法:
② Bootstrap的init方法:
经过前两步,设置了
catalinaDaemon = catalina对象
daemon = bootstrap对象
③ Bootstrap的load方法:
④ Bootstrap的start方法:
② Digester的parse方法:
得到的root变量内容:
server相关值:
service相关值:
engine相关值:
得到的内容与解析的server.xml文件配置内容一致
③ LifecycleBase的init方法(使用了设计模式的模板方法):
此处的initInternal会跳转至实现该接口的具体类的对象中
④ StandardServer的initInternal方法:
会跳转至service的init方法中,即继续调用service实现的Lifecycle的init方法
⑤ StandardService的initInternal方法:
分别初始化service中的容器部分和connector部分
⑥ StandardEngine的initInternal方法:
⑦ ContainerBase的initInternal方法:
注:ContainerBase是StandardEngine、StandardWrapper、StandardContext和StandardHost的父类
⑧ Connector的initInternal方法:
绑定适配器:
初始化protocolHandler组件:
⑨ AbstractProtocol的init方法:
⑩ AbstractEndpoint的init方法:
⑪ NioEndpoint的bind方法:
(1)流程图
与load过程很相似
(2)源码分析
① Catalina的start方法:
② LifecycleBase的start方法(同样使用了设计模式的模板方法):
④ StandardServer的startInternal方法:
⑤ StandardService的startInternal方法:
分别启动其中的engine和connector
① StandardEngine的startInternal方法:
最后调用父类ContainerBase的startInternal方法
② ContainerBase的startInternal方法:
Engine调用它父类执行该方法时,子容器为Host:
将子容器Host提交到具体的StartChild线程类并行执行
注:该线程池只专门用来实例化Host
Host调用它父类执行该方法时,通过设置生命周期事件来进行实例化
③ StartChild线程类:
④ StandardHost的startInternal方法:
⑤ LifecycleBase的setStateInternal方法:
⑥ LifecycleBase的fireLifecycleEvent方法:
触发Host的生命周期事件后,将后续工作交给生命周期监听器HostConfig来进行
⑦ Hostconfig的lifecycleEvent方法:
捕获start事件,执行start方法:
⑧ Hostconfig的start方法:
⑨ Hostconfig的deployApps方法:
根据不同的应用部署方式,调用不同的方法
⑩ Hostconfig的deployDirectories方法:
以线程方式并行处理多个项目:
⑪ DeployDirectory线程类:
⑫ Hostconfig的deployDirectory方法:
通过xml解析对象进行分析:
设置一些context应用的必要属性:
完善context的过程在addChild方法中
⑬ StandardHost的addChild方法:
⑭ ContainerBase的addChild方法:
⑮ ContainerBase的addChildInternal方法:
⑯ StandardContext的startInternal方法:
work目录存放jsp转换为servlet的中间过程临时文件
给每个应用设置类加载器:
关键步骤(把具体每个应用的处理交给了ContextConfig):
查看已初始化的Servlet,其中两个为tomcat默认的servlet,另一个resumeservlet为自己的项目web_demo中的servlet:
由上图看出,此时只读取了servlet对应的类,但并未生成实例化对象。
loadOnStartup方法根据web.xml中配置servlet的load-on-startup来进行创建实例化对应servlet。执行之后,instance就有具体对象了。
load-on-startup大于0时在容器启动时加载,否则在第一次访问该servlet时加载
⑰ StandardContext的loadOnStartup方法:
⑱ StandardWrapper的load方法:
⑲ StandardWrapper的loadServlet方法(实际实例化servlet的方法):
① Connector的startInternal方法:
② AbstractProtocol的start方法:
③ AbstractEndpoint的start方法:
④ NioEndpoint的startInternal方法:
Tomcat中的NIO模型:
(1) 获取请求的工作封装交给了Acceptor线程去完成
(2) poller线程(下面分析servlet请求处理时的入口)
检查selector中是否有数据到来的channel,如果有就要进行处理
⑤ AbstractEndpoint的startAcceptorThreads方法:
⑥ NioEndpoint的createAcceptor方法:
⑦ Acceptor线程类中的run方法:
一个servlet请求 --> 最终需要找到能够处理当前servlet请求的servlet实例 --> servlet.service()
(1)结构图
Tomcat中使用Mapper机制重新封装了Host-context-wrapper(servlet)之间的数据和关系。
在匹配出能够处理当前请求的对应Host、对应Context和对应Wrapper之前,mapper对象肯定已经初始化好了
(2)源代码结构
① Mapper类中有MappedHost数组,表示有多个Host
② MappedHost中有一个ContextList
③ ContextList类中有MappedContext数组,表示该Host有多个Context
④ MappedContext中有一个ContextVersion数组
⑤ ContextVersion中的MappedWrapper数组对应的就是servlet
⑥ MappedHost、MappedContext和MappedWrapper都有一个MapElement基类
StandardService --> startInternal --> mapperListener.start()中完成mapper对象初始化
(1)StandardService的startInternal方法:
基于已有信息数据完成Mapper对象的初始化
此处的mapperListener也遵从统一的生命周期管理
(2)MapperListener的startInternal方法:
(3)MapperListener的registerHost方法:
此时的变量情况:
(1)基本流程
(2)详细流程
Poller线程是追踪入口
(1) Poller的run方法:
(2)Poller的processKey方法:
在processSocket处设置断点:
(3)启动服务器,从浏览器发起访问请求
(4)AdstractEndpoint的processSocket方法:
把传入的socket交给一个线程进行处理
(5)线程类SocketProcessorBase的run方法:
(6)SocketProcessor的doRun方法:
此处的getHandler为中间过程对象
(7)ConnectionHandler的process方法:
取不到对应Processor的话
仍没有则创建一个新的
找到完成之后,使用其进行process
此时的processor为Http11Processor,因为默认的http协议是1.1版本。它是用来解析Socket中的请求信息的
(8)AbstractProcessorLight的process方法:
(9)Http11Processor的service方法:
此时获取得到的是CoyoteAdapter,连接器组件适配器
(10)CoyoteAdapter的service方法:
传入的参数req和res是原生的Request和Response,第一步先进行适配转换
转换完成后交给后面的Container中的具体组件处理,一层一层找到对应servlet,该流程对应了postParseRequest方法,即利用Mapper进行匹配查找,下面的(11)-(13)说明了这一过程
匹配完成后调用下面的invoke逐级调用匹配的结果,下面的(14)-(22)说明了这一过程
(11)CoyoteAdapter的postParseRequest方法:
(12)Mapper的map方法:
(13)Mapper的internalMap方法进行具体的相关匹配:
分别根据name匹配host、context和mapper
匹配到的结果存放在参数mappingData中(mappingDate就在request对象中):
(14)StandardEngineValve的invoke方法:
(15)AbstractAccessLogValve的invoke方法:
(16)ErrorReportValve的invoke方法:
(17)StandardHostValve的invoke方法:
Request的gerContext方法:
继续深入调用context
(18)AuthenticatorBase的invoke方法:
(19)StandardContextValve的invoke方法:
Request的getWrapper方法:
(20)StandardWrapperValve的invoke方法:
可以看到,执行完成后,得到的servlet即为对应的servlet
(21)ApplicationFilterChain的doFilter方法:
(22)ApplicationFilterChain的internalDoFilter方法:
此处是最终执行servlet中实际操作的部分!!
自己编写的ResumeServlet的内容