树木在夜晚来临前的淡金色的暮霭中闪闪发光。 —— 日坂菜乃 《文学少女》
本系列针对于 Tomcat 版本为 8.5X
文章已收录至精进Tomcat系列 系列其它文章 https://www.wormholestack.com/tag/Tomcat/
源码阅读环境:https://gitee.com/M-Analysis/source_tomcat8 已填充关键注释
承接上文:https://www.wormholestack.com/archives/641/ 《Tomcat架构的秘密》
1. 启动脚本
正常情况下启动 Tomcat 是通过运行脚本的方式
入口脚本:startup.bat 删除注释后如下所示
@echo off
setlocal
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"
if exist "%EXECUTABLE%" goto okExec
echo Cannot find "%EXECUTABLE%"
echo This file is needed to run this program
goto end
:okExec
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
以上脚本实际上只做了一件重要的事:
验证 CATALINA_HOME
变量所示目录下的 catalina.bat
文件是否存在,存在则启动 catalina.bat 批处理文件.不存在则批处理直接结束。
来看下 catalina.bat
@echo off
setlocal
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"
set RETVAL=%ERRORLEVEL%
del /Q "%TEMP%\%~nx0.Y" >NUL 2>&1
exit /B %RETVAL%
:mainEntry
del /Q "%TEMP%\%~nx0.run" >NUL 2>&1
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
if not "%CATALINA_BASE%" == "" goto gotBase
set "CATALINA_BASE=%CATALINA_HOME%"
:gotBase
if "%CATALINA_HOME%" == "%CATALINA_HOME:;=%" goto homeNoSemicolon
echo Using CATALINA_HOME: "%CATALINA_HOME%"
echo Unable to start as CATALINA_HOME contains a semicolon (;) character
goto end
:homeNoSemicolon
if "%CATALINA_BASE%" == "%CATALINA_BASE:;=%" goto baseNoSemicolon
echo Using CATALINA_BASE: "%CATALINA_BASE%"
echo Unable to start as CATALINA_BASE contains a semicolon (;) character
goto end
:baseNoSemicolon
set CLASSPATH=
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
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
if "%CLASSPATH%" == "" goto emptyClasspath
set "CLASSPATH=%CLASSPATH%;"
:emptyClasspath
set "CLASSPATH=%CLASSPATH%%CATALINA_HOME%\bin\bootstrap.jar"
if not "%CATALINA_TMPDIR%" == "" goto gotTmpdir
set "CATALINA_TMPDIR=%CATALINA_BASE%\temp"
:gotTmpdir
if not exist "%CATALINA_BASE%\bin\tomcat-juli.jar" goto juliClasspathHome
set "CLASSPATH=%CLASSPATH%;%CATALINA_BASE%\bin\tomcat-juli.jar"
goto juliClasspathDone
:juliClasspathHome
set "CLASSPATH=%CLASSPATH%;%CATALINA_HOME%\bin\tomcat-juli.jar"
:juliClasspathDone
if not "%JSSE_OPTS%" == "" goto gotJsseOpts
set "JSSE_OPTS=-Djdk.tls.ephemeralDHKeySize=2048"
:gotJsseOpts
set "JAVA_OPTS=%JAVA_OPTS% %JSSE_OPTS%"
set "JAVA_OPTS=%JAVA_OPTS% -Djava.protocol.handler.pkgs=org.apache.catalina.webresources"
if not "%LOGGING_CONFIG:~0.2%"=="-D" goto noLoggingDeprecation
if not "%CATALINA_LOGGING_CONFIG%" == "" goto noLoggingDeprecation
set "CATALINA_LOGGING_CONFIG=%LOGGING_CONFIG%"
:noLoggingDeprecation
if not "%CATALINA_LOGGING_CONFIG%" == "" goto noJuliConfig
set CATALINA_LOGGING_CONFIG=-Dnop
if not exist "%CATALINA_BASE%\conf\logging.properties" goto noJuliConfig
set CATALINA_LOGGING_CONFIG=-Djava.util.logging.config.file="%CATALINA_BASE%\conf\logging.properties"
:noJuliConfig
if not "%LOGGING_MANAGER%" == "" goto noJuliManager
set LOGGING_MANAGER=-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
:noJuliManager
set "JDK_JAVA_OPTIONS=%JDK_JAVA_OPTIONS% --add-opens=java.base/java.lang=ALL-UNNAMED"
set "JDK_JAVA_OPTIONS=%JDK_JAVA_OPTIONS% --add-opens=java.base/java.io=ALL-UNNAMED"
set "JDK_JAVA_OPTIONS=%JDK_JAVA_OPTIONS% --add-opens=java.base/java.util=ALL-UNNAMED"
set "JDK_JAVA_OPTIONS=%JDK_JAVA_OPTIONS% --add-opens=java.base/java.util.concurrent=ALL-UNNAMED"
set "JDK_JAVA_OPTIONS=%JDK_JAVA_OPTIONS% --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED"
set ENDORSED_PROP=ignore.endorsed.dirs
if "%JAVA_ENDORSED_DIRS%" == "" goto noEndorsedVar
set ENDORSED_PROP=java.endorsed.dirs
goto doneEndorsed
:noEndorsedVar
if not exist "%CATALINA_HOME%\endorsed" goto doneEndorsed
set ENDORSED_PROP=java.endorsed.dirs
:doneEndorsed
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%"
echo Using CATALINA_OPTS: "%CATALINA_OPTS%"
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=localhost: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
echo Usage: catalina ( commands ... )
echo commands:
echo debug Start Catalina in a debugger
echo debug -security Debug Catalina with a security manager
echo jpda start Start Catalina under JPDA debugger
echo run Start Catalina in the current window
echo run -security Start in the current window with security manager
echo start Start Catalina in a separate window
echo start -security Start in a separate window with security manager
echo stop Stop Catalina
echo configtest Run a basic syntax check on server.xml
echo version What version of tomcat are you running?
goto end
:doDebug
shift
set _EXECJAVA=%_RUNJDB%
set DEBUG_OPTS=-sourcepath "%CATALINA_HOME%\..\..\java"
if not ""%1"" == ""-security"" goto execCmd
shift
echo Using Security Manager
set "SECURITY_POLICY_FILE=%CATALINA_BASE%\conf\catalina.policy"
goto execCmd
:doRun
shift
if not ""%1"" == ""-security"" goto execCmd
shift
echo Using Security Manager
set "SECURITY_POLICY_FILE=%CATALINA_BASE%\conf\catalina.policy"
goto 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
:doStop
shift
set ACTION=stop
set CATALINA_OPTS=
goto execCmd
:doConfigTest
shift
set ACTION=configtest
set CATALINA_OPTS=
goto execCmd
:doVersion
%_EXECJAVA% -classpath "%CATALINA_HOME%\lib\catalina.jar" org.apache.catalina.util.ServerInfo
goto end
:execCmd
set CMD_LINE_ARGS=
:setArgs
if ""%1""=="""" goto doneSetArgs
set CMD_LINE_ARGS=%CMD_LINE_ARGS% %1
shift
goto setArgs
:doneSetArgs
if not "%JPDA%" == "" goto doJpda
if not "%SECURITY_POLICY_FILE%" == "" goto doSecurity
%_EXECJAVA% %CATALINA_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% %CATALINA_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% %CATALINA_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% %CATALINA_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
以上脚本实际上只做了一件重要的时,即执行 Bootstrap 类的 main 方法,只不过调用 main 方法会加入各种入参而已。
感兴趣的朋友可以浏览下这些脚本,了解 dos 命令阅读起来会比较容易。
2. 启动前准备工作
上文分析了执行脚本,从 startup.bat 启动 Tomcat 的最终会调用 org.apache.catalina.startup.Bootstrap 的 main 方法,并传过来的最后一个命令行参数是 start。
/**
* Main method and entry point when starting Tomcat via the provided
* scripts.
*
* @param args Command line arguments to be processed
*/
public static void main(String args[]) {
synchronized (daemonLock) {
// main方法第一次执行的时候,daemon肯定为null,所以直接new了一个Bootstrap对象,然后执行其init()方法
// daemon 就是 bootstrap
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
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);
}
}
// 执行守护对象的load方法和start方法
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")) {
// bootstrap 和 Catalina 一脉相连. 这里设置. 方法内部设置 Catalina 实例setAwait方法
daemon.setAwait(true);
// args 为 空.方法内部调用 Catalina 的 load 方法.
daemon.load(args);
// 相同. 反射调用 Catalina 的 start 方法 .至此.启动结束
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
if (t instanceof InvocationTargetException &&
t.getCause() != null) {
t = t.getCause();
}
handleThrowable(t);
t.printStackTrace();
System.exit(1);
}
}
daemon 是 Bootstrap 类中的一个静态成员变量,类型是 Bootstrap.初始化时是 null,所以对 daemon 进行实例化后调用 init 方法。
init 代码如下:
/**
* Initialize daemon.
*
* @throws Exception Fatal initialization error
*/
public void init() throws Exception {
// 初始化类加载器 Tomcat独有的类加载器,违背双亲委派模型机制
initClassLoaders();
// 设置上下文类加载器为catalinaLoader,这个类加载器负责加载Tomcat专用的类
Thread.currentThread().setContextClassLoader(catalinaLoader);
// 线程安全的加载class 负责加载Tomcat容器所需的class,检查是否安全
SecurityClassLoad.securityClassLoad(catalinaLoader);
// Load our startup class and call its process() method
if (log.isDebugEnabled()) {
log.debug("Loading startup class");
}
// 使用catalinaLoader 反射方法实例化Catalina
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
// 反射获取实例
Object startupInstance = startupClass.getConstructor().newInstance();
// Set the shared extensions class loader
if (log.isDebugEnabled()) {
log.debug("Setting startup class properties");
}
// 设置Catalina类的parentClassLoader属性为sharedLoader
String methodName = "setParentClassLoader";
Class<?> paramTypes[] = new Class[1];
// 创建一个抽象类加载器对象
paramTypes[0] = Class.forName("java.lang.ClassLoader");
// 创建一个对象数组
Object paramValues[] = new Object[1];
// 对象数组中放入分享类加载器
paramValues[0] = sharedLoader;
// 从Catalina类获取setParentClassLoader方法对象
Method method =
startupInstance.getClass().getMethod(methodName. paramTypes);
// 调用setParentClassLoader方法传入sharedLoader Catalina.parentClassLoader = sharedLoader
method.invoke(startupInstance. paramValues);
// 引用Catalina实例 catalinaDaemon = new Catalina()
catalinaDaemon = startupInstance;
}
简单说 init 主要做了以下几件事:
- 初始化类加载器(commonLoader、catalinaLoader、sharedLoader)
- 利用 catalinaLoader 反射方法实例化 org.apache.catalina.startup.Catalina 对象
- 赋值给静态成员 catalinaDaemon,以 sharedLoader 作为入参通过反射调用该对象的 setParentClassLoader 方法
回到 main 方法中,如果命令是启动,会执行 Bootstrap 的 load 方法和 start方法,先来看 load
daemon.load(args);
同样也是通过反射调用 Catalina 的 load 方法
我们都知道 Tomcat 里的 Server 由 Catalina 来管理,Catalina 是整个 Tomcat 的管理类,它里面的三个方法 load,start,stop 分别用来管理整个服务器的生命周期。
/**
* Load daemon.
*/
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);
}
// 通过反射调用Catalina的load()方法
method.invoke(catalinaDaemon. param);
}
走进 Catalina 的 load 方法,探探究竟
/*
* Load using arguments
*/
public void load(String args[]) {
try {
// 处理命令行的参数
if (arguments(args)) {
load();
}
} catch (Exception e) {
e.printStackTrace(System.out);
}
}
重载方法调用 load()
/**
* Start a new server instance.
*/
public void load() {
// 如果已经加载则退出
if (loaded) {
return;
}
loaded = true;
long t1 = System.nanoTime();
// (已经弃用)
initDirs();
// Before digester - it may be needed
// 初始化jmx的环境变量
initNaming();
// Create and execute our Digester
// 定义解析server.xml的配置,告诉Digester哪个xml标签应该解析成什么类
Digester digester = createStartDigester();
// 解析 server.xml
// 首先尝试加载conf/server.xml
// 如果不存在conf/server.xml,则加载server-embed.xml(该xml在catalina.jar中)
// 如果还是加载不到xml,则直接return
InputSource inputSource = null;
InputStream inputStream = null;
File file = null;
try {
try {
file = configFile();
inputStream = new FileInputStream(file);
inputSource = new InputSource(file.toURI().toURL().toString());
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("catalina.configFail". file). e);
}
}
if (inputStream == null) {
try {
inputStream = getClass().getClassLoader()
.getResourceAsStream(getConfigFile());
inputSource = new InputSource
(getClass().getClassLoader()
.getResource(getConfigFile()).toString());
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("catalina.configFail".
getConfigFile()). e);
}
}
}
// This should be included in catalina.jar
// Alternative: don't bother with xml. just create it manually.
if (inputStream == null) {
try {
inputStream = getClass().getClassLoader()
.getResourceAsStream("server-embed.xml");
inputSource = new InputSource
(getClass().getClassLoader()
.getResource("server-embed.xml").toString());
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("catalina.configFail".
"server-embed.xml"). e);
}
}
}
if (inputStream == null || inputSource == null) {
if (file == null) {
log.warn(sm.getString("catalina.configFail".
getConfigFile() + "] or [server-embed.xml]"));
} else {
log.warn(sm.getString("catalina.configFail".
file.getAbsolutePath()));
if (file.exists() && !file.canRead()) {
log.warn("Permissions incorrect. read permission is not allowed on the file.");
}
}
return;
}
try {
inputSource.setByteStream(inputStream);
// 把Catalina作为一个顶级实例
digester.push(this);
// 解析过程会实例化各个组件,比如Server、Container、Connector等
digester.parse(inputSource);
} catch (SAXParseException spe) {
log.warn("Catalina.start using " + getConfigFile() + ": " +
spe.getMessage());
return;
} catch (Exception e) {
log.warn("Catalina.start using " + getConfigFile() + ": ". e);
return;
}
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
// Ignore
}
}
}
// 给Server设置catalina信息
getServer().setCatalina(this);
getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
// Stream redirection
initStreams();
// Start the new server
try {
// 调用Lifecycle的init阶段 启动Server
getServer().init();
} catch (LifecycleException e) {
if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
throw new java.lang.Error(e);
} else {
log.error("Catalina.start". e);
}
}
long t2 = System.nanoTime();
if (log.isInfoEnabled()) {
log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms");
}
}
实际上述代码完成了几件事.
- 加载
server.xml
并创建 Digester 对象.将当前对象压入 Digester 里的对象栈顶.根据 inputSource 里设置的文件 xml 路径及所创建的 Digester 对象所包含的解析规则生成相应对象.例如核心组件,监听器等.
Digester digester = createStartDigester();
inputSource.setByteStream(inputStream);
digester.push(this);
digester.parse(inputSource);
经过对 xml 文件的解析将会产生
StandardServer
StandardService
Connector
StandardEngine
StandardHost
StandardContext等对象都是继承至ContainerBase
这些对象也存在着对象引用的关系(一对一或多对一),例如一个 StandardServer 可以包含多个 StandardService,一个 StandardService 只有一个 StandardEngine,但是可以有多个 Connector。
如下图所示:
当存在这个关系之后,容器的初始化和启动流程是由 StandardEngine 启动 StandardHost,再由StandardHost 启动 StandardContext,最后由 StandardContext 启动 StandardWrapper。
- 填充 Server 例如将当前 Catalina 设置到 Server 属性,调用 Server 对象的 init 方法,启动Server
getServer().setCatalina(this);
getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
getServer().init();
Server 的 init 方法实际上托管给了 Tomcat 的生命周期 Lifecycle 机制.
本系列第一篇《Tomcat架构的秘密》简单描述了下 Lifecycle 机制.
下面将进行详细说明.
3. Lifecycle生命周期机制
Tomcat 组件生命周期机制非常规范,统一接口管理,它就是 Lifecycle。
实际上它就是一个状态机,它定义了管理观察者的方法和它要所做的其它方法.利用了观察者模式和模版方法实现对组件的由初始化到销毁状态的管理。
几乎所有的组件类都继承了 LifecycleBase 类,例如 StandardEngine,StandardHost,StandardContext,StandardWrapper。
public interface Lifecycle {
// 添加监听器
public void addLifecycleListener(LifecycleListener listener);
// 获取所以监听器
public LifecycleListener[] findLifecycleListeners();
// 移除某个监听器
public void removeLifecycleListener(LifecycleListener listener);
// 初始化方法
public void init() throws LifecycleException;
// 启动方法
public void start() throws LifecycleException;
// 停止方法
public void stop() throws LifecycleException;
// 销毁方法
public void destroy() throws LifecycleException;
// 获取生命周期状态
public LifecycleState getState();
// 获取字符串类型的生命周期状态
public String getStateName();
}
LifecycleBase 是 Lifecycle 的基本实现.它提供了监听器查找,增加,删除的实现,并提供了一些模版方法的实现
/**
* The list of registered LifecycleListeners for event notifications.
* 监听器相关 线程安全的List
*/
private final List<LifecycleListener> lifecycleListeners = new CopyOnWriteArrayList<>();
/**
* 给该组件添加一个监听器
*/
@Override
public void addLifecycleListener(LifecycleListener listener) {
lifecycleListeners.add(listener);
}
/**
* 以数组的形式返回该组件所有的监听器
*/
@Override
public LifecycleListener[] findLifecycleListeners() {
// 生成新数组保证线程安全
return lifecycleListeners.toArray(new LifecycleListener[0]);
}
/**
* 删除一个监听器
*/
@Override
public void removeLifecycleListener(LifecycleListener listener) {
lifecycleListeners.remove(listener);
}
/**
* 生命周期事件发布
*/
protected void fireLifecycleEvent(String type. Object data) {
LifecycleEvent event = new LifecycleEvent(this. type. data);
for (LifecycleListener listener : lifecycleListeners) {
listener.lifecycleEvent(event);
}
}
LifecycleListener 接口定义如下
public interface LifecycleListener {
/**
* Acknowledge the occurrence of the specified event.
*
* @param event LifecycleEvent that has occurred
*/
public void lifecycleEvent(LifecycleEvent event);
}
LifecycleListener 可以理解成事抽象观察者,实现该接口的监听器则是具体的观察者.lifecycleEvent(LifecycleEvent event)
方法功能是当某个 LifecycleEvent(事件)产生时通知当前监听器的实现类,而针对该事件具体如何处理由监听器实现类也就是观察者自己决定。
LifecycleEvent 定义如下
ublic final class LifecycleEvent extends EventObject {
private static final long serialVersionUID = 1L;
/**
* Construct a new LifecycleEvent with the specified parameters.
*
* @param lifecycle Component on which this event occurred
* @param type Event type (required)
* @param data Event data (if any)
*/
public LifecycleEvent(Lifecycle lifecycle. String type. Object data) {
super(lifecycle);
this.type = type;
this.data = data;
}
/**
* The event data associated with this event.
*/
private final Object data;
/**
* The event type this instance represents.
*/
private final String type;
/**
* @return the event data of this event.
*/
public Object getData() {
return data;
}
/**
* 获取生命周期
*/
public Lifecycle getLifecycle() {
return (Lifecycle) getSource();
}
/**
* @return the event type of this event.
*/
public String getType() {
return this.type;
}
}
LifecycleEvent 继承了 jdk 内置的 java.util.EventObject
来支撑事件定义,它代表了一个事件.
事件的发布利用推模式,当每发布一个事件都会通知主题的所有具体观察者,由各观察者再来决定是否需要对该事件进行后续处理。
LifecycleState 是对 Lifecycle 类中定义好的字符串常量做了另外一层封装.如下所示,会发现和Lifecycle 中定义的常量一致。
public enum LifecycleState {
NEW(false. null).
INITIALIZING(false. Lifecycle.BEFORE_INIT_EVENT).
INITIALIZED(false. Lifecycle.AFTER_INIT_EVENT).
STARTING_PREP(false. Lifecycle.BEFORE_START_EVENT).
STARTING(true. Lifecycle.START_EVENT).
STARTED(true. Lifecycle.AFTER_START_EVENT).
STOPPING_PREP(true. Lifecycle.BEFORE_STOP_EVENT).
STOPPING(false. Lifecycle.STOP_EVENT).
STOPPED(false. Lifecycle.AFTER_STOP_EVENT).
DESTROYING(false. Lifecycle.BEFORE_DESTROY_EVENT).
DESTROYED(false. Lifecycle.AFTER_DESTROY_EVENT).
FAILED(false. null);
private final boolean available;
private final String lifecycleEvent;
private LifecycleState(boolean available. String lifecycleEvent) {
this.available = available;
this.lifecycleEvent = lifecycleEvent;
}
/**
* May the public methods other than property getters/setters and lifecycle
* methods be called for a component in this state? It returns
* <code>true</code> for any component in any of the following states:
* <ul>
* <li>{@link #STARTING}</li>
* <li>{@link #STARTED}</li>
* <li>{@link #STOPPING_PREP}</li>
* </ul>
*
* @return <code>true</code> if the component is available for use.
* otherwise <code>false</code>
*/
public boolean isAvailable() {
return available;
}
public String getLifecycleEvent() {
return lifecycleEvent;
}
}
4. 各组件初始化流程
4.1 Server初始化
上文启动流程提到 Server 的启动
getServer().init();
真正执行 Server 的 init 方法的实际上是 LifecycleBase 类
@Override
public final synchronized void init() throws LifecycleException {
// 非NEW状态,不允许调用init()方法
if (!state.equals(LifecycleState.NEW)) {
invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
}
try {
// 初始化逻辑之前,由LifecycleBase统一发出INITIALIZING事件
setStateInternal(LifecycleState.INITIALIZING. null. false);
// 抽象模版方法,初始化,需要组件自行实现
/**
* 采用模板方法模式来对所有支持生命周期管理的组件的生命周期各个阶段进行了总体管理,
* 每个需要生命周期管理的组件只需要继承这个基类,
* 然后覆盖对应的钩子方法即可完成相应的声明周期阶段的管理工作
*/
initInternal();
// 由LifecycleBase统一发出INITIALIZED事件
setStateInternal(LifecycleState.INITIALIZED. null. false);
} catch (Throwable t) {
// 初始化的过程中,可能会有异常抛出,这时需要捕获异常,并将状态变更为`FAILED`
handleSubClassException(t. "lifecycleBase.initFail". toString());
}
}
setState(LifecycleState.STARTING)
它调用了父类 org.apache.catalina.util.LifecycleBase
中的 setState 方法:
protected synchronized void setState(LifecycleState state)
throws LifecycleException {
setStateInternal(state. null. true);
}
这个类里面调用本类的一个同步方法 setStateInternal :
private synchronized void setStateInternal(LifecycleState state. Object data. boolean check)
throws LifecycleException {
if (log.isDebugEnabled()) {
log.debug(sm.getString("lifecycleBase.setState". this. state));
}
if (check) {
// Must have been triggered by one of the abstract methods (assume
// code in this class is correct)
// null is never a valid state
if (state == null) {
invalidTransition("null");
// Unreachable code - here to stop eclipse complaining about
// a possible NPE further down the method
return;
}
// Any method can transition to failed
// startInternal() permits STARTING_PREP to STARTING
// stopInternal() permits STOPPING_PREP to STOPPING and FAILED to
// STOPPING
if (!(state == LifecycleState.FAILED ||
(this.state == LifecycleState.STARTING_PREP &&
state == LifecycleState.STARTING) ||
(this.state == LifecycleState.STOPPING_PREP &&
state == LifecycleState.STOPPING) ||
(this.state == LifecycleState.FAILED &&
state == LifecycleState.STOPPING))) {
// No other transition permitted
invalidTransition(state.name());
}
}
this.state = state;
String lifecycleEvent = state.getLifecycleEvent();
// 如果该事件非空,则调用 fireLifecycleEvent 方法发布该事件
if (lifecycleEvent != null) {
fireLifecycleEvent(lifecycleEvent. data);
}
}
fireLifecycleEvent 调用了本类的方法来发布该事件
/**
* Allow sub classes to fire {@link Lifecycle} events.
*
* @param type Event type
* @param data Data associated with event.
*/
protected void fireLifecycleEvent(String type. Object data) {
LifecycleEvent event = new LifecycleEvent(this. type. data);
for (LifecycleListener listener : lifecycleListeners) {
listener.lifecycleEvent(event);
}
}
接着由 LifecycleBase 调用 模板方法 initInternal(),由子类具体实现。
StandardServer#initInternal()
执行代码如下:
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
// Register global String cache
// Note although the cache is global. if there are multiple Servers
// present in the JVM (may happen when embedding) then the same cache
// will be registered under multiple names
onameStringCache = register(new StringCache(). "type=StringCache");
// Register the MBeanFactory
MBeanFactory factory = new MBeanFactory();
factory.setContainer(this);
onameMBeanFactory = register(factory. "type=MBeanFactory");
// Register the naming resources
globalNamingResources.init();
// Populate the extension validator with JARs from common and shared
// class loaders
if (getCatalina() != null) {
ClassLoader cl = getCatalina().getParentClassLoader();
// Walk the class loader hierarchy. Stop at the system class loader.
// This will add the shared (if present) and common class loaders
while (cl != null && cl != ClassLoader.getSystemClassLoader()) {
if (cl instanceof URLClassLoader) {
URL[] urls = ((URLClassLoader) cl).getURLs();
for (URL url : urls) {
if (url.getProtocol().equals("file")) {
try {
File f = new File (url.toURI());
if (f.isFile() &&
f.getName().endsWith(".jar")) {
ExtensionValidator.addSystemResource(f);
}
} catch (URISyntaxException | IOException e) {
// Ignore
}
}
}
}
cl = cl.getParent();
}
}
// Initialize our defined Services
// 初始化所有的service组件
for (Service service : services) {
service.init();
}
}
关键代码实际上完成了循环调用 Server 类里内置的 Service 数组的 init 方法,让 Service 组件完成初始化.这也能变相的说明在同一个 Server 下面,可能存在多个 Service 组件。
4.2 Service初始化
service.init() 同样经过 LifecycleBase 的 init 方法,由 LifecycleBase 管理组件状态,调用 Service 标准实现类 StandardService 的 initInternal 方法完成 Service 的初始化(由于核心组件都是同样的处理方式,下文会省略此段说明。
StandardService#initInternal()
代码如下:
@Override
protected void initInternal() throws LifecycleException {
// 往jmx中注册StandardService
super.initInternal();
if (engine != null) {
// engine组件初始化
engine.init();
}
// Initialize any Executors
// 存在Executor线程池,则进行初始化,默认是没有的
for (Executor executor : findExecutors()) {
if (executor instanceof JmxEnabled) {
((JmxEnabled) executor).setDomain(getDomain());
}
// executor组件初始化
executor.init();
}
// Initialize mapper listener
mapperListener.init();
// Initialize our defined Connectors
synchronized (connectorsLock) {
for (Connector connector : connectors) {
try {
// connector组件初始化,而Connector又会对ProtocolHandler进行初始化,开启应用端口的监听.
connector.init();
} catch (Exception e) {
String message = sm.getString(
"standardService.connector.initFailed". connector);
log.error(message. e);
if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
throw new LifecycleException(message);
}
}
}
}
}
- 向 JMX 中注册 StandardService。
- 初始化 Engine,而 Engine 初始化过程中会去初始化 Realm (权限相关的组件)。
- 初始化 Connector 连接器,默认有 Http1.1、AJP 连接器,而 Connector 初始化过程,又会对 ProtocolHandler 进行初始化,开启应用端口的监听,后面会详细分析。
- 如果存在 Executor 线程池,还会进行 init 操作,这个 Excecutor 是 Tomcat 的接口,继承至java.util.concurrent.Executor、org.apache.catalina.Lifecycle。
下面我们先来看 Engine 的初始化
4.3 Engine初始化
Engine 是整个容器的最顶层容器,StandardEngine 继承至 ContainerBase。
而 StandardEngine 为 Engine 的标准实现
构造函数如下:
public StandardEngine() {
super();
// 使用默认的基础阀门创建标准Engine组件
pipeline.setBasic(new StandardEngineValve());
/* Set the jmvRoute using the system property jvmRoute */
try {
setJvmRoute(System.getProperty("jvmRoute"));
} catch (Exception ex) {
log.warn(sm.getString("standardEngine.jvmRouteFail"));
}
// By default, the engine will hold the reloading thread
backgroundProcessorDelay = 10;
}
StandardEngine#initInternal()
代码如下:
@Override
protected void initInternal() throws LifecycleException {
// Ensure that a Realm is present before any attempt is made to start
// one. This will create the default NullRealm if necessary.
getRealm();
// 初始化start、stop线程池
super.initInternal();
}
super.initInternal() 初始化了一个名为”启动停止“的线程池
@Override
protected void initInternal() throws LifecycleException {
BlockingQueue<Runnable> startStopQueue = new LinkedBlockingQueue<>();
startStopExecutor = new ThreadPoolExecutor(
getStartStopThreadsInternal().
getStartStopThreadsInternal(). 10. TimeUnit.SECONDS.
startStopQueue.
new StartStopThreadFactory(getName() + "-startStop-"));
// 允许core线程超时未获取任务时退出
startStopExecutor.allowCoreThreadTimeOut(true);
super.initInternal();
}
线程池设置属性如下:
- 核心线程数和最大线程数是相等的,默认为1。
- 允许核心线程在超时未获取到任务时退出线程。
- 线程获取任务的超时时间是10s,也就是说所有的线程(包括核心线程),超过10s未获取到任务,那么这个线程就会被销毁。
- 只需要在容器启动(start)和停止(stop)的时候发挥作用,没必要时时刻刻处理任务队列。
StartStopExecutor 线程池作用如下
- 在 Tomcat 启动(start)的时候,如果发现有子容器,则会把子容器的启动(start)操作放在线程池中进行处理。
- 在 Tomcat 销毁(stop)的时候,也会把销毁(stop)操作放在线程池中处理。
4.4 Connector初始化
// Initialize our defined Connectors
synchronized (connectorsLock) {
for (Connector connector : connectors) {
try {
// connector组件初始化,而Connector又会对ProtocolHandler进行初始化,开启应用端口的监听.
connector.init();
} catch (Exception e) {
String message = sm.getString(
"standardService.connector.initFailed". connector);
log.error(message. e);
if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
throw new LifecycleException(message);
}
}
}
}
上文提到,Service 初始化时会触发 Connector 的初始化。
Connector#initInternal()
代码如下:
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
// Initialize adapter
// 初始化adapter
adapter = new CoyoteAdapter(this);
protocolHandler.setAdapter(adapter);
// Make sure parseBodyMethodsSet has a default
if (null == parseBodyMethodsSet) {
// 设置接受body的method列表,默认为POST
setParseBodyMethods(getParseBodyMethods());
}
if (protocolHandler.isAprRequired() && !AprLifecycleListener.isInstanceCreated()) {
throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoAprListener".
getProtocolHandlerClassName()));
}
if (protocolHandler.isAprRequired() && !AprLifecycleListener.isAprAvailable()) {
throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoAprLibrary".
getProtocolHandlerClassName()));
}
if (AprLifecycleListener.isAprAvailable() && AprLifecycleListener.getUseOpenSSL() &&
protocolHandler instanceof AbstractHttp11JsseProtocol) {
AbstractHttp11JsseProtocol<?> jsseProtocolHandler =
(AbstractHttp11JsseProtocol<?>) protocolHandler;
if (jsseProtocolHandler.isSSLEnabled() &&
jsseProtocolHandler.getSslImplementationName() == null) {
// OpenSSL is compatible with the JSSE configuration. so use it if APR is available
jsseProtocolHandler.setSslImplementationName(OpenSSLImplementation.class.getName());
}
}
try {
// protocolHandler组件初始化
protocolHandler.init();
} catch (Exception e) {
throw new LifecycleException(
sm.getString("coyoteConnector.protocolHandlerInitializationFailed"). e);
}
}
ProtocolHandler 为协议处理器,策略模式般的存在为应对多种协议的请求做适配。
实例化 CoyoteAdapter 适配器,为 ProtocolHandler 设置适配器,执行 ProtocolHandler 的初始化。
可以发现 Coyote 提供了 ProtocolHandler 的多种实现
这里我们以默认的协议 AbstractProtocol 为例
@Override
public void init() throws Exception {
if (getLog().isInfoEnabled()) {
getLog().info(sm.getString("abstractProtocolHandler.init", getName()));
logPortOffset();
}
if (oname == null) {
// Component not pre-registered so register it
oname = createObjectName();
if (oname != null) {
Registry.getRegistry(null, null).registerComponent(this, oname, null);
}
}
if (this.domain != null) {
ObjectName rgOname = new ObjectName(domain + ":type=GlobalRequestProcessor,name=" + getName());
this.rgOname = rgOname;
Registry.getRegistry(null, null).registerComponent(
getHandler().getGlobal(), rgOname, null);
}
String endpointName = getName();
// 设置endpoint的名字,默认为:http-nio-{port}
endpoint.setName(endpointName.substring(1, endpointName.length() - 1));
endpoint.setDomain(domain);
// endpoint 组件的初始化
endpoint.init();
}
Endpoint 作为 Coyote 通信断点,通信监听的端口,是具体的 Socket 接受处理类,是对传输层的抽象,Tomcat 并没有 Endpoint 接口,而是提供了这个抽象类,AbstractEndpoint 根据 I/O 方式不同,提供了 NioEndpoint(NIO) ,AprEndpoint(APR),Nio2Endpoint(NIO2) 3 个实现。
endpoint.init 端口初始化由 AbstractEndpoint 默认实现
public void init() throws Exception {
// 执行bind()方法
if (bindOnInit) {
bindWithCleanup();
bindState = BindState.BOUND_ON_INIT;
}
if (this.domain != null) {
// Register endpoint (as ThreadPool - historical name)
oname = new ObjectName(domain + ":type=ThreadPool,name=\"" + getName() + "\"");
Registry.getRegistry(null, null).registerComponent(this, oname, null);
ObjectName socketPropertiesOname = new ObjectName(domain + ":type=SocketProperties,name=\"" + getName() + "\"");
socketProperties.setObjectName(socketPropertiesOname);
Registry.getRegistry(null, null).registerComponent(socketProperties, socketPropertiesOname, null);
for (SSLHostConfig sslHostConfig : findSslHostConfigs()) {
registerJmx(sslHostConfig);
}
}
}
bindWithCleanup() 方法执行了 bind 方法,bind 方法根据协议有三种实现。
以 NIO 为例
@Override
public void bind() throws Exception {
initServerSocket();
setStopLatch(new CountDownLatch(1));
// Initialize SSL if needed
initialiseSsl();
}
bind 实际干了这么几件事
5. 各组件启动流程
上文我们了解了各组件的初始化,下面步入各组件的启动流程。
回到 Bootstrap 的 main 方法,daemon.start()。
public void start() throws Exception {
if (catalinaDaemon == null) {
init();
}
Method method = catalinaDaemon.getClass().getMethod("start", (Class[]) null);
method.invoke(catalinaDaemon, (Object[]) null);
}
start 实际上就是反射调用 Catalina 的 start 方法
public void start() {
// getServer为空则重新调用load
if (getServer() == null) {
load();
}
if (getServer() == null) {
log.fatal("Cannot start server. Server instance is not configured.");
return;
}
long t1 = System.nanoTime();
// Start the new server
try {
// 调用Server的start方法,启动Server组件
getServer().start();
} catch (LifecycleException e) {
log.fatal(sm.getString("catalina.serverStartFail"), e);
try {
getServer().destroy();
} catch (LifecycleException e1) {
log.debug("destroy() failed for failed Server ", e1);
}
return;
}
long t2 = System.nanoTime();
if (log.isInfoEnabled()) {
log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
}
// Register shutdown hook
if (useShutdownHook) {
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
// 注册勾子,用于安全关闭tomcat
Runtime.getRuntime().addShutdownHook(shutdownHook);
// If JULI is being used, disable JULI's shutdown hook since
// shutdown hooks run in parallel and log messages may be lost
// if JULI's hook completes before the CatalinaShutdownHook()
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).setUseShutdownHook(
false);
}
}
// Bootstrap中会设置await为true,其目的在于让tomcat在shutdown端口阻塞监听关闭命令
if (await) {
// 作用就是在tomcat 启动完成之后,8005端口处于一种等待请求状态接受数据
await();
stop();
}
}
直接来看 getServer().start()
是不是似曾相识,前面我们分析过 getServer().init()
。
同理 LifecycleBase 也实现了 start 的模版方法。
如下代码所示。
@Override
public final synchronized void start() throws LifecycleException {
if (LifecycleState.STARTING_PREP.equals(state) || LifecycleState.STARTING.equals(state) ||
LifecycleState.STARTED.equals(state)) {
if (log.isDebugEnabled()) {
Exception e = new LifecycleException();
log.debug(sm.getString("lifecycleBase.alreadyStarted", toString()), e);
} else if (log.isInfoEnabled()) {
log.info(sm.getString("lifecycleBase.alreadyStarted", toString()));
}
return;
}
if (state.equals(LifecycleState.NEW)) {
init();
} else if (state.equals(LifecycleState.FAILED)) {
stop();
} else if (!state.equals(LifecycleState.INITIALIZED) &&
!state.equals(LifecycleState.STOPPED)) {
invalidTransition(Lifecycle.BEFORE_START_EVENT);
}
try {
setStateInternal(LifecycleState.STARTING_PREP, null, false);
// 抽象模版方法,启动,需要组件自行实现
/**
* 采用模板方法模式来对所有支持生命周期管理的组件的生命周期各个阶段进行了总体管理,
* 每个需要生命周期管理的组件只需要继承这个基类,
* 然后覆盖对应的钩子方法即可完成相应的声明周期阶段的管理工作
*/
startInternal();
if (state.equals(LifecycleState.FAILED)) {
// This is a 'controlled' failure. The component put itself into the
// FAILED state so call stop() to complete the clean-up.
stop();
} else if (!state.equals(LifecycleState.STARTING)) {
// Shouldn't be necessary but acts as a check that sub-classes are
// doing what they are supposed to.
invalidTransition(Lifecycle.AFTER_START_EVENT);
} else {
setStateInternal(LifecycleState.STARTED, null, false);
}
} catch (Throwable t) {
// This is an 'uncontrolled' failure so put the component into the
// FAILED state and throw an exception.
handleSubClassException(t, "lifecycleBase.startFail", toString());
}
}
5.1 Server启动
StandardServer 重写了 startInternal 方法,完成自己的逻辑。
@Override
protected void startInternal() throws LifecycleException {
// 发布configure_start启动事件
fireLifecycleEvent(CONFIGURE_START_EVENT, null);
setState(LifecycleState.STARTING);
globalNamingResources.start();
// Start our defined Services
synchronized (servicesLock) {
for (Service service : services) {
// Service的启动
service.start();
}
}
}
上述代码完成了以下功能:
- 由 LifecycleBase 统一发布 configure_start 启动事件,通知 LifecycleListener 在启动前做一些准备工作。
- NamingContextListener 会处理 configure_start事件,实例化 Tomcat 相关的上下文,以及ContextResource 资源。
- 启动Service组件。
5.2 Service启动
StandardService 的 start 代码如下所示:
@Override
protected void startInternal() throws LifecycleException {
if (log.isInfoEnabled()) {
log.info(sm.getString("standardService.start.name", this.name));
}
setState(LifecycleState.STARTING);
// Start our defined Container first
if (engine != null) {
synchronized (engine) {
// 启动Engine
engine.start();
}
}
synchronized (executors) {
for (Executor executor : executors) {
// 启动Executor线程池
executor.start();
}
}
// 启动MapperListener
mapperListener.start();
// Start our defined Connectors second
synchronized (connectorsLock) {
for (Connector connector : connectors) {
try {
// If it has already failed, don't try and start it
if (connector.getState() != LifecycleState.FAILED) {
// 启动Connector
connector.start();
}
} catch (Exception e) {
log.error(sm.getString(
"standardService.connector.startFailed",
connector), e);
}
}
}
}
和 Service 初始化流程类似,它完成了以下流程。
- 启动 Engine 组件
- 启动 Executor 线程池,这个 Excecutor 是 Tomcat 的接口,继承至java.util.concurrent.Executor、org.apache.catalina.Lifecycle。
- 启动 MapperListener
- 启动 Connector 组件
5.3 Engine启动
我们来看下 StandardEngine.startInternal
@Override
protected synchronized void startInternal() throws LifecycleException {
// Log our server identification information
if (log.isInfoEnabled()) {
log.info(sm.getString("standardEngine.start", ServerInfo.getServerInfo()));
}
// Standard container startup
super.startInternal();
}
super.startInternal() 调用父类 ContainerBase 的 startInternal()方法
主要分为3个步骤:
- 线程池依次启动子容器
- 启动 Pipeline,并且发出 STARTING 事件
- 如果 backgroundProcessorDelay 参数 >= 0,则开启 ContainerBackgroundProcessor 线程,用于调用子容器的 backgroundProcess
@Override
protected synchronized void startInternal() throws LifecycleException {
// Start our subordinate components, if any
logger = null;
getLogger();
Cluster cluster = getClusterInternal();
if (cluster instanceof Lifecycle) {
((Lifecycle) cluster).start();
}
Realm realm = getRealmInternal();
if (realm instanceof Lifecycle) {
((Lifecycle) realm).start();
}
Container children[] = findChildren();
// 把子容器的启动步骤放在线程中处理,默认情况下线程池只有一个线程处理任务队列
List<Future<Void>> results = new ArrayList<>();
for (Container child : children) {
results.add(startStopExecutor.submit(new StartChild(child)));
}
MultiThrowable multiThrowable = null;
// 阻塞当前线程,直到子容器start完成
for (Future<Void> result : results) {
try {
result.get();
} catch (Throwable e) {
log.error(sm.getString("containerBase.threadedStartFailed"), e);
if (multiThrowable == null) {
multiThrowable = new MultiThrowable();
}
multiThrowable.add(e);
}
}
if (multiThrowable != null) {
throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"),
multiThrowable.getThrowable());
}
// Start the Valves in our pipeline (including the basic), if any
// 启动Pipeline
if (pipeline instanceof Lifecycle) {
// 默认使用 StandardPipeline 实现类,它也是一个Lifecycle。在容器启动的时候,StandardPipeline 会遍历 Valve 链表,如果 Valve 是 Lifecycle 的子类,则会调用其 start 方法启动 Valve 组件,
((Lifecycle) pipeline).start();
}
setState(LifecycleState.STARTING);
// Start our thread
// 开启ContainerBackgroundProcessor线程用于调用子容器的backgroundProcess方法,默认情况下backgroundProcessorDelay=-1,不会启用该线程
threadStart();
}
把子容器的启动步骤放在线程中处理,默认情况下线程池只有一个线程处理任务队列
StandardEngine、StandardHost、StandardContext、StandardWrapper都是继承至ContainerBase,各个容器的启动,都是由父容器调用子容器的start方法,也就是说由StandardEngine 启动 StandardHost,再由 StandardHost 启动 StandardContext,最后由 StandardContext 启动 StandardWrapper。
由于它们都是继续至ContainerBase,当调用 start 启动Container容器时,首先会执行 ContainerBase 的 start 方法,它会寻找子容器,并且在线程池中启动子容器
关键代码
results.add(startStopExecutor.submit(new StartChild(child)));
StartChild 代码如下:
private static class StartChild implements Callable<Void> {
private Container child;
public StartChild(Container child) {
this.child = child;
}
@Override
public Void call() throws LifecycleException {
// 子容器启动
child.start();
return null;
}
}
ContainerBase 会把 StartChild 任务(子容器启动)丢给线程池处理,得到 Future,并且会遍历所有的 Future进行阻塞 result.get(),这个操作是将异步启动转同步,子容器启动完成才会继续运行。我们再来看看 submit 到线程池的 StartChild 任务,它实现了 java.util.concurrent.Callable 接口,在 call 里面完成子容器的 start 动作。
5.4 Host启动
我们来看下 StandardHost.startInternal
来看看构造方法,该方法用于设置 Host 的基础阀门 StandardHostValve。
public StandardHost() {
super();
pipeline.setBasic(new StandardHostValve());
}
@Override
protected synchronized void startInternal() throws LifecycleException {
// errorValve 默认使用 ErrorReportValve
String errorValve = getErrorReportValveClass();
if ((errorValve != null) && (!errorValve.equals(""))) {
try {
boolean found = false;
Valve[] valves = getPipeline().getValves();
for (Valve valve : valves) {
if (errorValve.equals(valve.getClass().getName())) {
found = true;
break;
}
}
if(!found) {
// 添加到basic前面
Valve valve =
(Valve) Class.forName(errorValve).getConstructor().newInstance();
getPipeline().addValve(valve);
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString(
"standardHost.invalidErrorReportValveClass",
errorValve), t);
}
}
super.startInternal();
}
StandardHost Pipeline 包含的 Valve 组件:
- AccessLogValve
- StandardHostValve (basic)
AccessLogValve 在管道中排在 StandardHostValve 前面
5.5 Context启动
接下来我们分析一下Context的实现 StandardContext。
来看看构造方法,该方法用于设置 Host 的基础阀门 StandardContextValve。
StandardContext#startInternal()
此方法较长,暂不赘述。
5.6 Wrapper启动
Wrapper 是一个 Servlet 的包装,我们先来看构造方法。主要作用就是设置基础阀门StandardWrapperValve
public StandardWrapper() {
super();
swValve=new StandardWrapperValve();
pipeline.setBasic(swValve);
broadcaster = new NotificationBroadcasterSupport();
}
StandardWrapper#startInternal()
代码如下:
@Override
protected synchronized void startInternal() throws LifecycleException {
// Send j2ee.state.starting notification
if (this.getObjectName() != null) {
Notification notification = new Notification("j2ee.state.starting",
this.getObjectName(),
sequenceNumber++);
broadcaster.sendNotification(notification);
}
// Start up this component
super.startInternal();
setAvailable(0L);
// Send j2ee.state.running notification
if (this.getObjectName() != null) {
Notification notification =
new Notification("j2ee.state.running", this.getObjectName(),
sequenceNumber++);
broadcaster.sendNotification(notification);
}
}
5.7 Pipeline启动
ContainerBase#startInternal()
中子组件启动过后会执行 pipeline 的启动如下所示:
if (pipeline instanceof Lifecycle) {
// 默认使用 StandardPipeline 实现类,它也是一个Lifecycle。在容器启动的时候,StandardPipeline 会遍历 Valve 链表,如果 Valve 是 Lifecycle 的子类,则会调用其 start 方法启动 Valve 组件,
((Lifecycle) pipeline).start();
}
本系列第一篇文章《Tomcat架构的秘密》曾介绍过 Pipeline 机制,简单回忆下。
Tomcat 引入了Pipeline-Valve管道-阀门机制,可以理解成是责任链模式。
用一条管道把多个对象(阀门部件)连接起来,整体看起来就像若干个阀门嵌套在管道中一样,而处理逻辑放在阀门上。
每一个组件都会有一个 Pipeline 结构,里面包含了 n 个Valve,每个 Pipeline 都有特定的 Valve,而且是在管道的最后一个执行,这个 Valve 叫做 BaseValve(不可删除的)。若配置了一个 Valve1,则加在最前面,若没有配置,则走默认的Pipeline流程(责任链模式)
默认使用 StandardPipeline 实现类,它也是一个 Lifecycle。
在容器启动的时候,StandardPipeline 会递归遍历 Valve 链表,如果 Valve 是 Lifecycle 的子类,则会调用其 start 方法启动 Valve 组件,并由由LifecycleBase发布start事件,代码如下
@Override
protected synchronized void startInternal() throws LifecycleException {
// Start the Valves in our pipeline (including the basic), if any
Valve current = first;
if (current == null) {
current = basic;
}
while (current != null) {
if (current instanceof Lifecycle) {
((Lifecycle) current).start();
}
current = current.getNext();
}
// 由LifecycleBase发布start事件
setState(LifecycleState.STARTING);
}
5.8 Connector启动
来看下 Connector#startInternal()
实现
@Override
protected void startInternal() throws LifecycleException {
// Validate settings before starting
if (getPortWithOffset() < 0) {
throw new LifecycleException(sm.getString(
"coyoteConnector.invalidPort", Integer.valueOf(getPortWithOffset())));
}
setState(LifecycleState.STARTING);
try {
// 调用ProtocolHandler启动方法
protocolHandler.start();
} catch (Exception e) {
throw new LifecycleException(
sm.getString("coyoteConnector.protocolHandlerStartFailed"), e);
}
}
protocolHandler.start() 默认实现如下
@Override
public void start() throws Exception {
if (getLog().isInfoEnabled()) {
getLog().info(sm.getString("abstractProtocolHandler.start", getName()));
logPortOffset();
}
// 启动Endpoint
endpoint.start();
// Start timeout thread
// 开启异步超时线程,线程执行单元为`Asynctimeout`
asyncTimeout = new AsyncTimeout();
Thread timeoutThread = new Thread(asyncTimeout, getNameInternal() + "-AsyncTimeout");
int priority = endpoint.getThreadPriority();
if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) {
priority = Thread.NORM_PRIORITY;
}
timeoutThread.setPriority(priority);
timeoutThread.setDaemon(true);
timeoutThread.start();
}
- 调用了
endpoint.start()
方法,Endpoint 运行多个线程(由属性 acceptorThreadCount 确定),每个线程运行一个AbstractEndpoint.Acceptor
实例 - 开启异步超时线程,线程执行单元为
Asynctimeout
,检测超时的请求,并将该请求再转发到工作线程池处理
来详细看下 endpoint.start()
public final void start() throws Exception {
if (bindState == BindState.UNBOUND) {
bindWithCleanup();
bindState = BindState.BOUND_ON_START;
}
// 监听端口通信(I/O方式)不同,具体处理方式也不同
startInternal();
}
@Override
public void startInternal() throws Exception {
if (!running) {
running = true;
paused = false;
if (socketProperties.getProcessorCache() != 0) {
processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getProcessorCache());
}
if (socketProperties.getEventCache() != 0) {
eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getEventCache());
}
if (socketProperties.getBufferPool() != 0) {
nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getBufferPool());
}
// Create worker collection
// 创建工作者线程池(真正执行任务的)
if (getExecutor() == null) {
createExecutor();
}
// 初始化连接latch,用于限制请求的并发量 最大处理8192个连接请求
initializeConnectionLatch();
// Start poller thread
// 单线程开启轮询poller线程。poller用于对接受者线程生产的消息(或事件)进行处理,poller最终调用的是Handler的代码
poller = new Poller();
Thread pollerThread = new Thread(poller, getName() + "-Poller");
pollerThread.setPriority(threadPriority);
pollerThread.setDaemon(true);
pollerThread.start();
// 单线程acceptor(接受端口来的数据)后台启动线程,只要Endpoint处于运行状态,始终循环监听
startAcceptorThread();
}
}
创建工作线程池 createExecutor()
/**
* 启动work线程池
*/
public void createExecutor() {
internalExecutor = true;
TaskQueue taskqueue = new TaskQueue();
TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());
// 默认Work线程大小10
executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS, taskqueue, tf);
taskqueue.setParent((ThreadPoolExecutor) executor);
}
创建 Accpetor线程 startAcceptorThread();
protected void startAcceptorThread() {
acceptor = new Acceptor<>(this);
String threadName = getName() + "-Acceptor";
acceptor.setThreadName(threadName);
Thread t = new Thread(acceptor, threadName);
t.setPriority(getAcceptorThreadPriority());
t.setDaemon(getDaemon());
t.start();
}
创建 Poller 线程
public class Poller implements Runnable {
private Selector selector;
// poller事件队列
private final SynchronizedQueue<PollerEvent> events =
new SynchronizedQueue<>();
public Poller() throws IOException {
// 为每个Poller打开了一个新的Selector
this.selector = Selector.open();
}
/**
* The background thread that adds sockets to the Poller, checks the
* poller for triggered events and hands the associated socket off to an
* appropriate processor as events occur.
* <p>
* Poller线程执行方法 事件处理
*/
@Override
public void run() {
// Loop until destroy() is called
// 循环
while (true) {
boolean hasEvents = false;
try {
if (!close) {
hasEvents = events();
if (wakeupCounter.getAndSet(-1) > 0) {
// If we are here, means we have other stuff to do
// Do a non blocking select
keyCount = selector.selectNow();
} else {
keyCount = selector.select(selectorTimeout);
}
wakeupCounter.set(0);
}
if (close) {
events();
timeout(0, false);
try {
selector.close();
} catch (IOException ioe) {
log.error(sm.getString("endpoint.nio.selectorCloseFail"), ioe);
}
break;
}
// Either we timed out or we woke up, process events first
if (keyCount == 0) {
hasEvents = (hasEvents | events());
}
} catch (Throwable x) {
ExceptionUtils.handleThrowable(x);
log.error(sm.getString("endpoint.nio.selectorLoopError"), x);
continue;
}
// 获取当前选择器中所有注册的“选择键(已就绪的监听事件)”
Iterator<SelectionKey> iterator =
keyCount > 0 ? selector.selectedKeys().iterator() : null;
// Walk through the collection of ready keys and dispatch
// any active event.
while (iterator != null && iterator.hasNext()) {
SelectionKey sk = iterator.next();
iterator.remove();
NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment();
// Attachment may be null if another thread has called
// cancelledKey()
if (socketWrapper != null) {
// 真正处理事件的地方
processKey(sk, socketWrapper);
}
}
// Process timeouts
timeout(keyCount, hasEvents);
}
getStopLatch().countDown();
}
protected void processKey(SelectionKey sk, NioSocketWrapper socketWrapper) {
try {
if (close) {
cancelledKey(sk, socketWrapper);
} else if (sk.isValid()) {
if (sk.isReadable() || sk.isWritable()) {
if (socketWrapper.getSendfileData() != null) {
processSendfile(sk, socketWrapper, false);
} else {
unreg(sk, socketWrapper, sk.readyOps());
boolean closeSocket = false;
// Read goes before write
// 处理读事件,比如生成Request对象
if (sk.isReadable()) {
if (socketWrapper.readOperation != null) {
if (!socketWrapper.readOperation.process()) {
closeSocket = true;
}
} else if (socketWrapper.readBlocking) {
synchronized (socketWrapper.readLock) {
socketWrapper.readBlocking = false;
socketWrapper.readLock.notify();
}
} else if (!processSocket(socketWrapper, SocketEvent.OPEN_READ, true)) {
closeSocket = true;
}
}
// 处理写事件,比如将生成的Response对象通过socket写回客户端
if (!closeSocket && sk.isWritable()) {
if (socketWrapper.writeOperation != null) {
if (!socketWrapper.writeOperation.process()) {
closeSocket = true;
}
} else if (socketWrapper.writeBlocking) {
synchronized (socketWrapper.writeLock) {
socketWrapper.writeBlocking = false;
socketWrapper.writeLock.notify();
}
} else if (!processSocket(socketWrapper, SocketEvent.OPEN_WRITE, true)) {
closeSocket = true;
}
}
if (closeSocket) {
cancelledKey(sk, socketWrapper);
}
}
}
} else {
// Invalid key
cancelledKey(sk, socketWrapper);
}
} catch (CancelledKeyException ckx) {
cancelledKey(sk, socketWrapper);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("endpoint.nio.keyProcessingError"), t);
}
}
上述代码主要做了以下 4 件事:
- 创建 Work 工作者线程池 用于真正执行任务,默认10个等待处理
- 初始化连接 latch,用于限制请求的并发量,最大处理 8192 个连接请求
- 创建轮询单 Poller 守护线程。poller用于对接受者线程生产的消息(或事件)进行处理, Poller 线程主要用于以较少的资源轮询已连接套接字以保持连接,当数据可用时转给工作线程。
- 创建单 Acceptor 守护线程,端口 8080,Acceptor 线程主要用于监听套接字,将已连接套接字转给Poller线程,Acceptor 线程数由 AbstracEndPoint 的 acceptorThreadCount 成员变量控制,默认值为 1 。
现在你可能会有些好奇,为什么 Tomcat 启动这么多种类的线程,这些线程各自有什么作用,为什么要这么设计,以及 Connector 请求逻辑,这些会放到下一篇文章《Tomcat源码解读之HTTP请求处理流程》中解读。
5.9 启动的后置处理
Bootstrap 中会设置 await 为 true,其目的在于让 Tomcat 在 shutdown 端口阻塞监听关闭命令。
回到 Catalina 的启动流程的后置处理部分
// Bootstrap中会设置await为true,其目的在于让tomcat在shutdown端口阻塞监听关闭命令
if (await) {
// 作用就是在tomcat 启动完成之后,8005端口处于一种等待请求状态接受数据
await();
stop();
}
public void await() {
getServer().await();
}
调用 StandardServer 的 await 方法
@Override
public void await() {
// Negative values - don't wait on port - tomcat is embedded or we just don't like ports
if (getPortWithOffset() == -2) {
// undocumented yet - for embedding apps that are around, alive.
return;
}
if (getPortWithOffset() == -1) {
try {
awaitThread = Thread.currentThread();
while (!stopAwait) {
try {
Thread.sleep(10000);
} catch (InterruptedException ex) {
// continue and check the flag
}
}
} finally {
awaitThread = null;
}
return;
}
// Set up a server socket to wait on
try {
awaitSocket = new ServerSocket(getPortWithOffset(), 1,
InetAddress.getByName(address));
} catch (IOException e) {
log.error(sm.getString("standardServer.awaitSocket.fail", address,
String.valueOf(getPortWithOffset()), String.valueOf(getPort()),
String.valueOf(getPortOffset())), e);
return;
}
try {
awaitThread = Thread.currentThread();
// Loop waiting for a connection and a valid command
while (!stopAwait) {
ServerSocket serverSocket = awaitSocket;
if (serverSocket == null) {
break;
}
// Wait for the next connection
Socket socket = null;
StringBuilder command = new StringBuilder();
try {
InputStream stream;
long acceptStartTime = System.currentTimeMillis();
try {
// 开启端口8005接收数据
socket = serverSocket.accept();
socket.setSoTimeout(10 * 1000); // Ten seconds
stream = socket.getInputStream();
} catch (SocketTimeoutException ste) {
// This should never happen but bug 56684 suggests that
// it does.
log.warn(sm.getString("standardServer.accept.timeout",
Long.valueOf(System.currentTimeMillis() - acceptStartTime)), ste);
continue;
} catch (AccessControlException ace) {
log.warn(sm.getString("standardServer.accept.security"), ace);
continue;
} catch (IOException e) {
if (stopAwait) {
// Wait was aborted with socket.close()
break;
}
log.error(sm.getString("standardServer.accept.error"), e);
break;
}
// Read a set of characters from the socket
int expected = 1024; // Cut off to avoid DoS attack
while (expected < shutdown.length()) {
if (random == null) {
random = new Random();
}
expected += (random.nextInt() % 1024);
}
while (expected > 0) {
int ch = -1;
try {
ch = stream.read();
} catch (IOException e) {
log.warn(sm.getString("standardServer.accept.readError"), e);
ch = -1;
}
// Control character or EOF (-1) terminates loop
if (ch < 32 || ch == 127) {
break;
}
command.append((char) ch);
expected--;
}
} finally {
// Close the socket now that we are done with it
try {
if (socket != null) {
socket.close();
}
} catch (IOException e) {
// Ignore
}
}
// Match against our command string
boolean match = command.toString().equals(shutdown);
if (match) {
log.info(sm.getString("standardServer.shutdownViaPort"));
break;
} else {
log.warn(sm.getString("standardServer.invalidShutdownCommand", command.toString()));
}
}
} finally {
ServerSocket serverSocket = awaitSocket;
awaitThread = null;
awaitSocket = null;
// Close the server socket and return
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
// Ignore
}
}
}
}
关键代码
socket = serverSocket.accept();
在 Tomcat 启动完成之后,8005端口处于一种等待请求状态接受数据,让主线程不退出。
至此 Tomcat 服务器就启动了。
6. 小结
自本系列第一篇文章对 Tomcat 架构进行了分析后,本文又对 Tomcat 启动流程进行了源码分析,至此对 Tomcat 框架的大体架构应该有了基本的认识,你也许会好奇 Tomcat 成功启动了之后,一个请求经过 Tomcat 处理后响应回去的流程是什么样的呢,不要着急,下篇文章我们一起来探索。
本文涉及到的 Tomcat 启动流程的源码分析图如下链接:
https://www.processon.com/view/link/63b991f1971d7b4eb6afdd1c