Tomcat8源码解析
系统架构
一个请求到达tomcat之后,Connector接收该请求并将接收的请求封装为Request,交给Container处理并返回Response处理结果。
环境准备
-
下载tomcat8.5.61源码:源码地址https://mirror.bit.edu.cn/apache/tomcat/tomcat-8/v8.5.61/src/apache-tomcat-8.5.61-src.zip
-
解压,在apache-tomcat-8.5.61-src目录下添加pom.xml文件
<?xml version="1.0" encoding="UTF-8"?> <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.0</modelVersion> <groupId>org.apache.tomcat</groupId> <artifactId>Tomcat8.5</artifactId> <name>Tomcat8.5</name> <version>8.5</version> <build> <finalName>Tomcat8.5</finalName> <sourceDirectory>java</sourceDirectory> <testSourceDirectory>test</testSourceDirectory> <resources> <resource> <directory>java</directory> </resource> </resources> <testResources> <testResource> <directory>test</directory> </testResource> </testResources> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.3</version> <configuration> <encoding>UTF-8</encoding> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>javax.xml.soap</groupId> <artifactId>javax.xml.soap-api</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.easymock</groupId> <artifactId>easymock</artifactId> <version>3.6</version> </dependency> <dependency> <groupId>ant</groupId> <artifactId>ant</artifactId> <version>1.7.0</version> </dependency> <dependency> <groupId>wsdl4j</groupId> <artifactId>wsdl4j</artifactId> <version>1.6.2</version> </dependency> <dependency> <groupId>javax.xml</groupId> <artifactId>jaxrpc</artifactId> <version>1.1</version> </dependency> <dependency> <groupId>org.eclipse.jdt.core.compiler</groupId> <artifactId>ecj</artifactId> <version>4.5.1</version> </dependency> </dependencies> </project>
-
直接启动org.apache.catalina.startup.Bootstrap的时候没有加org.apache.jasper.servlet.JasperInitializer,从而无法编译JSP。在tomcat的源码org.apache.catalina.startup.ContextConfig类中手动将JSP解析器初始化。context.addServletContainerInitializer(new JasperInitializer(), null);
-
在apache-tomcat-8.5.61-src新增目录resource,将conf和webapps目录剪切至resource目录
-
配置启动类
注:E:\soft\tomcat8\apache-tomcat-8.5.61-src需要替换成您自己本地的源码目录
-Dcatalina.home=E:\soft\tomcat8\apache-tomcat-8.5.61-src\source
-Dcatalina.base=E:\soft\tomcat8\apache-tomcat-8.5.61-src\source
-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
-Djava.util.logging.config.file=E:\soft\tomcat8\apache-tomcat-8.5.61-src\source\conf\logging.properties
-Dfile.encoding=UTF-8
源码分析理论
- 抓主线
- 宏观视角
- 经验 (调用栈,方法名,查看方法调用,do…实际干活的方法,实践)
源码分析
针对架构图就有了4个问题:
-
Connector如何建立连接请求?
Bootstrap初始化流程
#main
#init 实例化Catalina
#load
Catalina#load
Server#init
LifecycleBase#init
StandardServer#initInternal
Service#initInternal
Connector#initInternal
ProtocolHandler#init
AbstractProtocol#init
AbstractEndpoint#init
NioEndpoint#bind 绑定连接监听端口,创建ServerSocketChannel
Bootstrap启动流程
#start
Catalina#start
Server#startInternal
Service#startInternal
处理连接请求
Connector#startInternal
protocolHandler#start
endpoint#start
AbstractEndpoint#startAcceptorThreads 开启多个Acceptor线程,即selector线程, 创建SocketChannel,并将SocketChannel注册至Selector上
NioEndpoint#startInternal 开启一个处理请求的Poller线程
#processKey
AbstractEndpoint#processSocket
SocketProcessor#doRun
ConnectionHandler#process 处理请求…
部署应用
ContainerBase#startInternal
StartChild#start 只针对Engine的Host放入startStopExecutor去异步执行
StandardHost#startInternal(StarndardContext#start,StandardWrapper#start)
HostConfig#lifecycleEvent
#start
#deployApps
#deployDirectories -
Connector如何将请求封装Request和Response?
-
Connector如何将Request交给Container处理?
紧接着第一步Connector如何建立连接请求:
AbstractProcessorLight(Processor子类)#process
Http11Processor#service
CoyoteAdapter#service 将Request,Response转成HttpServletRequest,HttpServletResponse
org.apache.catalina.core.StandardEngineValve#invoke 使用Pipeline-Value管道来处理
AbstractAccessLogValve#invoke
ErrorReportValve#invoke
StandardHostValve#invoke
NonLoginAuthenticator#invoke
StandardContextValve#invoke
StandardWrapperValve#invoke
#createFilterChain 将请求封装成链式任务
ApplicationFilterChain#doFilter
#internalDoFilter
HttpServlet#service 至此将请求信息交给servelt处理 -
Container如何匹配请求的Servelt?
CoyoteAdapter#service
#postParseRequest 根据请求信息找到能够处理当前请求的HOST->CONTEXT->WRAPPER(SERVELT)
Mapper#map
#internalMap 找到匹配的Servelt
Mapper在什么时候初始化?
Bootstrap启动流程
#start
Catalina#start
Server#startInternal
Service#startInternal
Mapper添加信息
MapperListener#start
#startInternal
#registerHost
-
Container如何将请求处理返回给客户端?
紧接第三步,HttpServlet#service 至此将请求信息交给servelt处理:
HttpServlet#service
DefaultServlet#service
#serveResource 封装处理结果交给Connector,Connector在通过Socket的方式将结果返回给客户端
#registerHost -
Container如何将请求处理返回给客户端?
紧接第三步,HttpServlet#service 至此将请求信息交给servelt处理:
HttpServlet#service
DefaultServlet#service
#serveResource 封装处理结果交给Connector,Connector在通过Socket的方式将结果返回给客户端