Tomcat - 源码分析准备和分析入口
上文我们介绍了Tomcat的架构设计,接下来我们便可以下载源码以及寻找源码入口了。@pdai
源代码下载和编译
首先是去官网下载Tomcat的源代码和二进制安装包,我这里分析最新的Tomcat9.0.39稳定版本https://tomcat.apache.org/download-90.cgi
下载二进制包和源码
下载二进制包的主要目的在于,让我们回顾一下包中的内容;其次,在我们后面通过源码包编译后,以方便和二进制包进行对比。
- 下载两个包
- 查看二进制包中主要模块
编译源码
- 导入IDEA
- 使用ant编译
理解编译后模块
这里有两点要注意下:第一:在编译完之后,编译输出到哪里了呢?第二:编译后的结果是不是和我们下载的二进制文件对的上呢?
- 编译的输出在哪里
- 编译的输出结果是否对的上,很显然和上面的二进制包一致
从启动脚本定位Tomcat源码入口
好了,到这里我们基本上已经有准备好代码了,接下来便是寻找代码入口了。@pdai
startup.bat
当我们初学tomcat的时候, 肯定先要学习怎样启动tomcat. 在tomcat的bin目录下有两个启动tomcat的文件, 一个是startup.bat, 它用于windows环境下启动tomcat; 另一个是startup.sh, 它用于linux环境下tomcat的启动. 两个文件中的逻辑是一样的, 我们只分析其中的startup.bat.
- startup.bat的源码: startup.bat文件实际上就做了一件事情: 启动catalina.bat.
@echo off
rem Licensed to the Apache Software Foundation (ASF) under one or more
rem contributor license agreements. See the NOTICE file distributed with
rem this work for additional information regarding copyright ownership.
rem The ASF licenses this file to You under the Apache License, Version 2.0
rem (the "License"); you may not use this file except in compliance with
rem the License. You may obtain a copy of the License at
rem
rem http://www.apache.org/licenses/LICENSE-2.0
rem
rem Unless required by applicable law or agreed to in writing, software
rem distributed under the License is distributed on an "AS IS" BASIS,
rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
rem See the License for the specific language governing permissions and
rem limitations under the License.
rem ---------------------------------------------------------------------------
rem Start script for the CATALINA Server
rem ---------------------------------------------------------------------------
setlocal
rem Guess CATALINA_HOME if not defined
set "CURRENT_DIR=%cd%"
if not "%CATALINA_HOME%" == "" goto gotHome
set "CATALINA_HOME=%CURRENT_DIR%"
if exist "%CATALINA_HOME%incatalina.bat" goto okHome
cd ..
set "CATALINA_HOME=%cd%"
cd "%CURRENT_DIR%"
:gotHome
if exist "%CATALINA_HOME%incatalina.bat" goto okHome
echo The CATALINA_HOME environment variable is not defined correctly
echo This environment variable is needed to run this program
goto end
:okHome
set "EXECUTABLE=%CATALINA_HOME%incatalina.bat"
rem Check that target executable exists
if exist "%EXECUTABLE%" goto okExec
echo Cannot find "%EXECUTABLE%"
echo This file is needed to run this program
goto end
:okExec
rem Get remaining unshifted command line arguments and save them in the
set CMD_LINE_ARGS=
:setArgs
if ""%1""=="""" goto doneSetArgs
set CMD_LINE_ARGS=%CMD_LINE_ARGS% %1
shift
goto setArgs
:doneSetArgs
call "%EXECUTABLE%" start %CMD_LINE_ARGS%
:end
- 当然如果你感兴趣,不妨也可以看下上面脚本的含义
- .bat文件中@echo是打印指令, 用于控制台输出信息, rem是注释符.
- 跳过开头的注释, 我们来到配置CATALINA_HOME的代码段, 执行startup.bat文件首先会设置CATALINA_HOME.
set "CURRENT_DIR=%cd%" if not "%CATALINA_HOME%" == "" goto gotHome set "CATALINA_HOME=%CURRENT_DIR%" if exist "%CATALINA_HOME%incatalina.bat" goto okHome cd .. set "CATALINA_HOME=%cd%" cd "%CURRENT_DIR%" :gotHome if exist "%CATALINA_HOME%incatalina.bat" goto okHome echo The CATALINA_HOME environment variable is not defined correctly echo This environment variable is needed to run this program goto end :okHome
- 先通过set指令把当前目录设置到一个名为CURRENT_DIR的变量中,
- 如果系统中配置过CATALINA_HOME则跳到gotHome代码段. 正常情况下我们的电脑都没有配置CATALINA_HOME, 所以往下执行, 把当前目录设置为CATALINA_HOME.
- 然后判断CATALINA_HOME目录下是否存在catalina.bat文件, 如果存在就跳到okHome代码块.
- 在okHome中, 会把catalina.bat文件的的路径赋给一个叫EXECUTABLE的变量, 然后会进一步判断这个路径是否存在, 存在则跳转到okExec代码块, 不存在的话会在控制台输出一些错误信息.
- 在okExec中, 会把setArgs代码块的返回结果赋值给CMD_LINE_ARGS变量, 这个变量用于存储启动参数.
- setArgs中首先会判断是否有参数, (if ""%1""==""""判断第一个参数是否为空), 如果没有参数则相当于参数项为空. 如果有参数则循环遍历所有的参数(每次拼接第一个参数).
- 最后执行call "%EXECUTABLE%" start %CMD_LINE_ARGS%, 也就是说执行catalina.bat文件, 如果有参数则带上参数.
这样看来, 在windows下启动tomcat未必一定要通过startup.bat, 用catalina.bat start也是可以的.
catalina.bat
catalina的脚本有点多,我们分开看:
- 跳过开头的注释, 我们来到下面的代码段:
setlocal
rem Suppress Terminate batch job on CTRL+C
if not ""%1"" == ""run"" goto mainEntry
if "%TEMP%" == "" goto mainEntry
if exist "%TEMP%\%~nx0.run" goto mainEntry
echo Y>"%TEMP%\%~nx0.run"
if not exist "%TEMP%\%~nx0.run" goto mainEntry
echo Y>"%TEMP%\%~nx0.Y"
call "%~f0" %* <"%TEMP%\%~nx0.Y"
rem Use provided errorlevel
set RETVAL=%ERRORLEVEL%
del /Q "%TEMP%\%~nx0.Y" >NUL 2>&1
exit /B %RETVAL%
:mainEntry
del /Q "%TEMP%\%~nx0.run" >NUL 2>&1
- 大多情况下我们启动tomcat都没有设置参数, 所以直接跳到mainEntry代码段, 删除了一个临时文件后, 继续往下执行.
rem Guess CATALINA_HOME if not defined
set "CURRENT_DIR=%cd%"
if not "%CATALINA_HOME%" == "" goto gotHome
set "CATALINA_HOME=%CURRENT_DIR%"
if exist "%CATALINA_HOME%incatalina.bat" goto okHome
cd ..
set "CATALINA_HOME=%cd%"
cd "%CURRENT_DIR%"
:gotHome
if exist "%CATALINA_HOME%incatalina.bat" goto okHome
echo The CATALINA_HOME environment variable is not defined correctly
echo This environment variable is needed to run this program
goto end
:okHome
rem Copy CATALINA_BASE from CATALINA_HOME if not defined
if not "%CATALINA_BASE%" == "" goto gotBase
set "CATALINA_BASE=%CATALINA_HOME%"
- 可以看到这段代码与startup.bat中开头的代码相似, 在确定CATALINA_HOME下有catalina.bat后把CATALINA_HOME赋给变量CATALINA_BASE.
rem Get standard environment variables
if not exist "%CATALINA_BASE%insetenv.bat" goto checkSetenvHome
call "%CATALINA_BASE%insetenv.bat"
goto setenvDone
:checkSetenvHome
if exist "%CATALINA_HOME%insetenv.bat" call "%CATALINA_HOME%insetenv.bat"
:setenvDone
rem Get standard Java environment variables
if exist "%CATALINA_HOME%insetclasspath.bat" goto okSetclasspath
echo Cannot find "%CATALINA_HOME%insetclasspath.bat"
echo This file is needed to run this program
goto end
:okSetclasspath
call "%CATALINA_HOME%insetclasspath.bat" %1
if errorlevel 1 goto end
rem Add on extra jar file to CLASSPATH
rem Note that there are no quotes as we do not want to introduce random
rem quotes into the CLASSPATH
if "%CLASSPATH%" == "" goto emptyClasspath
set "CLASSPATH=%CLASSPATH%;"
:emptyClasspath
set "CLASSPATH=%CLASSPATH%%CATALINA_HOME%inootstrap.jar"
上面这段代码依次执行了setenv.bat和setclasspath.bat文件, 目的是获得CLASSPATH, 相信会Java的同学应该都会在配置环境变量时都配置过classpath, 系统拿到classpath路径后把它和CATALINA_HOME拼接在一起, 最终定位到一个叫bootstrap.jar的文件. 虽然后面还有很多代码, 但是这里必须暂停提示一下: bootstrap.jar将是我们启动tomcat的环境.
- 接下来从gotTmpdir代码块到noEndorsedVar代码块进行了一些配置, 由于不是主要内容暂且跳过.
echo Using CATALINA_BASE: "%CATALINA_BASE%"
echo Using CATALINA_HOME: "%CATALINA_HOME%"
echo Using CATALINA_TMPDIR: "%CATALINA_TMPDIR%"
if ""%1"" == ""debug"" goto use_jdk
echo Using JRE_HOME: "%JRE_HOME%"
goto java_dir_displayed
:use_jdk
echo Using JAVA_HOME: "%JAVA_HOME%"
:java_dir_displayed
echo Using CLASSPATH: "%CLASSPATH%"
set _EXECJAVA=%_RUNJAVA%
set MAINCLASS=org.apache.catalina.startup.Bootstrap
set ACTION=start
set SECURITY_POLICY_FILE=
set DEBUG_OPTS=
set JPDA=
if not ""%1"" == ""jpda"" goto noJpda
set JPDA=jpda
if not "%JPDA_TRANSPORT%" == "" goto gotJpdaTransport
set JPDA_TRANSPORT=dt_socket
:gotJpdaTransport
if not "%JPDA_ADDRESS%" == "" goto gotJpdaAddress
set JPDA_ADDRESS=8000
:gotJpdaAddress
if not "%JPDA_SUSPEND%" == "" goto gotJpdaSuspend
set JPDA_SUSPEND=n
:gotJpdaSuspend
if not "%JPDA_OPTS%" == "" goto gotJpdaOpts
set JPDA_OPTS=-agentlib:jdwp=transport=%JPDA_TRANSPORT%,address=%JPDA_ADDRESS%,server=y,suspend=%JPDA_SUSPEND%
:gotJpdaOpts
shift
:noJpda
if ""%1"" == ""debug"" goto doDebug
if ""%1"" == ""run"" goto doRun
if ""%1"" == ""start"" goto doStart
if ""%1"" == ""stop"" goto doStop
if ""%1"" == ""configtest"" goto doConfigTest
if ""%1"" == ""version"" goto doVersion
- 接下来, 我们能看到一些重要的信息, 其中的重点是:
set _EXECJAVA=%_RUNJAVA%, 设置了jdk中bin目录下的java.exe文件路径.
set MAINCLASS=org.apache.catalina.startup.Bootstrap, 设置了tomcat的启动类为Bootstrap这个类. (后面会分析这个类)
set ACTION=start设置tomcat启动
大家可以留意这些参数, 最后执行tomcat的启动时会用到.
if not ""%1"" == ""jpda"" goto noJpda
set JPDA=jpda
if not "%JPDA_TRANSPORT%" == "" goto gotJpdaTransport
set JPDA_TRANSPORT=dt_socket
:gotJpdaTransport
if not "%JPDA_ADDRESS%" == "" goto gotJpdaAddress
set JPDA_ADDRESS=8000
:gotJpdaAddress
if not "%JPDA_SUSPEND%" == "" goto gotJpdaSuspend
set JPDA_SUSPEND=n
:gotJpdaSuspend
if not "%JPDA_OPTS%" == "" goto gotJpdaOpts
set JPDA_OPTS=-agentlib:jdwp=transport=%JPDA_TRANSPORT%,address=%JPDA_ADDRESS%,server=y,suspend=%JPDA_SUSPEND%
:gotJpdaOpts
shift
:noJpda
if ""%1"" == ""debug"" goto doDebug
if ""%1"" == ""run"" goto doRun
if ""%1"" == ""start"" goto doStart
if ""%1"" == ""stop"" goto doStop
if ""%1"" == ""configtest"" goto doConfigTest
if ""%1"" == ""version"" goto doVersion
- 接着判断第一个参数是否是jpda, 是则进行一些设定. 而正常情况下第一个参数是start, 所以跳过这段代码. 接着会判断第一个参数的内容, 根据判断, 我们会跳到doStart代码段. (有余力的同学不妨看下debug, run等启动方式)
:doStart
shift
if "%TITLE%" == "" set TITLE=Tomcat
set _EXECJAVA=start "%TITLE%" %_RUNJAVA%
if not ""%1"" == ""-security"" goto execCmd
shift
echo Using Security Manager
set "SECURITY_POLICY_FILE=%CATALINA_BASE%confcatalina.policy"
goto execCmd
- 可以看到doStart中无非也是设定一些参数, 最终会跳转到execCmd代码段
:execCmd
rem Get remaining unshifted command line arguments and save them in the
set CMD_LINE_ARGS=
:setArgs
if ""%1""=="""" goto doneSetArgs
set CMD_LINE_ARGS=%CMD_LINE_ARGS% %1
shift
goto setArgs
:doneSetArgs
可以看到这段代码也是在拼接参数, 把参数拼接到一个叫CMD_LINE_ARGS的变量中, 接下来就是catalina最后的一段代码了.
rem Execute Java with the applicable properties
if not "%JPDA%" == "" goto doJpda
if not "%SECURITY_POLICY_FILE%" == "" goto doSecurity
%_EXECJAVA% %LOGGING_CONFIG% %LOGGING_MANAGER% %JAVA_OPTS% %CATALINA_OPTS% %DEBUG_OPTS% -D%ENDORSED_PROP%="%JAVA_ENDORSED_DIRS%" -classpath "%CLASSPATH%" -Dcatalina.base="%CATALINA_BASE%" -Dcatalina.home="%CATALINA_HOME%" -Djava.io.tmpdir="%CATALINA_TMPDIR%" %MAINCLASS% %CMD_LINE_ARGS% %ACTION%
goto end
:doSecurity
%_EXECJAVA% %LOGGING_CONFIG% %LOGGING_MANAGER% %JAVA_OPTS% %CATALINA_OPTS% %DEBUG_OPTS% -D%ENDORSED_PROP%="%JAVA_ENDORSED_DIRS%" -classpath "%CLASSPATH%" -Djava.security.manager -Djava.security.policy=="%SECURITY_POLICY_FILE%" -Dcatalina.base="%CATALINA_BASE%" -Dcatalina.home="%CATALINA_HOME%" -Djava.io.tmpdir="%CATALINA_TMPDIR%" %MAINCLASS% %CMD_LINE_ARGS% %ACTION%
goto end
:doJpda
if not "%SECURITY_POLICY_FILE%" == "" goto doSecurityJpda
%_EXECJAVA% %LOGGING_CONFIG% %LOGGING_MANAGER% %JAVA_OPTS% %JPDA_OPTS% %CATALINA_OPTS% %DEBUG_OPTS% -D%ENDORSED_PROP%="%JAVA_ENDORSED_DIRS%" -classpath "%CLASSPATH%" -Dcatalina.base="%CATALINA_BASE%" -Dcatalina.home="%CATALINA_HOME%" -Djava.io.tmpdir="%CATALINA_TMPDIR%" %MAINCLASS% %CMD_LINE_ARGS% %ACTION%
goto end
:doSecurityJpda
%_EXECJAVA% %LOGGING_CONFIG% %LOGGING_MANAGER% %JAVA_OPTS% %JPDA_OPTS% %CATALINA_OPTS% %DEBUG_OPTS% -D%ENDORSED_PROP%="%JAVA_ENDORSED_DIRS%" -classpath "%CLASSPATH%" -Djava.security.manager -Djava.security.policy=="%SECURITY_POLICY_FILE%" -Dcatalina.base="%CATALINA_BASE%" -Dcatalina.home="%CATALINA_HOME%" -Djava.io.tmpdir="%CATALINA_TMPDIR%" %MAINCLASS% %CMD_LINE_ARGS% %ACTION%
goto end
:end
- 跳过前面两行判断后, 来到了关键语句:
%_EXECJAVA% %LOGGING_CONFIG% %LOGGING_MANAGER% %JAVA_OPTS% %CATALINA_OPTS% %DEBUG_OPTS% -D%ENDORSED_PROP%="%JAVA_ENDORSED_DIRS%" -classpath "%CLASSPATH%" -Dcatalina.base="%CATALINA_BASE%" -Dcatalina.home="%CATALINA_HOME%" -Djava.io.tmpdir="%CATALINA_TMPDIR%" %MAINCLASS% %CMD_LINE_ARGS% %ACTION%
_EXECJAVA也就是_RUNJAVA, 也就是平时说的java指令, 但在之前的doStart代码块中把_EXECJAVA改为了start "%TITLE%" %_RUNJAVA%, 所以系统会另启一个命令行窗口, 名字叫Tomcat.
在拼接一系列参数后, 我们会看见%MAINCLASS%, 也就是org.apache.catalina.startup.Bootstrap启动类, 拼接完启动参数后, 最后拼接的是%ACTION%, 也就是start.
总结:
- catalina.bat最终执行了Bootstrap类中的main方法.
- 我们可以通过设定不同的参数让tomcat以不同的方式运行. 在ide中我们是可以选择debug等模式启动tomcat的, 也可以为其配置参数, 在catalina.bat中我们看到了启动tomcat背后的运作流程.
参考文章
相关文章
- Tomcat - 如何设计一个简单的web容器
- 在学习Tomcat前,很多人先入为主的对它的认知是巨复杂的;所以第一步,在学习它之前,要打破这种观念,我们通过学习如何设计一个最基本的web容器来看它需要考虑什么;进而在真正学习Tomcat时,多把重点放在它的顶层设计上,而不是某一块代码上, 思路永远比具体实现重要的多。
- Tomcat - 理解Tomcat架构设计
- 前文我们已经介绍了一个简单的Servlet容器是如何设计出来,我们就可以开始正式学习Tomcat了,在学习开始,我们有必要站在高点去看看Tomcat的架构设计。
- Tomcat - 源码分析准备和分析入口
- 上文我们介绍了Tomcat的架构设计,接下来我们便可以下载源码以及寻找源码入口了。
- Tomcat - 启动过程:初始化和启动流程
- 在有了Tomcat架构设计和源码入口以后,我们便可以开始真正读源码了。
- Tomcat - 启动过程:类加载机制详解
- 上文我们讲了Tomcat在初始化时会初始化classLoader。本文将具体分析Tomcat的类加载机制,特别是区别于传统的
双亲委派模型
的加载机制。
- 上文我们讲了Tomcat在初始化时会初始化classLoader。本文将具体分析Tomcat的类加载机制,特别是区别于传统的
- Tomcat - 启动过程:Catalina的加载
- 通过前两篇文章,我们知道了Tomcat的类加载机制和整体的组件加载流程;我们也知道通过Bootstrap初始化的catalinaClassLoader加载了Catalina,那么进而引入了一个问题就是Catalina是如何加载的呢?加载了什么呢?本文将带你进一步分析。
- Tomcat - 组件生命周期管理:LifeCycle
- 上文中,我们已经知道Catalina初始化了Server(它调用了 Server 类的 init 和 start 方法来启动 Tomcat);你会发现Server是Tomcat的配置文件server.xml的顶层元素,那这个阶段其实我们已经进入到Tomcat内部组件的详解;这时候有一个问题,这么多组件是如何管理它的生命周期的呢?
- Tomcat - 组件拓展管理:JMX和MBean
- 我们在前文中讲Lifecycle以及组件,怎么会突然讲JMX和MBean呢?本文通过承接上文Lifecycle讲Tomcat基于JMX的实现。
- Tomcat - 事件的监听机制:观察者模式
- 本文承接上文中Lifecycle中实现,引出Tomcat的监听机制。
- Tomcat - Server的设计和实现: StandardServer
- 基于前面的几篇文章,我们终于可以总体上梳理Server的具体实现了,这里体现在StandardServer具体的功能实现上。
- Tomcat - Service的设计和实现: StandardService
- 上文讲了Server的具体实现了,本文主要讲Service的设计和实现;我们从上文其实已经知道Server中包含多个service了。
- Tomcat - 线程池的设计与实现:StandardThreadExecutor
- 上文中我们研究了下Service的设计和实现,StandardService中包含Executor的调用;这个比较好理解,Tomcat需要并发处理用户的请求,自然而言就想到线程池,那么Tomcat中线程池(Executor)具体是如何实现的?本文带你继续深度解析。
- Tomcat - Request请求处理: Container设计
- 在理解了Server,Service和Executor后,我们可以进入Request处理环节了。我们知道客户端是可以发起多个请求的,Tomcat也是可以支持多个webapp的,有多个上下文,且一个webapp中可以有多个Servlet...等等,那么Tomcat是如何设计组件来支撑请求处理的呢?本节文将介绍Tomcat的Container设计。
- Tomcat - Container容器之Engine:StandardEngine
- 上文已经知道Container的整体结构和设计,其中Engine其实就是Servlet Engine,负责处理request的顶层容器。
- Tomcat - Container的管道机制:责任链模式
- 上文中介绍了Engine的设计,其中有Pipline相关内容没有介绍,本文将向你阐述Tomcat的管道机制以及它要解决的问题。
- Tomcat - Request请求处理过程:Connector
- 本文主要介绍request请求的处理过程。