Tomcat源码解析-高层之Catalina
1 Catalina职责
- 通过解析server.xml、构建tomcat内所有组件。
- 给tomcat JVM进程注册一个钩子线程,负责用来当tomcat被关闭时完成一些清理工作。
2 Catalina运行流程
2.1 Bootstarp运行流程
Bootstarp作为tomcat启动类,JVM启动会调用main函数完成tomcat启动启动,停止,配置等功能,其内部流程如下:
-
1 实例化Bootstrap对象。
-
2 对Bootstrap对象init,其内部包括构建Tomcat自定义类加载器,并实例化Catalina对象
-
3 以main函数args参数作为指令,通过调用Catalina类中不同的方法,完成相应的功能。
- startd指令 : 首先调用load()方法,接着调用start()方法,
- start指令 : 首先调用setAwait()方法,接着调用load()方法,最后调用start()方法
- stopd指令 : 调用stopServer()方法
- configtest指令 : 调用load()方法
核心方法功能
- bootstrap.init():负责初始化Bootstrap,在初始化的过程最重要的就是创建tomcat自定义的类加载器commonLoader,catalinaLoader,sharedLoader,并创建Catalina。
- daemon.load(args):负责通过反射调用Catalina.load方法,实现tomcat加载。
- daemon.start():负责通过反射调用Catalina.start方法,实现tomcat启动。
- daemon.stop():负责通过反射调用Catalina.stop方法,实现tomcat停止。
- daemon.setAwait(true):负责通过反射调用Catalina.setAwait方法,设置当前命令执行执行的线程是否要阻塞,对于start指令,当前线程是tomcat主线程,我们当然需要阻塞主线程,保证tomcat的运行。而主线程被释放则表示tomcat被停止。
2.2 Catalina运行流程
- catalina.init():使用Digester解析Server.xml,并构建Server.xml中描述的所有组件,之后调用刚刚构建好的Server组件初始化方法init().
- catalina.start():首先调用Server组件start启动方法,之后给当前JVM进程注册一个钩子线程,该线程负责用来当tomcat被关闭时完成一些清理工作。最后调用Server组件await()方法阻塞当前线程(tomcat主线程),如果当前线程从await()方法返回表示tomcat被停止。
- catalina.stop():负责向JVM清理掉注册的钩子线程,之后调用Server组件停止方法stop(),最后调用清理方法destroy()。
- catalina.stopServer():首先调用Server组件停止方法stop(),之后调用清理方法destroy(),最后向Tomcat指定的端口发送shutdown指令,tomcat接受指令后会使tomcat主线程从阻塞中退出。
3 实例化Catalina
/**
* 实例化Catalina
*/
public Catalina() {
/** 从catalina.properties读取受保护类,注册到Security中 **/
setSecurityProtection();
ExceptionUtils.preload();
}
4 加载Catalina
加载Catalina核心流程如下:
-
1 Catalina加载核心是使用Digester解析Server.xml,并实例化所有组件
-
2 调用Server组件初始化方法init(),由于server组件是所有tomcat组件父组件,而父组件初始化过程中会调用子组件的初始化,因此可以说调用Server组件start初始化方法相当于一键初始化tomcat中所有组件;
/**
* tomcat配置文件,用来实例化Tomcat Server组件
*/
protected String configFile = "conf/server.xml";
/**
* Tomcat Server组件
*/
protected Server server = null;
public void load() {
/** 判断是否已经加载过,防止重复加载 **/
if (loaded) {
return;
}
loaded = true;
long t1 = System.nanoTime();
/** 检查java.io.tmdir系统属性值对应目录是否存在 **/
initDirs();
/** 初始化JNDI系统属性 **/
initNaming();
/** 创建Digester实例,Digester中定义解析/conf/Server.xml文件规则**/
Digester digester = createStartDigester();
InputSource inputSource = null;
InputStream inputStream = null;
File file = null;
try {
/** 读取 catalina_home\conf\server.xml 配置文件 **/
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);
}
}
/** 读取 classpath:\conf\server.xml **/
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);
}
}
}
/** 读取 classpath:\conf\server-embed.xml **/
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;
}
/** 解析xml转换为server对象,并设置到Catalina.server属性中 **/
try {
inputSource.setByteStream(inputStream);
digester.push(this);
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);
/** 设置server组件在所在tomcat安装目录 **/
getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
/** 设置server组件在所在tomcat工作目录 **/
getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
// 调用System类的方法重定向标准输出和标准错误输出
initStreams();
/** 初始化Server【一键初始化tomcat所有组件】**/
try {
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");
}
}
/**
* 返回配置文件的绝对路径File对象
* @return the main configuration file
*/
protected File configFile() {
File file = new File(configFile);
/** 判断路径名是否是绝对的 **/
if (!file.isAbsolute()) {
file = new File(Bootstrap.getCatalinaBase(), configFile);
}
return (file);
}
4.1 Digester
Digester是一款用于将xml转换为Java对象的事件驱动型工具,是对SAX的高层次的封装。
使用流程
-
1 是创建一个解析器Digester,其中定义每一个标签对应的一个或多个解析规则。
-
2 将当前对象Catalina压入Digester栈结构顶部,作为要处理对象。
-
3 将xml文件解析成对象,并按照标签名称设置到栈顶元素对应的属性中。这里会将XML文件解析为一个Server组件对象设置到Catalina对象server属性中。
/**
* Tomcat Server组件
*/
protected Server server = null;
/** 将xml解析对象放置到当前对象属性中 **/
try {
inputSource.setByteStream(inputStream);
digester.push(this);
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;
}
默认server.xml
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<GlobalNamingResources>
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
</GlobalNamingResources>
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" socket.soKeepAlive="true"/>
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
<Engine name="Catalina" defaultHost="localhost">
<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
</Engine>
</Service>
</Server>
详细请参考:Tomcat相关技术-Digester(二)Digester使用和原理。
5 启动Catalina
启动Catalina核心流程如下:
1 启动Catalina核心是调用Server组件start启动方法。由于server组件是所有tomcat组件父组件,而父组件启动过程中会调用子组件的启动,因此可以说调用Server组件start启动方法相当于一键启动tomcat中所有组件;
2 给当前JVM进程注册一个钩子线程,用来完成tomcat进程被关闭前执行一些清理工作。
3 调用Server组件await()方法阻塞当前线程(tomcat主线程)。如果当前线程从await()方法返回表示tomcat被停止。
如下情况会导致tomcat被停止。
- 1 内部调用server组件stopAwait()方法,表示将要停止tomcat
- 2 接收到客户端发起SHUTDOWN命令
/**
* 是否需要阻塞tomcat主线程
*/
protected boolean await = false;
/**
* 是否需要向JVM注册关闭钩子线程,当JVM发生以下情况关闭前,会触发注册钩子线程动作
* 1. 程序正常退出
* 2. 使用System.exit()
* 3. 终端使用Ctrl+C触发的中断
* 4. 系统关闭
* 5. 使用Kill pid命令干掉进程
*/
protected boolean useShutdownHook = true;
/**
* JVM注册关闭钩子线程
*/
protected Thread shutdownHook = null;
/**
* 是否开启JNDI服务
*/
protected boolean useNaming = true;
/**
* 加载标识,防止重复加载。
*/
protected boolean loaded = false;
/**
* 启动Catalina
*/
public void start() {
/** 如果不存在server组件,说明初始化失败,重新加载Catalina **/
if (getServer() == null) {
/** 重新加载Catalina **/
load();
}
/** 如果还是找不到server组件,直接返回 **/
if (getServer() == null) {
log.fatal("Cannot start server. Server instance is not configured.");
return;
}
/** 获取当前时间 **/
long t1 = System.nanoTime();
/** 启动Server组件 **/
try {
getServer().start();
}
/** 发生LifecycleException异常 销毁Server组件 **/
catch (LifecycleException e) {
log.fatal(sm.getString("catalina.serverStartFail"), e);
try {
/** 销毁Server组件 **/
getServer().destroy();
} catch (LifecycleException e1) {
log.debug("destroy() failed for failed Server ", e1);
}
return;
}
/** 打印tomcat执行启动用时 **/
long t2 = System.nanoTime();
if(log.isInfoEnabled()) {
log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
}
/** 判断是否需要向JVM注册一个钩子线程, **/
if (useShutdownHook) {
/** 实例化钩子线程CatalinaShutdownHook **/
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
/**将shutdownHook注册JMV**/
Runtime.getRuntime().addShutdownHook(shutdownHook);
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).setUseShutdownHook(
false);
}
}
/** 执行Bootstarp.main函数的指令为start时会设置 Catalina的await属性为true,**/
if (await) {
/**
* 阻塞当前线程,当前线程从await()方法返回表示tomcat被停止
*
* 如下两种情况会导致tomcat停止
*
* 1 内部调用server组件stopAwait()方法
*
* 2 接收到客户端发起SHUTDOWN命令
* **/
await();
/** 停止Catalina **/
stop();
}
}
public void await() {
getServer().await();
}
/**
* tomcat关闭注册到JVM中钩子线程
*/
protected class CatalinaShutdownHook extends Thread {
@Override
public void run() {
try {
/** 如果tomcat server组件存在则调用Catalina.this.stop() **/
if (getServer() != null) {
Catalina.this.stop();
}
} catch (Throwable ex) {
ExceptionUtils.handleThrowable(ex);
log.error(sm.getString("catalina.shutdownHookFail"), ex);
} finally {
/** 关闭ClassLoaderLogManager **/
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).shutdown();
}
}
}
}
6 停止Catalina
停止Catalina核心流程如下:
1 向JVM清理掉注册的钩子线程
2 调用Server组件停止方法stop()以及清理方法destroy()
/**
* 停止Catalina
*/
public void stop() {
try {
/** 判断是否向JVM注册了钩子线程 **/
if (useShutdownHook) {
/** 向JVM清理掉注册的钩子线程 **/
Runtime.getRuntime().removeShutdownHook(shutdownHook);
/**设置LogManager中注册到JVM钩子线程在JVM停止时会执行,重置日志系统 **/
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).setUseShutdownHook(
true);
}
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
}
/** 获取Tomcat Server组件,调用stop()关闭,destroy()清理 **/
try {
Server s = getServer();
LifecycleState state = s.getState();
/** 如果Tomcat Server组件 状态为'STOPPING_PREP', 'DESTROYED' 忽略当前动作**/
if (LifecycleState.STOPPING_PREP.compareTo(state) <= 0
&& LifecycleState.DESTROYED.compareTo(state) >= 0) {
// Nothing to do. stop() was already called
} else {
s.stop();
s.destroy();
}
} catch (LifecycleException e) {
log.error("Catalina.stop", e);
}
}
7 stopServer
1 创建一个用来处理停止Server解析器digester。使用新建的digester重新解析设置到当前对象Catalina,Server属性中。这里仅仅时为了清理tomcat中组件对象占用的堆内存。
2 调用Server组件停止方法stop()以及清理方法destroy()
3 向Tomcat Server指定的端口发送shutdown指令,停止tomcat主线程。
public void stopServer(String[] arguments) {
/** 处理指定的命令行参数 **/
if (arguments != null) {
arguments(arguments);
}
/** 获取tomcat Server组件实例 **/
Server s = getServer();
/** 如果omcat Server组件实例不存在,使用Digester构造一个tomcat Server组件 **/
if (s == null) {
Digester digester = createStopDigester();
File file = configFile();
try (FileInputStream fis = new FileInputStream(file)) {
InputSource is =
new InputSource(file.toURI().toURL().toString());
is.setByteStream(fis);
digester.push(this);
digester.parse(is);
} catch (Exception e) {
log.error("Catalina.stop: ", e);
System.exit(1);
}
} else {
/** 对存在Tomcat Server组件执行停止,销毁动作 **/
try {
s.stop();
s.destroy();
} catch (LifecycleException e) {
log.error("Catalina.stop: ", e);
}
return;
}
/**
* 向Tomcat Server指定的端口发送shutdown指令
*/
s = getServer();
if (s.getPort()>0) {
try (Socket socket = new Socket(s.getAddress(), s.getPort());
OutputStream stream = socket.getOutputStream()) {
String shutdown = s.getShutdown();
for (int i = 0; i < shutdown.length(); i++) {
stream.write(shutdown.charAt(i));
}
stream.flush();
} catch (ConnectException ce) {
log.error(sm.getString("catalina.stopServer.connectException",
s.getAddress(),
String.valueOf(s.getPort())));
log.error("Catalina.stop: ", ce);
System.exit(1);
} catch (IOException e) {
log.error("Catalina.stop: ", e);
System.exit(1);
}
} else {
log.error(sm.getString("catalina.stopServer"));
System.exit(1);
}
}
8 总结时刻
Catalina是tomcat的启动类,负责实现Bootstrap指令调用方法,其中主要包括start,load,setAwait,stopServer。而其核心功能是在load方法中通过解析server.xml、构建tomcat内所有组件,并通过构建tomcat最高层组件server来实现tomcat一键初始化,启动,停止。
Catalina会在start方法给tomcat JVM进程注册一个钩子线程,负责用来当tomcat被关闭时完成一些清理工作,并调用Server组件await()方法阻塞tomcat主线程.
image image