Tomcat中间件-Catalina的加载load和start流程
前言:对于Tomcat的运行流程其实是十分有帮助的,因为自己之前有看到关于jolokia的反序列化,其中就都有涉及到关于LifeCycle的知识点,这里刚看到,所以会觉得有点开心!
如何进行寻找Tomcat的入口
在平常我们启动Tomcat程序的时候,windows上启动的bat为startup.bat,那我们这里就来看下startup.bat的内容
那么这里就继续打开catalina.bat文件进行观察,我在最后进行打印操作
运行效果图为如下:
D:\ALL\javaidea\apache-tomcat-8.5.50-src\bin>.\catalina.bat start Using CATALINA_BASE: "D:\ALL\javaidea\apache-tomcat-8.5.50-src" Using CATALINA_HOME: "D:\ALL\javaidea\apache-tomcat-8.5.50-src" Using CATALINA_TMPDIR: "D:\ALL\javaidea\apache-tomcat-8.5.50-src\temp" Using JRE_HOME: "C:\Program Files\Java\jdk1.8.0_181" Using CLASSPATH: "D:\ALL\javaidea\apache-tomcat-8.5.50-src\bin\bootstrap.jar;D:\ALL\javaidea\apache-tomcat-8.5.50-src\bin\tomcat-juli.jar" start "Tomcat" "C:\Program Files\Java\jdk1.8.0_181\bin\java.exe" -Dnop -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djdk.tls.ephemeralDHKeySize=2048 -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -Dignore.endorsed.dirs="" -classpath "D:\ALL\javaidea\apache-tomcat-8.5.50-src\bin\bootstrap.jar;D:\ALL\javaidea\apache-tomcat-8.5.50-src\bin\tomcat-juli.jar" -Dcatalina.base="D:\ALL\javaidea\apache-tomcat-8.5.50-src" -Dcatalina.home="D:\ALL\javaidea\apache-tomcat-8.5.50-src" -Djava.io.tmpdir="D:\ALL\javaidea\apache-tomcat-8.5.50-src\temp" org.apache.catalina.startup.Bootstrap start 11111
这里可以看到org.apache.catalina.startup.Bootstrap start
,则可以说明启动Main类的java类为org.apache.catalina.startup.Bootstrap
,所以我们也就找到了Tomcat的启动类为Bootstrap了
Tomcat入口点启动过程
首先是如下,实例化Bootstrap对象,主要就是调用了init的方法
public static void main(String args[]) { synchronized (daemonLock) { if (daemon == null) { // 实例化一个当前Bootstrap引导类对象 Bootstrap bootstrap = new Bootstrap(); try { // 执行当前类方法init做一些初始化动作 bootstrap.init(); } catch (Throwable t) { handleThrowable(t); t.printStackTrace(); return; } // 把当前Bootstrap引导类对象赋值给了一个变量daemon daemon = bootstrap; } else { Thread.currentThread().setContextClassLoader(daemon.catalinaLoader); } }
这里来看下init方法,是如何进行初始化的
初始化完了之后又出来继续走main方法中的代码,如下所示
这里稍微总结下:
关于catalina的load和start分析
因为这里对catalina对象进行load和start两个方法调用,所以这里继续来到org/apache/catalina/startup/Catalina.java来进行观察这两个方法的行为
Catalina对象的load - 初始化阶段
1、配置文件server.xml初始化
在Catalina对象的load方法打下断点,进行单步跟踪
先是通过Digester digester = createStartDigester();
创建用来解析server.xml文件的对象
接着后面就会通过文件流读取conf/server.xml,然后通过digester对象来进行读取存储到this.server属性中去
this.server属性的内容如下所示,也就是server.xml中的内容
2、server.init
这个里面,server就会开始对自己的Listener初始化,和service相关的组件进行初始化操作
然后再跟到initInternal方法中去,这个方法是每个组件都需要实现的一个方法,因为Tomcat中的所有组件都需要有对应的生命周期,那么就都需要实现LifeCycle的接口中的initInternal方法
那么首先会来到StandardServer对象中,Server会对自身的一些组件进行初始化,下面就是一些可能用到的,这些不重要,继续看下面
接着standardServer对象中走到后面,就可以看到对service结构中的组件初始化了,这里一般service只有一个,如果想要有多个其实也可以,但是正常来说默认都是一个service的
跟进去,可以看到首先进行组件初始化的是关于catalina的Engine组件
继续跟到Engine的init方法中
因为Catalina的Engine中又有套娃式的结构,所以这里应该都是对这些Host,Context,Wrapper组件的初始化了,下图所示,但是看到的却是关于线程池的代码(线程池提交线程,如果有多个HOST,那么就可以多次线程一起并行实例化HOST,便于加快tomcat的启动速度),这个线程池和相关Host,Context,rapper详细的会在engine组件的start阶段使用,留在后面来讲
Engine组件初始化完成了之后就开始相关的Connector组件的初始化了
其实这里的Connector一般都是有两个的,一个是HTTP 8080,还有一个是AJP 8009,这里就讲的话就拿HTTP 8080来讲了,关于AJP 8009的之后顺便和AJP文件读取漏洞来讲,正好一起学习...
这里继续跟进connector.init()
中的initInternal方法,可以看到这里就是coyote相关组件的初始化,可以看到Adapter组件的初始化
接着就是protocolHandler.init();
,跟进去一看可以发现首先是EndPoint组件的初始化
接着就是开始绑定端口,这里只是进行了绑定端口,还没有真正的开始进行接收请求,在下面的Catalina对象的start中才会开始,接下来下面继续讲Catalina的start方法
稍微总结下:
Catalina对象的start - 启动阶段
我们从上面的来看,这里重点的讲还是Engine的启动和Connector的启动,上面已经对这两个组件已经进行了加载操作,那么这里的Catalina的start也就是要来讲关于这两个组件的启动流程了
Engine的启动
这里先来讲Eninge组件的启动过程,首先还是在start的方法的入口点打下断下
老样子,还是通过实现的统一接口startInternal方法来进行调用,这里调用的对象是standardServer
接着就是开始启动service的过程,这里继续跟进service的start方法
一样的,此时调用的就是service实现的startInternal方法
在service中启动的就有相关Engine组件的操作,这里就跟进engine.start();
一样的,这里就会调用StandardEngine实现的startInternal方法
之前大家在Catalina的加载阶段的时候就看到,在standardEngine只创建了一系列的线程池,但是相关的HOST,Context,Wrapper都没有看到,那是因为加载阶段的时候这些都不需要,这些都会在启动阶段的时候用到,下面来慢慢讲解
这里就可以发现,Engine在这里获取了相关HOST,然后放入了加载阶段中创建的线程池中,
我这里想说下关于HOST、Context、Wrapper的调用,在Tomcat中都是通过一种叫做事件监听器的fireLifecycleEvent方法来完成的,当Engine被触发了,接着Engine又会去触发HOST,HOST又会去进行部署相关webapps目录下的东西,期间HOST又会去触发Context,将每个webapps下的应用的上下文的创建提交到加载阶段创建的线程池中,都是通过一种递进式的方法来进行调用,这里就不讲了
Connector的启动
上面的走完出来后又回到StandardService对象中,这时候继续来调用Connector对象的start方法
进入到其实现的startInternal方法中,这里会对其中的protocolHandler来进行启动
之前加载阶段的时候只是对endpoint进行了绑定端口的操作,这里则会对其进行启动操作
一样的,这里AbstractEndPoint调用了startInternal
这里有相关的poller的知识点,这里留在后面继续讲,然后这里的startAcceptorThreads方法会启动来进行accept外部的请求
稍微总结下:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY