我们研究的版本是tomcat9(之前spring版本的研究也对应的tomcat9)
tomcat9下载传送门
在解压后的源码中放入一个内容如下的pom文件,其中的版本号跟随你自己下载的tomcat版本号自行调整
<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>tomcat9artifactId>
<name>tomcat-9.0.41name>
<version>9.0.41version>
<build>
<finalName>tomcat-9.0.41finalName>
<sourceDirectory>javasourceDirectory>
<resources>
<resource>
<directory>javadirectory>
resource>
resources>
<testResources>
<testResource>
<directory>testdirectory>
testResource>
testResources>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.6.0version>
<configuration>
<encoding>UTF-8encoding>
<source>1.8source>
<target>1.8target>
configuration>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-jar-pluginartifactId>
<version>3.0.2version>
plugin>
plugins>
build>
<dependencies>
<dependency>
<groupId>org.apache.antgroupId>
<artifactId>antartifactId>
<version>1.9.5version>
dependency>
<dependency>
<groupId>biz.aQute.bndgroupId>
<artifactId>biz.aQute.bndlibartifactId>
<version>5.2.0version>
<scope>providedscope>
dependency>
<dependency>
<groupId>org.apache.tomcatgroupId>
<artifactId>tomcat-jasperartifactId>
<version>9.0.41version>
dependency>
<dependency>
<groupId>org.apache.antgroupId>
<artifactId>ant-apache-log4jartifactId>
<version>1.9.5version>
dependency>
<dependency>
<groupId>org.apache.antgroupId>
<artifactId>ant-commons-loggingartifactId>
<version>1.9.5version>
dependency>
<dependency>
<groupId>javax.xml.rpcgroupId>
<artifactId>javax.xml.rpc-apiartifactId>
<version>1.1version>
dependency>
<dependency>
<groupId>wsdl4jgroupId>
<artifactId>wsdl4jartifactId>
<version>1.6.2version>
dependency>
<dependency>
<groupId>org.eclipse.jdt.core.compilergroupId>
<artifactId>ecjartifactId>
<version>4.6.1version>
dependency>
dependencies>
project>
创建启动类,mainClass指定为tomcat的Bootstrap
,增加以下的vm options解决乱码的问题
-Duser.language=en
-Duser.region=US
-Dfile.encoding=UTF-8
-Dsun.jnu.encoding=UTF-8
从conf下的server.xml开始分析(结合源码),逐步得到整体的包含关系,每一个标签都有对应的类(一般是接口,tomcat对其做了抽象封装)
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<GlobalNamingResources>
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
GlobalNamingResources>
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<Engine name="Catalina" defaultHost="localhost">
<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
Realm>
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
Host>
Engine>
Service>
Server>
server即是tomcat,其内包含一些监听器,可以指定一些资源的配置信息,最重要的是他包含了service进来
service服务包含了Connector连接器和Engine引擎两个重要组件
连接器能够进行端口的监听,获取到请求
引擎能够进行请求的处理,其内包含了Host
虚拟主机,可以映射域名,它内部包含了Context
他就是一个个的web应用(咱们部署的包),它内部包含了Wrapper
每一个Wrapper都封装了一个Servlet的配置详情
他是tomcat的生命周期接口,基本上上述组件都实现了他,类图如下:
Lifecycle的具体实现类,利用模板模式,定义好整体的算法步骤,核心的实现留给组件自己去实现
容器接口,上述的Host,Engine,Context,Wrapper等都是他的实现,容器可以继续添加子组件,并且容器中有一个特性是拥有一个管道Pipeline
(请求必须要经历每一层容器的管道,里面的所有的阀门就可以来进行干预)
比如
Host
中的AccessLogValve
日志阀门,可以用来记录请求的日志,阀门机制是Tomcat中,控制数据流向处理的核心,利用阀门来做类似Filter的预处理,阀门属于一个责任链的模式
容器的特点:
catalina.load
方法daemon.start();
执行服务器的启动方法,通过反射调用catalina.start
方法getServer().start()
getServer().await()
,一直while循环接收数据,监听8005端口,接收命令首先调用BootStrap的init方法进行初始化(加了锁的),并且用volatile修饰了BootStrap的实例变量daemon
准备common,server和shared三个类加载器(我实际断点调试时发现全部返回的是父类URLClassLoader类型)
commonLoader = createClassLoader("common", null);
if (commonLoader == null) {
// no config file, default to this loader - we might be in a 'single' env.
commonLoader = this.getClass().getClassLoader();
}
catalinaLoader = createClassLoader("server", commonLoader);
sharedLoader = createClassLoader("shared", commonLoader);
配置文件的每一个标签就对应这一个类,创建对象封装好标签配置的属性内容,Host里面并不会在此时扫描加载Context应用组件(只是单纯的解析xml中的架构,按照层次封装到Catalina对象中)
getServer().init()执行服务器的初始化,他最终调用到了子类StandardServer
的initInternal()
初始化方法,具体的步骤如下
service.init()
engine.init()
connector.init()
protocolHandler.init()
:他最重要的是endpoint.init()
端口的初始化,将ServerSocket绑定好整个初始化的逻辑,是一层一层嵌套的,他的顺序就是上面讲过的包含关系,一层层传递下去
connector
在初始化时准备好了ProtocolHandler
协议处理器和CoyoteAdapter
适配器两个组件(新版本的tomcat默认采用的都是http/1.1 的NIO协议),之后他开始调用ProtocolHandler.start()
方法,协议处理器又调用了endpoint.start()
断点启动方法:
maxConnections
的限制,最大为8*1024=8192条连接(但是不建议参数设置过大,根据具体机器配置调整)Poller
拉取者单线程,然后在创建启动一个Acceptor
接受者单线程
Acceptor
一直循环接收端口来的数据(serverSocket.accept,端口开启一个线程在后台一直接收数据),监听8080端口
Acceptor
一直接收8080的请求,会尝试endpoint.setSocketOptions(socket)
PollerEvent
,添加事件队列到SynchronizedQueue
中Poller
一直判断是否有事件.有的话就触发events.poll()SocketProcessorBase
里面,把这个SocketProcessorBase直接扔给线程池Http11Processor.process
进行处理,最终Http11Processor的service方法最终处理这个request(最终会层层调用到servlet的service方法)