运行Bootstrap启动类后,会创建Catalina实例,并调用其process()
方法。
org.apache.catalina.startup.Catalina类
先看process方法做了那些事情
public void process(String args[]) {
setCatalinaHome(); //设置"catalina.home"路径
setCatalinaBase(); //设置"catalina.base"路径
try {
if (arguments(args)) //分析命令带入的参数
execute(); //执行
} catch (Exception e) {
e.printStackTrace(System.out);
}
}
protected void execute() throws Exception {
if (starting) //若启动参数带有start,则starting变量为true
start();
else if (stopping) //若启动参数带有stop,则stopping变量为true
stop();
}
每个启动参数-config -debug -nonaming start stop
其实都会对应一个变量,记录这些启动参数,控制程序运行时的部分细节.
Catalina类中的start()
protected void start() {
// Create and execute our Digester
/**
* 加载分析/conf/server.xml的digester
*/
Digester digester = createStartDigester();
/**
* 找到server.xml文件
* 解析server.xml文件 组装成想要的server对象并将其赋值给当前catalina类
*/
File file = configFile();
try {
InputSource is =
new InputSource("file://" + file.getAbsolutePath());
FileInputStream fis = new FileInputStream(file);
is.setByteStream(fis);
digester.push(this); //--------这一步解释了digester规则中-匹配Server的规则-digester.addSetNext("Server"...)
digester.parse(is);
fis.close();
} catch (Exception e) {
System.out.println("Catalina.start: " + e);
e.printStackTrace(System.out);
System.exit(1);
}
// Setting additional variables
if (!useNaming) {
System.setProperty("catalina.useNaming", "false");
} else {
System.setProperty("catalina.useNaming", "true");
//TODO
//....有待分析
}
// If a SecurityManager is being used, set properties for
// checkPackageAccess() and checkPackageDefinition
if( System.getSecurityManager() != null ) {
String access = Security.getProperty("package.access");
//TODO
//....有待分析
}
// Replace System.out and System.err with a custom PrintStream
SystemLogHandler log = new SystemLogHandler(System.out);
System.setOut(log);
System.setErr(log);
Thread shutdownHook = new CatalinaShutdownHook();
// Start the new server
if (server instanceof Lifecycle) {
try {
server.initialize();
((Lifecycle) server).start();
try {
// Register shutdown hook
Runtime.getRuntime().addShutdownHook(shutdownHook);
} catch (Throwable t) {
// This will fail on JDK 1.2. Ignoring, as Tomcat can run
// fine without the shutdown hook.
}
// Wait for the server to be told to shut down
server.await();
} catch (LifecycleException e) {
System.out.println("Catalina.start: " + e);
e.printStackTrace(System.out);
if (e.getThrowable() != null) {
System.out.println("----- Root Cause -----");
e.getThrowable().printStackTrace(System.out);
}
}
}
// Shut down the server
if (server instanceof Lifecycle) {
try {
try {
// Remove the ShutdownHook first so that server.stop()
// doesn't get invoked twice
Runtime.getRuntime().removeShutdownHook(shutdownHook);
} catch (Throwable t) {
// This will fail on JDK 1.2. Ignoring, as Tomcat can run
// fine without the shutdown hook.
}
((Lifecycle) server).stop();
} catch (LifecycleException e) {
System.out.println("Catalina.stop: " + e);
e.printStackTrace(System.out);
if (e.getThrowable() != null) {
System.out.println("----- Root Cause -----");
e.getThrowable().printStackTrace(System.out);
}
}
}
}
这个是Tomcat启动的关键,包含了以下流程:
- 创建了server.xml文件的digerster(用于解析xml文件并依据制定的规则创建对应的类);
- 找到并解析server.xml文件,完成后,catalina类中的变量server就是依照server.xml配置生成的对象;
- useNaming…(待分析);
- securityManager…(待分析);
- 配置关闭钩子–CatalinaShutdownHook();
server.initialize(); ((Lifecycle) server).start();
这两步,包含了启动所有的Tomcat要使用的组件(一个包含connectors和container的service);server.wait();
监听另外的端口的停止命令,无线循环直至收到停止命令;- 收到命令后才会继续执行的:移除钩子(避免执行两次停止程序),执行
server.stop();
,关闭server服务器组件包含的所有内容;
备注:server表示Catalina的整个Servlet引擎,囊括所有的组件。有了它,我们就无需再对connectors和container(处理http请求的两大关键模块 )分别启动/关闭了
Catalina类中的stop()
protected void stop() {
// Create and execute our Digester
Digester digester = createStopDigester();
File file = configFile();
try {
InputSource is =
new InputSource("file://" + file.getAbsolutePath());
FileInputStream fis = new FileInputStream(file);
is.setByteStream(fis);
digester.push(this);
digester.parse(is);
fis.close();
} catch (Exception e) {
System.out.println("Catalina.stop: " + e);
e.printStackTrace(System.out);
System.exit(1);
}
// Stop the existing server
try {
Socket socket = new Socket("127.0.0.1", server.getPort());
OutputStream stream = socket.getOutputStream();
String shutdown = server.getShutdown();
for (int i = 0; i < shutdown.length(); i++)
stream.write(shutdown.charAt(i));
stream.flush();
stream.close();
socket.close();
} catch (IOException e) {
System.out.println("Catalina.stop: " + e);
e.printStackTrace(System.out);
System.exit(1);
}
}
server的关闭流程:
- 依据server.xml创建关闭所需的digester(比启动简单的多),解析server.xml;
- 向server侦听的关闭端口(解析server.xml文件获取)发送关闭指令,通过start()流程中的步骤8,关闭server.
分析可以看出,Catalina中的server是一个很重要的对象,整个流程基本可概括为构建server对象,启动server以及关闭server,server是干什么的呢。
问题
- catalina也有main函数, 为什么要建一个Bootstrap用反射的机制来调用catalina.process()
这样启动和调用Bootstrap类启动有什么区别?public static void main(String args[]) { (new Catalina()).process(args); }
答: Bootstrap类开头的一段注释解释了为什么
这种迂回的方式的目的就是将catalina的内部类以及其他catalina所依赖的类与应用层的calsspath区分开,这样的话就能保证catalina所使用依赖的类对应用程序级别的类不可见.* Bootstrap loader for Catalina. This application constructs a class loader * for use in loading the Catalina internal classes (by accumulating all of the * JAR files found in the "server" directory under "catalina.home"), and * starts the regular execution of the container. The purpose of this * roundabout approach is to keep the Catalina internal classes (and any * other classes they depend on, such as an XML parser) out of the system * class path and therefore not visible to application level classes.
延伸
论钩子的作用和工作原理
github上的示例代码
Runtime.getRuntime().addShutdownHook(shutdownHook);
在jvm中增加一个关闭的钩子,当jvm关闭的时候,会执行系统中已经设置的所有通过方法addShutdownHook添加的钩子,当系统执行完这些钩子后,jvm才会关闭。所以这些钩子可以在jvm关闭的时候进行内存清理、对象销毁等操作.
什么是Runtime
通过Runtime实例,使得应用程序和其运行环境相连接。Runtime是在应用启动期间自动建立,应用程序不能够创建Runtime,但可以通过Runtime.getRuntime()来获得当前应用的Runtime对象引用,通过该引用我们可以获得当前运行环境的相关信息,比如空闲内存、最大内存以及为当前虚拟机添加关闭钩子(addShutdownHook()),执行指定命令(exec())等等.
jvm关闭
jvm的关闭方式有三种:
- 正常关闭:当最后一个非守护线程结束或者调用了System.exit或者通过其他特定平台的方法关闭(发送SIGINT,SIGTERM信号等);
- 强制关闭:通过调用Runtime.halt方法或者是在操作系统中直接kill(发送SIGKILL信号)掉JVM进程;
- 异常关闭:运行中遇到RuntimeException异常等。
关闭钩子(shutdown hooks)
一些情况下,我们需要在jvm关闭时做一些扫尾清理工作。为此JVM提供了关闭钩子(shutdown hooks)来做这些事情。另外特别注意的是:如果JVM因异常关闭,那么子线程(Hook本质上也是子线程)将不会停止。但在JVM被强行关闭时,这些线程都会被强行结束.
当应用程序遇到非正常关闭时,我们在应用程序中加的钩子就能起作用了,帮我们进行清理工作,Tomcat就是利用钩子在程序异常退出时正常的让server stop。