java tomcat源码_详解Tomcat系列(一)-从源码分析Tomcat的启动
在整個Tomcat系列文章講解之前, 我想說的是雖然整個Tomcat體系比較復雜, 但是Tomcat中的代碼并不難讀, 只要認真花點功夫, 一定能啃下來.
由于篇幅的原因, 很難把Tomcat所有的知識點都放到同一篇文章中, 我將把Tomcat系列文章分為Tomcat的啟動, Tomcat中各模塊的介紹和Tomcat中的設計模式三部分, 歡迎閱讀與關注.
一:通過idea搭建Tomcat源碼閱讀環境
首先我們到Tomcat的官網(http://tomcat.apache.org/)上下載Tomcat的源碼, 本文分析的Tomcat版本是Tomcat7.0.94.
進入官網后在左邊的目錄中選擇Tomcat7, 然后到頁面末尾的源碼區進行下載.
下載完成后打開idea, 選擇File->Open->選擇tomcat的源碼目錄
然后到項目配置中設置JDK和源碼目錄. File->Project Structure->project SDK
設置完畢后我們便可以開始愉快的源碼閱讀之旅了. (eclipse或其他ide搭建環境的思路也是差不多的, 可以摸索一下).
二:startup源碼分析
當我們初學tomcat的時候, 肯定先要學習怎樣啟動tomcat. 在tomcat的bin目錄下有兩個啟動tomcat的文件, 一個是startup.bat, 它用于windows環境下啟動tomcat; 另一個是startup.sh, 它用于linux環境下tomcat的啟動. 兩個文件中的邏輯是一樣的, 我們只分析其中的startup.bat.
下面給出startup.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文件, 如果有參數則帶上參數.
總結: startup.bat文件實際上就做了一件事情: 啟動catalina.bat.
ps: 這樣看來, 在windows下啟動tomcat未必一定要通過startup.bat, 用catalina.bat start也是可以的.
catalina.bat源碼分析
為了不讓文章看起來太臃腫, 這里就不先把整個文件貼出來了, 我們逐塊代碼進行分析.
跳過開頭的注釋, 我們來到下面的代碼段:
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" %*
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方法.
ps: 分析完整個catalina.bat我們發現我們可以通過設定不同的參數讓tomcat以不同的方式運行. 在ide中我們是可以選擇debug等模式啟動tomcat的, 也可以為其配置參數, 在catalina.bat中我們看到了啟動tomcat背后的運作流程.
Tomcat啟動流程分析
上面我們從運行startup.bat一路分析來到Bootstrap的啟動, 下面我們先對Tomcat中的組件進行一次總覽. 由于篇幅的原因, tomcat中各個模塊的關系將留到下一篇文章敘述, 這里先給出一張tomcat中各模塊的關系圖.
從圖中我們可以看出tomcat中模塊還是挺多的, 那么tomcat是如何啟動這些模塊的呢? 請看下面這張示意圖.
由圖中我們可以看到從Bootstrap類的main方法開始, tomcat會以鏈的方式逐級調用各個模塊的init()方法進行初始化, 待各個模塊都初始化后, 又會逐級調用各個模塊的start()方法啟動各個模塊. 下面我們通過部分源碼看一下這個過程.
首先我們來到Bootstrap類的main方法
public static void main(String args[]) {
if (daemon == null) {
// Don't set daemon until init() has completed
Bootstrap bootstrap = new Bootstrap();
try {
bootstrap.init();
} catch (Throwable t) {
handleThrowable(t);
t.printStackTrace();
return;
}
daemon = bootstrap;
} else {
// When running as a service the call to stop will be on a new
// thread so make sure the correct class loader is used to prevent
// a range of class not found exceptions.
Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
}
try {
String command = "start";
if (args.length > 0) {
command = args[args.length - 1];
}
if (command.equals("startd")) {
args[args.length - 1] = "start";
daemon.load(args);
daemon.start();
} else if (command.equals("stopd")) {
args[args.length - 1] = "stop";
daemon.stop();
} else if (command.equals("start")) {
daemon.setAwait(true);
daemon.load(args);
daemon.start();
if (null == daemon.getServer()) {
System.exit(1);
}
} else if (command.equals("stop")) {
daemon.stopServer(args);
} else if (command.equals("configtest")) {
daemon.load(args);
if (null == daemon.getServer()) {
System.exit(1);
}
System.exit(0);
} else {
log.warn("Bootstrap: command \"" + command + "\" does not exist.");
}
} catch (Throwable t) {
// Unwrap the Exception for clearer error reporting
// 省略
System.exit(1);
}
}
我們可以看到, Bootstrap類首先會創建一個本類對象, 然后調用init()方法進行初始化. 執行完init()方法后會判斷啟動參數的值, 由于我們采取默認的啟動方式, 所以main方法的參數是start, 會進入下面的判斷代碼塊
else if (command.equals("start")) {
daemon.setAwait(true);
daemon.load(args);
daemon.start();
if (null == daemon.getServer()) {
System.exit(1);
}
可以看到在設置等待后, 調用了本類對象的load()方法. 我們跟進查看load()方法的源碼.
private void load(String[] arguments)
throws Exception {
// Call the load() method
String methodName = "load";
Object param[];
Class> paramTypes[];
if (arguments==null || arguments.length==0) {
paramTypes = null;
param = null;
} else {
paramTypes = new Class[1];
paramTypes[0] = arguments.getClass();
param = new Object[1];
param[0] = arguments;
}
Method method =
catalinaDaemon.getClass().getMethod(methodName, paramTypes);
if (log.isDebugEnabled())
log.debug("Calling startup class " + method);
method.invoke(catalinaDaemon, param);
}
可以看到方法的最后通過反射的方式調用了成員變量catalinaDaemon的load()方法. 通過跟蹤源碼我們可以看到catalinaDaemon是Bootstrap類的一個私有成員變量
/**
* Daemon reference.
*/
private Object catalinaDaemon = null;
它會在Bootstrap的init()方法中通過反射的方式完成初始化. 下面我們回過頭來看init()方法的源碼.
public void init()
throws Exception
{
// Set Catalina path
setCatalinaHome();
setCatalinaBase();
initClassLoaders();
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
// Load our startup class and call its process() method
if (log.isDebugEnabled())
log.debug("Loading startup class");
Class> startupClass =
catalinaLoader.loadClass
("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.newInstance();
// Set the shared extensions class loader
if (log.isDebugEnabled())
log.debug("Setting startup class properties");
String methodName = "setParentClassLoader";
Class> paramTypes[] = new Class[1];
paramTypes[0] = Class.forName("java.lang.ClassLoader");
Object paramValues[] = new Object[1];
paramValues[0] = sharedLoader;
Method method =
startupInstance.getClass().getMethod(methodName, paramTypes);
method.invoke(startupInstance, paramValues);
catalinaDaemon = startupInstance;
}
可以看到init()方法創建了一個Catalina對象, 并把該對象賦給了catalinaDaemon.
再回過頭來看之前分享給大家的啟動流程圖, 對應的是這一塊
后面的過程我就不逐一分析了, 你可以按照這張圖去跟蹤代碼進行驗證. 最終所有模塊都能被初始化并啟動.
本篇結束
看到這里相信你應該對tomcat的啟動過程已經有一個清晰的認識了, 但是這僅僅是理解tomcat的開始, 下一篇文章開始我將會詳細介紹tomcat中的組件, 以及組件間的關系. 了解了各個組件后, tomcat的結構于你而言將不再神秘.
擴展閱讀:
總結
以上是生活随笔為你收集整理的java tomcat源码_详解Tomcat系列(一)-从源码分析Tomcat的启动的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux mysql清除缓存_案例:通
- 下一篇: 逸动1.6自动挡变速箱加油口在那个位置?