Before I go through the entire Tomcat series, I want to point out that while the overall Tomcat system is complex, the code in Tomcat is not hard to read and can be chewed up with a bit of effort. Due to the length, it is difficult to put all knowledge points of Tomcat into one article. I will divide the Tomcat series into three parts: the start of Tomcat, the introduction of each module in Tomcat and the design mode in Tomcat. Welcome to read and pay attention to it.

Some videos have been recorded to explain Tomcat, which may be better understood based on some documents I have sorted out. If you need it, you can directly click the blue word L to get it.

One: Build the Tomcat source code reading environment through IDEA

  • First of all, we go to the official website of Tomcat to download the source code of Tomcat. The Version of Tomcat analyzed in this article is Tomcat7.0.94.
  • Once in the official website, select Tomcat7 from the directory on the left and go to the source code section at the end of the page to download it.

  • After downloading, Open idea and choose File->Open-> Tomcat source directory
  • Then go to the project configuration and set up the JDK and source directory. File->Project Structure->project SDK

  • Once set up, we are ready to have a fun reading of the source code. (The idea of eclipse or other IDE constructs is similar, you can explore it.)
  • If you think the article is a little difficult to understand, you can watch the video I recorded on Tomcat to help you study.

Startup source code analysis

  • When we first learn Tomcat, we must first learn how to start Tomcat. In the bin directory of tomcat, there are two files to start tomcat. One is startup.bat. The other is startup.sh, which is used to start Tomcat on Linux. The logic in both files is the same. We only analyze startup.bat.
  • The startup.bat source code is provided below
@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% :endCopy the code
  • The bat file@echoIs a print instruction used to output information to the console, and REM is an annotation.
  • Skip the initial comments and go to the code snippet that configureCATALINA_HOME. Executing the startup.bat file will first set 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 :okHomeCopy the code
  • Using the set directive to set the current directory to a variable named CURRENT_DIR,
  • If CATALINA_HOME is configured in the system, jump to the gotHome code snippet. Normally, CATALINA_HOME is not configured on our computer, so go ahead and set the current directory to CATALINA_HOME.
  • Then determine if catalina.bat exists in CATALINA_HOME and skip to the okHome code block if it does.
  • In okHome, the path of the Catalina. bat file is assigned to a variable called EXECUTABLE, and the EXECUTABLE will determine if the path exists, jump to the okExec block, and output some error messages to the console if it does not.
  • In okExec, the return result of the setArgs code block is assigned to the CMD_LINE_ARGS variable, which is used to store startup parameters.
  • SetArgs first checks if there are arguments, (if ""%1""==""""Check whether the first parameter is null), if there is no parameter, the parameter item is null. If there are any arguments, loop through all of them (concatenating the first argument each time).
  • The last executioncall "%EXECUTABLE%" start %CMD_LINE_ARGS%Execute catalina.bat with parameters if any.
  • Summary: The startup.bat file actually does one thing: start Catalina.bat.
  • Tomcat is not required to be started using startup.bat on Windowscatalina.bat startThat’s ok.

Catalina.bat source code analysis

  • In order not to make the article look too bloated, I will not post the entire file here, so I will analyze the code block by block.
  • Skip the opening comment and come to the following code snippet:
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

Copy the code
  • Most of the time we start Tomcat without setting parameters, so skip to the mainEntry code snippet, delete a temporary file, and continue.
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%"Copy the code
  • Bat. After confirming that CATALINA_HOME is in CATALINA_HOME, assign CATALINA_HOME to 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"

Copy the code
  • This code executes the setenv.bat file and setclasspath. Bat file in order to obtain the CLASSPATH. The system takes the classpath path, splashes it together with CATALINA_HOME, and eventually locates a file called bootstrap.jar. There’s a lot more to come, but it’s important to pause here: Bootstrap. jar will be the environment in which we start Tomcat.
  • Next I did some configuration from the gotTmpdir code block to the noEndorsedVar code block, which I skipped because it wasn’t the main thing.
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 doVersionCopy the code
  • Next, we can see some important information, the main points of which are:
  • set _EXECJAVA=%_RUNJAVA%To set the java.exe file path in the bin directory in the JDK.
  • set MAINCLASS=org.apache.catalina.startup.Bootstrap, set the Tomcat startup class to the Bootstrap class.
  • set ACTION=startSetting up Tomcat startup
  • Keep an eye out for these parameters, which will be used at the end of the tomcat startup.
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 doVersionCopy the code
  • Then determine if the first parameter is JPDA, and if it is, do some Settings. Normally the first argument is start, so skip this code. We will then determine the content of the first parameter, and based on that, we will skip to the doStart code section.
: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

Copy the code
  • As you can see, doStart does nothing more than set a few parameters and eventually jump to the execCmd code section
: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

Copy the code
  • As you can see, this code is also concatenating parameters, concatenating them into a variable called CMD_LINE_ARGS, and this is catalina’s last piece of code.
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 :endCopy the code
  • After skipping the previous two lines, we come to the key statement:
  • %_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 is also _RUNJAVA, which is also known as the Java directive, but was changed to _EXECJAVA in the previous doStart code blockstart "%TITLE%" %_RUNJAVA%, so the system will open another command line window, named Tomcat.
  • After a series of parameters by joining together, we will see a % % MAINCLASS, namely org. Apache. Catalina. Startup. Start the Bootstrap class, after joining together the launch parameters, finally patchwork is % % ACTION, is also a start.
  • Summary: Catalina.bat finally executes the main method in the Bootstrap class.
  • Ps: Analyzing the entire Catalina.bat we found that we could make Tomcat work differently by setting different parameters. In the IDE, you can start Tomcat in debug mode or configure parameters for it. In Catalina.bat, you can see the process behind starting Tomcat.

Analysis of Tomcat startup process

  • Bat Bootstrap Bootstrap Bootstrap Bootstrap Bootstrap Due to the lack of space, the relationship of each module in Tomcat will be left to the next article. Here is a relationship diagram of each module in Tomcat.

  • As you can see from the figure, there are quite a few modules in Tomcat. How does Tomcat start these modules? Please look at the diagram below.

  • From the figure, we can see that starting from the main method of Bootstrap class, Tomcat will call the init() method of each module step by step to initialize it. After all modules are initialized, Tomcat will call the start() method of each module step by step to start each module. Let’s take a look at this process with some source code.
  • First, let’s go to the main method of the Bootstrap class
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 of circumstance error reporting // omits system.exit (1); }}Copy the code
  • As you can see, the Bootstrap class first creates an object of its own class and then calls the init() method to initialize it. After the init() method is executed, the value of the startup parameter is determined. Since we use the default startup method, the main method takes the start parameter, which enters the following judgment code block
 else if (command.equals("start")) {
                daemon.setAwait(true);
                daemon.load(args);
                daemon.start();
                if (null == daemon.getServer()) {
                    System.exit(1);
                }

Copy the code
  • You can see that after the wait is set, the load() method of this class object is called. We followed up with the source code for the load() method.
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); }Copy the code
  • You can see that the method ends with a reflection call to the load() method of the catalinaDaemon member variable. CatalinaDaemon is a private member variable of the Bootstrap class
/**
 * Daemon reference.
 */
private Object catalinaDaemon = null;

Copy the code
  • It is initialized by reflection in the init() method of Bootstrap. Let’s go back to the source code for the init() method.
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; }Copy the code
  • You can see that the init() method creates a Catalina object and assigns it to catalinaDaemon.
  • Let’s go back to the startup flow chart that I shared with you earlier, and this is the corresponding one

  • I won’t go through the rest of the process, but you can follow this diagram to trace the code for verification. Eventually all modules can be initialized and started.

This end

  • This should give you a good idea of how Tomcat is started, but this is just the beginning of understanding Tomcat. The next article will start with a detailed description of the components in Tomcat and how they relate to each other. Once you understand the components, tomcat’s structure is no longer a mystery to you.