0
点赞
收藏
分享

微信扫一扫

Tomcat的架构与源码分析学习笔记



文章目录

  • ​​前言​​
  • ​​Tomcat核心源码分析学习笔记​​
  • ​​1、认识Tomcat架构​​
  • ​​Tomcat架构:封装了许多的组件(类)​​
  • ​​Tomcat架构的好处​​
  • ​​2、Tomcat源码构建方式​​
  • ​​2.1、详细构建过程​​
  • ​​2.2、注意之后构建项目的language level(error:java 无效的源发行版11)​​
  • ​​2.3、源码分析(初始化与启动阶段)​​
  • ​​生命周期Lifecycle接口​​
  • ​​①初始化阶段​​
  • ​​②启动阶段​​
  • ​​2.4、Servlet请求处理链路​​
  • ​​①Servlet请求处理分析​​
  • ​​②Servlet请求处理流程示意​​
  • ​​③Mapper组件体系结构​​
  • ​​小总结​​
  • ​​参考文章​​

前言

本篇博客是学习B站教程​​手撕 tomcat 核心源码15讲【全网最详细tomcat 源码解析,底层原理 】​​的学习笔记,如有错误请指出。

所有博客文件目录索引:博客目录索引(持续更新)

Tomcat核心源码分析学习笔记

​Tomcat​​两个最重要的功能:

  • ​Http​​服务器(Connector):Socket通信(TCP)、解析HTTP报文。
  • ​Servlet​​​容器:自带​​servlet​​​以及我们可以自定义​​Servlet​​​。​​Servlet​​来处理业务逻辑处理。

​Tomcat​​启动逻辑是基于观察者模式的。

​LifecycleBase​​​中的​​init()​​​、​​start()​​方法就使用到了模板方法模式:​​LifecycleBase​​​是一个抽象类,其实现了​​Lifecycle​​​的接口,一个组件实现类实际上继承了该抽象类,每次执行如​​init()​​​、​​start()​​​方法时实际上会去执行抽象类中的对应方法,在该方法中来真正执行重写的如​​initInternal();​​​、​​startInternal();​​方法。

  • Tomcat的架构与源码分析学习笔记_服务器

​Connector​​​中的​​Apapter​​中使用到了适配器模式:将原生的​​request​​​以及​​response​​​转换成​​HttpRequest​​​、​​HttpResponse​​。

1、认识Tomcat架构

Tomcat架构:封装了许多的组件(类)

简单描述:套娃式架构设计

  • ​Server​​​(tomcat实例,只有一个),可包含多个​​Service​​服务,默认是一个,也没有必要多个。
  • ​Service​​​服务:可包含多个​​Connector​​​(监听与解析​​TCP/IP​​​协议与​​HTTP​​​报文)与​​Container​​(Servlet容器用来处理请求,包含多个servlet)。
  • ​Connector​​​(叫做Coyote):多个​​Connector​​​主要是用来监听不同的端口,默认是三个,多个只能对应一个​​Container​​。其中包含三个组件分别处理TCP/IP协议、HTTP报文、适配器(将Request转为其他ServletRequest)。
  • ​Container​​(Servlet容器,又叫做cataline):其中有多个servlet来处理不同的业务请求,并返回ServletResponse响应,Wrapper中包含着servlet。
  • ​Engine​​​和​​Host​​:Engine组件(引擎)是Servlet容器Catalina的核⼼,它⽀持在其下定义多个虚拟主机(Host),虚拟主机允许Tomcat引擎在将配置在⼀台机器上的多个域名,⽐如www.baidu.com、www.bat.com分割开来互不⼲扰;
  • ​Context​​:每个虚拟主机⼜可以⽀持多个web应⽤部署在它下边,这就是我们所熟知的上下⽂对象Context,上下⽂是使⽤由Servlet规范中指定的Web应⽤程序格式表示,不论是压缩过的war包形式的⽂件还是未压缩的⽬录形式;在AppBase中进行配置,在webapps目录下每一个都可以看做是一个Context,
  • ​Wrapper​​:在上下⽂中⼜可以部署多个servlet,并且每个servlet都会被⼀个包装组件(Wrapper)所包含(⼀个wrapper对应⼀个servlet)。

Tomcat的架构与源码分析学习笔记_apache_02

  • ​AJP​​协议:是早期apache(静态服务器)与tomcat(动态服务器)结合进行通信使用的AJP协议。
  • ​网络IO​​:早期默认网络IO模型使用BIO,tomcat8.0之后版本使用NIO,APR是apache的可扩展的移植包需要安装在linux系统上后再进行指定(并发调优),可以进行重新指定。
  • ​Adapter​​:适配器,将一个东西转为另一个东西如Request转为ServletReuqest进行一层封装。



接下来通过看一下配置文件来更加了解整体架构


​server.xml​​:通过看/conf/server.xml来看一下整体架构

<?xml version="1.0" encoding="UTF-8"?>
<!-- 对应一个Server实例 -->
<Server port="8005" shutdown="SHUTDOWN">

<!-- 配置监听器 -->
<Listener className="org.apache.catalina.startup.VersionLoggerListener"/>
<Listener SSLEngine="on" className="org.apache.catalina.core.AprLifecycleListener"/>
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener"/>
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener"/>
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener"/>

<!-- 全局资源的配置 -->
<GlobalNamingResources>
<Resource auth="Container" description="User database that can be updated and saved" factory="org.apache.catalina.users.MemoryUserDatabaseFactory" name="UserDatabase" pathname="conf/tomcat-users.xml" type="org.apache.catalina.UserDatabase"/>
</GlobalNamingResources>

<!-- service服务:实际可以设置多份(看做是做个tomcat),一般一份就够了 -->
<Service name="Catalina">

<!-- Connector(连接器):用来监听8080端口,主要进行通信与解析 -->
<Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/>
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443"/>

<!-- Engine(引擎):Servlet容器的引擎 -->
<Engine defaultHost="localhost" name="Catalina">
<!-- 一些权限相关的可以使用它来做 -->
<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/>
</Realm>

<!-- Host(虚拟主机,可多份):在一个虚拟主机下可以配置多个应用Context这里不体现,Host可以设置多份如www.blog.changlu.com,www.watch.changlu.com -->
<Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" pattern="%h %l %u %t "%r" %s %b" prefix="localhost_access_log" suffix=".txt"/>
</Host>
</Engine>
</Service>

</Server>

认识​​Context​​​与​​Wrapper​​:你将一个项目放在webapps目录下就是一个Context,Context下就是Wrapper(对应Servlet,一个Wrapper就是一个Servlet)。



Tomcat架构的好处

1、组件关系:一层套一层的关系,组件关系清晰,便于后面的组件生命周期管理。

2、层次明确:​​server.xml​​配置文件中标签与架构设计对应上,解读xml以及封装对象关系更加容易。

3、通过这种组件式的关系,能够让子容器继承父容器的一些配置。



2、Tomcat源码构建方式

2.1、详细构建过程


本次源码构建采用Tomcat 8.5.66版本


源码下载地址:​​Tomcat8.5.66下载页面​​

Tomcat的架构与源码分析学习笔记_服务器_03

接着按照下面几个步骤来进行构建:

步骤⼀:解压源码压缩包,得到⽬录 apache-tomcat-8.5.50-src。

步骤⼆:进⼊ apache-tomcat-8.5.50-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>apache-tomcat-8.5.50-src</artifactId>
<name>Tomcat8.5</name>
<version>8.5</version>
<build>
<!--指定源⽬录-->
<finalName>Tomcat8.5</finalName>
<sourceDirectory>java</sourceDirectory>
<resources>
<resource>
<directory>java</directory>
</resource>
</resources>
<plugins>
<!--引⼊编译插件,指定编译级别和编码-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<encoding>UTF-8</encoding>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
<!--Tomcat是java开发的,封装了很多功能,它需要依赖⼀些基础的jar包-->
<dependencies>
<!--远程过程调⽤⼯具包-->
<dependency>
<groupId>javax.xml</groupId>
<artifactId>jaxrpc</artifactId>
<version>1.1</version>
</dependency>
<!--soap协议处理⼯具包-->
<dependency>
<groupId>javax.xml.soap</groupId>
<artifactId>javax.xml.soap-api</artifactId>
<version>1.4.0</version>
</dependency>
<!--解析webservice的wsdl⽂件⼯具-->
<dependency>
<groupId>wsdl4j</groupId>
<artifactId>wsdl4j</artifactId>
<version>1.6.2</version>
</dependency>
<!--Eclipse Java编译器-->
<dependency>
<groupId>org.eclipse.jdt.core.compiler</groupId>
<artifactId>ecj</artifactId>
<version>4.5.1</version>
</dependency>
<!--ant管理⼯具-->
<dependency>
<groupId>ant</groupId>
<artifactId>ant</artifactId>
<version>1.7.0</version>
</dependency>
<!---easymock辅助单元测试-->
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>3.4</version>
</dependency>
</dependencies>
</project>

步骤三:在 apache-tomcat-8.5.50-src ⽬录中创建 source ⽂件夹,将原本项目中的conf、webapps ⽬录移动到source ⽂件夹。

Tomcat的架构与源码分析学习笔记_服务器_04

步骤五:将源码⼯程导⼊到 IDEA 中;

步骤六:给 tomcat 的源码程序启动类 Bootstrap 配置 VM 参数,因为 tomcat 源码运⾏也需要加载配置⽂件等。

  • 本项目是java se项目,所以创建一个application配置起始运行类以及vm参数:
  • Tomcat的架构与源码分析学习笔记_服务器_05
# 注意修改其中的source目录路径(1、2、3路径修改),注意这里是windows环境下,Mac环境\要改成/,并且去掉C:
# 这里是在启动时添加四个参数,之后运行可以获取到
-Dcatalina.home=C:\Users\93997\Desktop\apache-tomcat-8.5.66-src\source
-Dcatalina.base=C:\Users\93997\Desktop\apache-tomcat-8.5.66-src\source
-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
-Djava.util.logging.config.file=C:\Users\93997\Desktop\apache-tomcat-8.5.66-src\source\conf\logging.properties

步骤七:由于Tomcat源码中Jsp引擎Jasper没有被初始化,⽆法编译处理Jsp。所以找到​​ContextConfig​​​类,在​​configureStart()​​方法中添加初始化操作:

Tomcat的架构与源码分析学习笔记_java_06

//初始化JSP解析引擎-jasper
context.addServletContainerInitializer(new JasperInitializer(),null);

步骤八:启动项目,出现以下信息表示成功运行!

Tomcat的架构与源码分析学习笔记_java_07

此时访问http://localhost:8080/,即可访问到汤姆猫页面!

2.2、注意之后构建项目的language level(error:java 无效的源发行版11)

①​​Project strucutre​​​中的​​Project​​​与​​Moudles​​​中设置​​language level​​为8

Tomcat的架构与源码分析学习笔记_java_08

Tomcat的架构与源码分析学习笔记_apache_09

②Settings中设置项目版本为8

Tomcat的架构与源码分析学习笔记_apache_10

③确保Maven配置的插件设置为版本8

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>

2.3、源码分析(初始化与启动阶段)

生命周期Lifecycle接口

​Tomcat​​​启动过程中,一定会将组件进行实例化,为了统一规范它们的生命周期,​​Tomcat​​​抽象除了​​LifeCycle​​​生命周期接口,生命周期中包含初始化​​init()​​​、启动​​start()​​​、​​stop​​​、​​destory​​等。

下面是​​LifeCycle​​​接口的继承图,我们需要关注其中的​​LifecycleBase​​抽象类:

Tomcat的架构与源码分析学习笔记_tomcat_11

图中你可以看到​​StandardEngine​​​、​​StandardServer​​​等一些组件实际上都可以看做是继承的抽象类​​LifecycleMBeanBase​​​(实际继承的是该类的子类),他们都有​​LifeCycle​​接口中的方法,通过使用模板方法设计模式,来定义好每个方法其中的执行过程来进行多个组件初始化的相同步骤。



①初始化阶段


指的是​​init()​​​阶段。(对应在​​daemon.load(args);​​ 方法中执行)


Tomcat的架构与源码分析学习笔记_xml_12

​Bootstrap​​​的​​main()​​启动

  • 实例化Bootstrap,调用init()方法,在init()方法中实例化org.apache.catalina.startup.Catalina,并调用Catalina实例的setParentClassLoader()方法。最后给​​Object catalinaDaemon​​赋值​​Catalina​​实例。简而言之就是实例化Catalina类
  • ​volatile Bootstrap daemon = Bootstrap实例​​,引导类实例化并赋值。
  • 执行最重要的两步骤:
daemon.load(args);  //load加载初始化,调用Catalina的load()方法
daemon.start(); //start启动,调用Catalina的start()方法

​catalina​​​的​​load()​​​:通过使用​​Digester​​​进行加载​​server.xml​​​,其中该实例执行​​parse()​​​进行解析​​xml​​配置文件。

  • 注意解析​​server.xml​​​过程就是实例化各个组件的过程,解析到​​Catalina​​​实例中的​​Server​​​属性(​​StandardServer​​​类)中。在​​StandardServer​​实例中包含配置文件中的所有信息、以及组件。
  • Tomcat的架构与源码分析学习笔记_java_13
  • 调用​​StandardServer.init()​​​,其中执行了​​LifecycleBase的init()​​​,其中又执行了​​StandardServer​​​的​​initInternal()​​​方法重要的来了,其中遍历了​​services​​​(即servcice服务),执行​​Service​​​服务的​​init()​​​,接着其中继续执行​​Engine​​​以及​​Connector​​​的​​init()​​。
  • 注意在​​Engine​​​的​​initInternal()​​​中(即组件初始化),创建了一个线程池​​ThreadPoolExecutor​​,这个线程池在engine组件的start阶段使用。目的是由于engine可以有多个Host所以若是有多个Host就放置在线程池中进行执行。
  • 依旧是在​​StandardServer​​​的​​initInternal()​​​方法中执行​​Listener​​​、​​Connector​​​的​​init()​​​,在连接器中有​​ProtocalHandler​​​实例以及Adapter实例,后者先进行实例,前者绑定了配置文件中的端口8080实现监听并且默认使用​​NIO​​网络模型。

说明:这个初始化阶段主要就进行对应的实例化以及初始化操作,其中包含了读取配置文件server.xml的操作。



②启动阶段

Tomcat的架构与源码分析学习笔记_xml_14

较重要的说明多线程处理方式处理Host主机;​​start​​​过程中的​​HostConfig​​​中根据​​webapps​​​目录下进行遍历来进行创建​​context​​​实例,有三种解析方式​​xml​​​,​​war​​包,文件目录形式。通过多线程处理方式处理多个应用即​​context​​​,加载​​context​​​过程中进一步封装​​wrapper​​​也就是​​Servlet​​​。中间过程会创建​​work​​​目录用来存放​​jsp​​​生成的​​servlet​​。

在​​StandardContext​​​中的​​startInternal()​​​里的​​fireLifecycleEvent()​​​方法读某个​​Context​​​的​​web.xml​​配置文件。

之后就是​​connector​​​的启动过程,​​NIO​​​的多路复用解决多个无用请求切换的问题,使用​​poller​线程来检查​​selector​​​是否有数据到来的​​channel​​。

说明:先是进行​​Engine​​​的​​start()​​​启动,接着进行连接器​​Connector​​及其内部组件的启动。



2.4、Servlet请求处理链路

①Servlet请求处理分析

分析⼀个servlet是如何被tomcat处理的?

  • ⼀个serlvet请求—> 最终我们是需要找到能够处理当前​​serlvet​​​请求的​​servlet​​​实例 —>​​serlvet.service()​

Tomcat的架构与源码分析学习笔记_apache_15



②Servlet请求处理流程示意

​Poller​​线程是追踪⼊⼝

Tomcat的架构与源码分析学习笔记_apache_16

在​​HTTP11Processor​​​(处理HTTP1.1的处理器)中进行​​request​​​、​​response​​​对象的封装,之后将​​request​​​、​​response​​​交给​​Adapter​​​封装成​​ServletRequest​​​以及​​ServletResponse​​。

中间会根据你的请求地址去一一匹配​​Host​​​、​​Context​​​、​​Wrapper​​​封装到​​MappingData​​对象中,

Tomcat的架构与源码分析学习笔记_tomcat_17

在获取​​servlet​​​之后,会有一个​​FilterChain​​​链,首先会去执行过滤器链,过滤器链中具有​​request​​核心业务请求。

PS:一般过滤器你需要去实现Filter接口。



③Mapper组件体系结构

Tomcat的架构与源码分析学习笔记_java_18

Tomcat中使⽤Mapper机制重新封装了Host-context-wrapper(serlvet)之间的数据和关系当请求到来时,根据请求uri匹配出能够处理当前请求的那个Host、那个Context、那个Wrapper。那么此时mapper对象肯定已经初始化好了。

疑问:mapper对象数据是什么时候初始化的?

  • StandardService—>startInternal—>mapperListener.start()中完成mapper对象初始化。


小总结

首先进行初始化​​load()​​​,之后进行启动​​start()​​。

  • ①​​load()​​​过程:实例化​​Catalina​​​,接着实例​​Bootstrap​​​ 。先根据​​server.xml​​​来进行解析(解析出​​Server​​​),最大容器就是​​Catalina​​​容器中的​​Server​​​实例(​​StandardServer​​​)。其中执行了​​Service​​​服务的​​init()​​​,并执行​​Engine​​​以及​​Connector​​​的​​init()​​。
  • 在​​Engine​​进行组件初始化时创建了线程池,为之后启动做准备。
  • 在​​Connector​​​中创建其中的各个组件实例,绑定监听端口以及使用​​NIO​​网络模型。
  • ②​​start()​​阶段:多线程方式来处理多个Host主机(暂且只有一个​​localhost​​​)。通过​​HostConfig​​​类来遍历​​webapps​​​目录并创建​​Context​​实例,包含三种解析方式(xml、war包、文件目录)。接着多线程方式处理多个context应用,在加载​​context​​​过程中封装​​wrapper​​​(也就是​​servlet​​​),过程中创建work目录。在​​StandardContext​​​中的​​StartInternal()​​​里读取某个​​Context​​​的​​web.xml​​​配置文件。接着就是​​Connector​​​的启动,使用​​NIO​​网络模型进行通信,其中使用poller线程来检查selector是否有数据到来的channel

​Servlet​​​请求处理:通过​​Pollezr​​线程检测出需要处理的Socket后交给其他线程来处理,使用​​Processor​​​解析处理​​socket​​​,封装​​Request​​​以及​​Response​​​。之后使用适配器​​CoyoteAdapter​​​来将​​Request​​​及​​Response​​​进行适配转换为​​Servletxxx​​​,并进行路径映射(匹配​​Host​​​、​​Context​​​、​​Wrapper​​​,匹配过程通过​​Mapper​​​组件进行)。匹配到之后调用​​Wrapper​​​得到​​Servlet​​​,进行构造​​FilterChain​​(过滤器链)。

注意:​​Servlet​​​核心业务是在​​FilterChain​​过滤器链中进行的!



参考文章

[1]. Tomcat启动过程源码分析一 从startup.bat开始分析整个执行过程

[2]. META-INF目录是干啥用的?

较好文章,值的反复查看

Tomcat运行过程和简单模拟

tomcat-------------tomcat文件结构及简介

请求在Tomcat中的运行流程


我是长路,感谢你的耐心阅读。如有问题请指出,我会积极采纳!
欢迎关注我的公众号【长路Java】,分享Java学习文章及相关资料
Q群:851968786 我们可以一起探讨学习
注明:转载可,需要附带上文章链接




举报

相关推荐

0 条评论