Tomcat詳解系列(3) - 源碼分析準備和分析入口

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%\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"

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%\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也是可以的.

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%\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

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%\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

rem Get standard Java environment variables
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

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%\bin\bootstrap.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%\conf\catalina.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背後的運作流程.

參考文章

相關文章

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章