深入剖析Tomcat原理
一、 Tomcat源码部署和运行(intellij IDEA)
1、下载tomcat源码,以tomcat-8为例
链接: https://tomcat.apache.org/
2、源码部署到IDEA中
①创建新的空工程
②解压源码压缩包到该工程的目录(目录名最好是非中文和非空格组成的)下
③创建home文件,并将webapps和conf文件移入home文件中,目的是为了后期配置IDEA运行时参数时方便
④新建pom.xml文件,在其中添加tomcat运行时依赖:
<?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.42‐src</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>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>3.4</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>
⑤在这个空project项目新建maven项目:
⑥新建完maven项目后,添加application(用来启动/调试这些源码)
找到**java/org.apache/catalina/startup/bootstrap(类)**点击完成添加(主要是这个类就是整个tomcat启动的类,其中包含main()方法)
这时有可能添加错误,因为没有导入jdk
⑦不报错后设置运行时参数:
就是选择刚刚新建的home目录路径
参数:
-Dcatalina.home=C:/Users/14047/IdeaProjects/tomcat-src/apache-tomcat-8.5.42-src/home
-Dcatalina.base=C:/Users/14047/IdeaProjects/tomcat-src/apache-tomcat-8.5.42-src/home
-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
-Djava.util.logging.config.file=C:/Users/14047/IdeaProjects/tomcat-src/apache-tomcat-8.5.42-src/home/conf/logging.properties
注意
⑧以上设置无误后则可以启动
⑨浏览器访问Tomcat会出现问题:
原因:
解决:
搜索并找到这个ContextConfig类下的configureStart()方法
在该方法中的webConfig()方法下添加:
context.addServletContainerInitializer(new JasperInitializer(), null)
目的是手动将JSP解析器初始化
重新启动Tomcat问题解决
二、不得不提的HTTP工作流程与DNS解析详细过程
三、Tomcat整体架构
1)Http服务器的执行逻辑的两种方法:
1、Http服务器接收请求,根据请求先做一个判断,找到对应的业务类执行具体业务逻辑,这种方法耦合度过高(不推荐)
2、Http服务器不亲自调用业务类,而是直接把请求转发给tomcat,由tomcat去判断需要调用具体哪个类来执行业务操作。
显然第二种方法解耦合度比较好。
2)连接器+容器
Tomcat整体包含两大组件:连接器+容器 = service
这两大组件分别与之对应的是http服务器、servlet容器
1、 连接器:主要负责客户端浏览器传过来的请求+初始化request和response
2、 容器主要负责处理某个具体servlet的调用逻辑
浏览器发出请求----->连接器首先接收请求,构建request对象,在request对象构建中解析请求信息,封装相关属性(url、method…)。----->连接器将构建好的request对象传递给容器处理------->容器拿到request,根据request对象中的信息定位到哪个具体的servlet,并初始化且调用该servlet执行具体逻辑------>容器在调用某个servlet前已经将response对象构建好了,servlet执行完具体业务逻辑后,response对象中会包含响应数据,容器再将包含响应数据的response对象再响应回给连接器。------>连接器解析完response后再响应给浏览器
四、Tomcat整体架构——连接器(Coyote)与容器初讲
连接器在tomcat中称为coyote
Coyote主要负责底层socket连接的接收与响应,接收到浏览器的请求后,构建并封装进request对象,最终再转换成servletRequest对象传递给容器
将协议的处理、io的相关等操作交给coyote,将具体业务逻辑的操作等交给容器
一个service可以有多个连接器coyote和一个容器,但它们都不能单独对外提供服务,两者结合为service后,一个service才能对外提供服务。
一个service至少包含一个连接器和容器。
通过service接口就可看出其对应关系
向Service中添加Connector,可以是数组(add……)
给service设置一个容器只能是一个(set……)
连接器组件coyote的内部细节
1)连接器组件coyote的内部细节:
一个连接器中包含了四个组件:EndPoint、Processor、Adapter、ProtocolHandler
客户端浏览器发送请求至EndPoint(通信端点(专门用来接收发送过来的socket请求)),
EndPoint处理完后将请求又发送给处理器Processor,Processor将请求按照HTTP协议格式进行解析,拆封并封装成request对象。
对容器来说它只支持servletRequest规范,而对Processor来说它只提供Request,这时就需要用到适配器组件,将Request转换为servletRequest。
2)service中容器和连接器的整体地位
从源码中也能看出来,是一一对应的,一个service中有很多组件,核心就是容器
catalina:这个包包含启动项、容器、连接器(整体)、公共类等
coyote:这个包包含连接器(connector)下的各个组件(protocolHandler、adapter、processor等)
3)service内部细节
Contain(容器)具体结构详解:
Engine(引擎):管理多个虚拟站点(虚拟主机),也是一个service只能有一个引擎。一个引擎可管理多个Host。一个Host可包含多个Context(项目),一个Context下包含多个Wrapper(servlet)
通过server.xml就可以看出其中的层次关系与上描述相符
通过源码分析看出
容器中包含着这四个组件
五、Tomcat源码基础上深入分析启动流程
Tomcat启动时序图:
1、用户点击startup.bat即可启动tomcat
Startup.bat文件中的具体细节:
其中调用了catalina.bat这个文件
catalina.bat这个文件中又调用了bootStrap类中的main()方法来开启整个tomcat的启动
六、上手Debug 源码跟踪Tomcat各个步骤
明确tomcat入口点为BootStrap类中的main()方法:
Startup.bat—>Catalina.bat---->Bootstrap.main()
1、Application让Bootstrap.main()方法启动,此方法一启动Bootstrap类的静态属性先初始化
//获取到home文件目录
2、初始化完静态变量后来到main()方法体:
构建BootStrap,给非静态属性赋初值null
3、bootstrap.init()方法执行,初始化Bootstrap
①初始化类加载器
② 反射创建Catalina(容器)组件
Catalina组件的配置是依靠配置文件(server.xml)
最后init()方法结束,Catalina组件成功构建
初始化完成之后继续往下走:
③ 调用load方法:
匹配命令参数(“start”)
bootstrap调用load()方法
从以上可以看出:BootStrap的load()方法主要功能就是去调用Catalina组件的load方法。
Server.xml配置文件解析的相关设置完成,返回digester
Parse这一步是核心,因为这里在解析server.xml配置文件的同时时已将server接口的一个实现类创建出来(standardServer),并依据配置文件中的值,给standardServer的各个属性设置好相应的值,其中就包括services数组等
Parse前:
Parse后:
services(Server)中只有一个名为Server01的service
server.xml
这些组件在这一步只是依照server配置文件的配置项被构建出来,但组件中的一些必须的初始化还是要等到后续的init()方法完成。
④ 创建完Server后开始进行初始化init()
这里getServer()拿到Server,实现Server接口的是standardServer类
Server.init()根据多态,调用standardServer的init方法,standardServer无init()方法,根据继承默认调用父类 LifecycleMBeanBase的init()方法,发现LifecycleMBeanBase也无此方法
,则又去默认调用父类LifecycleBase的init()方法,诶发现有
则执行init()方法中的具体逻辑(this=standardServer,this.super.super.init()),其中又有initInternal()方法执行,此方法为抽象方法,standardServer中已实现,this.super.super.initInternal()调用时传的是this也就是standardServer,所以又跑回去执行standardServer中initInternal()方法具体逻辑
initInternal()方法中核心点就是对services数组中的每一个service进行初始化:
*(LifecycleMBeanBase类实现了LifeCycle接口,并抽取且提供了一些相同的代码功能,standardServer继承此类,重写相应方法,其它配置直接调用父类方法即可(模板思想)。)
⑤ 初始化每个service时同样利用多态思想,与server的初始化大致相同
This.super.super.init()方法初始化,其中initInternal()传递this,调用standardService的
引擎等都属于容器范畴,所以又多继承了一层containerBase
也就是:this.super.super.super.init(),同样的其中initInternal()还是回到StrandardEngine类中
引擎(Engine)初始化完毕。