源码分析准备和分析入口
一:源代码下载和编译
首先是去官网下载Tomcat的源代码和二进制安装包,这里分析最新的tomcat9.0.91
要同时下载二进制安装包和源码包:【windows系统下载对应的zip包,图示是mac系统的】

二进制文件包的主要目录说明

二:编译源码
导入到IDEA中之后,点击build.xml,使用ant进行deploy

理解编译后模块
- 在编译完之后,编译输出到哪里了呢(output/build)
- 编译后的结果是不是和我们下载的二进制文件对的上呢(可以发现是对上的)

三:从启动脚本定位Tomcat源码入口
到这里我们基本上已经有准备好代码了,接下来便是寻找代码入口了
1:startup.bat
在tomcat的bin目录下有两个启动tomcat的文件:
- 一个是startup.bat, 它用于windows环境下启动tomcat;
- 另一个是startup.sh, 它用于linux环境下tomcat的启动
两个文件逻辑是相同的,这里只讨论startup.bat。startup.bat文件实际上就做了一件事情: 启动catalina.bat.
@echo off
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ---------------------------------------------------------------------------
# Start script for the CATALINA Server
# ---------------------------------------------------------------------------
setlocal
# 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%\bin\catalina.bat" goto okHome
cd ..
set "CATALINA_HOME=%cd%"
cd "%CURRENT_DIR%"
:gotHome
if exist "%CATALINA_HOME%\bin\catalina.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%\bin\catalina.bat"
# 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
# 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% # 调用catalina.bat
:end
如果感兴趣,可以看看下面的脚本的含义:
-
.bat文件中@echo是打印指令, 用于控制台输出信息 -
rem是注释符,后面跟的是注释内容 -
跳过开头的注释, 我们来到配置CATALINA_HOME的代码段, 执行startup.bat文件首先会设置CATALINA_HOME
set "CURRENT_DIR=%cd%" # 先通过set指令把当前目录设置到一个名为CURRENT_DIR的变量中
if not "%CATALINA_HOME%" == "" goto gotHome
set "CATALINA_HOME=%CURRENT_DIR%"
if exist "%CATALINA_HOME%\bin\catalina.bat" goto okHome
cd ..
set "CATALINA_HOME=%cd%"
cd "%CURRENT_DIR%"
:gotHome
if exist "%CATALINA_HOME%\bin\catalina.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也是可以的.
2:catalina.bat
跳过开头的注释, 我们来到下面的代码段:
# 该批处理脚本的主要目的是在作业运行期间捕获和处理CTRL+C中断信号,以避免作业被意外中断。
setlocal # 启用局部环境变量,防止批处理脚本修改影响外部环境。
# 检查命令行参数,如果第一个参数不是"run",则跳转到mainEntry标签。
if not ""%1"" == ""run"" goto mainEntry
# 检查临时文件夹%TEMP%是否存在,如果不存在,则跳转到mainEntry标签
if "%TEMP%" == "" goto mainEntry
# 检查是否存在名为%~nx0.run的文件,如果存在,则跳转到mainEntry标签。
if exist "%TEMP%\%~nx0.run" goto mainEntry
# 创建一个名为%~nx0.run的文件,并写入字符"Y"。
echo Y>"%TEMP%\%~nx0.run"
# 检查是否成功创建了%~nx0.run文件,如果没有,则跳转到mainEntry标签。
if not exist "%TEMP%\%~nx0.run" goto mainEntry
# 创建一个名为%~nx0.Y的文件,并写入字符"Y"。
echo Y>"%TEMP%\%~nx0.Y"
# 调用当前批处理脚本自身,并将所有命令行参数传递给它,同时从%~nx0.Y文件中读取输入。
call "%~f0" %* <"%TEMP%\%~nx0.Y"
# 设置RETVAL变量为上一个命令的退出码。
set RETVAL=%ERRORLEVEL%
# 删除%~nx0.Y文件。
del /Q "%TEMP%\%~nx0.Y" >NUL 2>&1
# 使用exit /B命令退出批处理脚本,并将RETVAL作为退出码。
exit /B %RETVAL%
# :mainEntry标签:删除%~nx0.run文件。
:mainEntry
del /Q "%TEMP%\%~nx0.run" >NUL 2>&1
大多情况下我们启动tomcat都没有设置参数, 所以直接跳到mainEntry代码段, 删除了一个临时文件后, 继续往下执行:
# 设置当前目录变量
set "CURRENT_DIR=%cd%"
# 如果已经定义了CATALINA_HOME变量,则跳过查找过程
if not "%CATALINA_HOME%" == "" goto gotHome
# 首先尝试将当前目录设置为CATALINA_HOME
set "CATALINA_HOME=%CURRENT_DIR%"
# 检查是否找到了catalina.bat,如果找到则跳转到okHome
if exist "%CATALINA_HOME%\bin\catalina.bat" goto okHome
# 如果没有找到,上移一级目录,然后再次尝试
cd ..
set "CATALINA_HOME=%cd%"
cd "%CURRENT_DIR%"
:gotHome
# 再次检查是否找到了catalina.bat,如果找到则跳转到okHome
if exist "%CATALINA_HOME%\bin\catalina.bat" goto okHome
# 如果没有找到,提示用户CATALINA_HOME环境变量未正确设置
echo The CATALINA_HOME environment variable is not defined correctly
echo This environment variable is needed to run this program
goto end
:okHome
# 如果CATALINA_BASE未定义,则从CATALINA_HOME复制值
# 如果已经定义了CATALINA_BASE变量,则跳过复制过程
if not "%CATALINA_BASE%" == "" goto gotBase
set "CATALINA_BASE=%CATALINA_HOME%"
:gotBase
可以看到这段代码与startup.bat中开头的代码相似,
在确定CATALINA_HOME下有catalina.bat后把CATALINA_HOME赋给变量CATALINA_BASE
# 检查并调用setenv.bat脚本
if not exist "%CATALINA_BASE%\bin\setenv.bat" goto checkSetenvHome
call "%CATALINA_BASE%\bin\setenv.bat"
goto setenvDone
:checkSetenvHome
if exist "%CATALINA_HOME%\bin\setenv.bat" call "%CATALINA_HOME%\bin\setenv.bat"
:setenvDone
# 获取标准Java环境变量
if exist "%CATALINA_HOME%\bin\setclasspath.bat" goto okSetclasspath
echo Cannot find "%CATALINA_HOME%\bin\setclasspath.bat"
echo This file is needed to run this program
goto end
:okSetclasspath
call "%CATALINA_HOME%\bin\setclasspath.bat" %1
if errorlevel 1 goto end
# 向CLASSPATH添加额外的jar文件
# 注意,这里没有使用引号,因为我们不希望在CLASSPATH中引入随机的引号
if "%CLASSPATH%" == "" goto emptyClasspath
set "CLASSPATH=%CLASSPATH%;"
:emptyClasspath
set "CLASSPATH=%CLASSPATH%%CATALINA_HOME%\bin\bootstrap.jar"
上面这段代码依次执行了setenv.bat和setclasspath.bat文件, 目的是获得CLASSPATH
相信会Java的同学应该都会在配置环境变量时都配置过classpath, 系统拿到classpath路径后把它和CATALINA_HOME拼接在一起, 最终定位到一个叫bootstrap.jar的文件. 虽然后面还有很多代码
bootstrap.jar将是我们启动tomcat的环境
接下来, 我们能看到一些重要的信息, 其中的重点是:
set _EXECJAVA=%_RUNJAVA% # 设置了jdk中bin目录下的java.exe文件路径.
set MAINCLASS=org.apache.catalina.startup.Bootstrap # 设置了tomcat的启动类为Bootstrap这个类. (后面会分析这个类)
set ACTION=start # 设置tomcat启动
接着判断第一个参数是否是jpda, 是则进行一些设定
而正常情况下第一个参数是start, 所以跳过这段代码. 接着会判断第一个参数的内容
根据判断, 我们会跳到doStart代码段
# 判断第一个参数是否为"jpda",若不是,则跳转到:noJpda
if not ""%1"" == ""jpda"" goto noJpda
# 设置JPDA变量为"jpda"
set JPDA=jpda
# 判断JPDA_TRANSPORT变量是否已设置,若没有,则跳转到:gotJpdaTransport
if not "%JPDA_TRANSPORT%" == "" goto gotJpdaTransport
# 设置JPDA_TRANSPORT变量为"dt_socket"
set JPDA_TRANSPORT=dt_socket
:gotJpdaTransport
# 判断JPDA_ADDRESS变量是否已设置,若没有,则跳转到:gotJpdaAddress
if not "%JPDA_ADDRESS%" == "" goto gotJpdaAddress
# 设置JPDA_ADDRESS变量为"localhost:8000"
set JPDA_ADDRESS=localhost:8000
:gotJpdaAddress
# 判断JPDA_SUSPEND变量是否已设置,若没有,则跳转到:gotJpdaSuspend
if not "%JPDA_SUSPEND%" == "" goto gotJpdaSuspend
# 设置JPDA_SUSPEND变量为"n"
set JPDA_SUSPEND=n
:gotJpdaSuspend
# 判断JPDA_OPTS变量是否已设置,若没有,则跳转到:gotJpdaOpts
if not "%JPDA_OPTS%" == "" goto gotJpdaOpts
# 根据其他变量的值,设置JPDA_OPTS变量,用于配置JDWP代理库的参数
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
可以看到doStart中无非设置一些参数,最终会跳转到execCmd代码段
: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%\conf\catalina.policy"
goto execCmd # 最终会跳到这里
# 可以看到这段代码也是在拼接参数
# 把参数拼接到一个叫CMD_LINE_ARGS的变量中
: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
接下来就是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
总结一下:
1:catalina.bat最终执行了Bootstrap类中的main方法.
2:我们可以通过设定不同的参数让tomcat以不同的方式运行,在ide中我们是可以选择debug等模式启动tomcat的, 也可以为其配置参数, 在catalina.bat中我们看到了启动tomcat背后的运作流程

被折叠的 条评论
为什么被折叠?



