之前一直说,catalina是tomcat的一个重要组成。Server、connector、container都归Catalina来管理,它的主要功能就是,在启动的时候读取server.xml文件,创建server对象,也就创建了server管理的connector、container。在应用程序关闭的时候,它进行一些善后的工作。
应用程序关闭的时候,需要做一些清理现场的工作。如果程序正常关闭,可以调用catalina.stop()进行清理。可是如果用户键入ctrl+c或者未关闭程序的情况下退出系统,会导致jvm强制关闭,这时候怎么办呢?
于是就有了ShutdownHook。
ShutdownHook是交给系统的,当程序异常关闭的时候,虚拟机会调用启动之前已经注册的ShutdownHook的run(),来进行清理工作。
// --------------------------------------- CatalinaShutdownHook Inner Class /** * CatalinaShutdownHook是catalina的内部类,调用server的stop方法清理内部的connector、container等 * Shutdown hook which will perform a clean shutdown of Catalina if needed. */ protected class CatalinaShutdownHook extends Thread { public void run() { if (server != null) { try { ((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); } } } } }catalina在启动时,读取server.xml文件,创建Server,这是用digester实现的。
/** * Startup/Shutdown shell program for Catalina. The following command line * options are recognized: * <ul> * <li><b>-config {pathname}</b> - Set the pathname of the configuration file * to be processed. If a relative path is specified, it will be * interpreted as relative to the directory pathname specified by the * "catalina.base" system property. [conf/server.xml] * <li><b>-help</b> - Display usage information. * <li><b>-stop</b> - Stop the currently running instance of Catalina. * </u> * * @author Craig R. McClanahan * @version $Revision: 1.48 $ $Date: 2002/05/23 17:22:37 $ */ public class Catalina { /** * Pathname to the server configuration file. */ protected String configFile = "conf/server.xml"; /** * 管理的Server * The server component we are starting or stopping */ protected Server server = null; /** * The application main program. * * @param args Command line arguments */ public static void main(String args[]) { (new Catalina()).process(args); } /** * The instance main program. * * @param args Command line arguments */ public void process(String args[]) { setCatalinaHome(); setCatalinaBase(); try { if (arguments(args)) execute(); } catch (Exception e) { e.printStackTrace(System.out); } } /** * Execute the processing that has been configured from the command line. */ protected void execute() throws Exception { if (starting) start(); else if (stopping) stop(); } /** * 创建digester,用于读取server.xml * Create and configure the Digester we will be using for startup. */ protected Digester createStartDigester() { // Initialize the digester Digester digester = new Digester(); if (debug) digester.setDebug(999); digester.setValidating(false); // Configure the actions we will be using digester.addObjectCreate("Server", "org.apache.catalina.core.StandardServer", "className"); digester.addSetProperties("Server"); digester.addSetNext("Server", "setServer", "org.apache.catalina.Server");//在start()中首先push了catalina,所以此处是调用catalina.setServer() digester.addObjectCreate("Server/GlobalNamingResources", "org.apache.catalina.deploy.NamingResources"); digester.addSetProperties("Server/GlobalNamingResources"); digester.addSetNext("Server/GlobalNamingResources", "setGlobalNamingResources", "org.apache.catalina.deploy.NamingResources"); //此处...省略一大段 return (digester); } /** * Start a new server instance. */ protected void start() { // Create and execute our Digester Digester digester = createStartDigester(); 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.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"); String value = "org.apache.naming"; String oldValue = System.getProperty(javax.naming.Context.URL_PKG_PREFIXES); if (oldValue != null) { value = value + ":" + oldValue; } System.setProperty(javax.naming.Context.URL_PKG_PREFIXES, value); value = System.getProperty (javax.naming.Context.INITIAL_CONTEXT_FACTORY); if (value == null) { System.setProperty (javax.naming.Context.INITIAL_CONTEXT_FACTORY, "org.apache.naming.java.javaURLContextFactory"); } } // If a SecurityManager is being used, set properties for // checkPackageAccess() and checkPackageDefinition if( System.getSecurityManager() != null ) { String access = Security.getProperty("package.access"); if( access != null && access.length() > 0 ) access += ","; else access = "sun.,"; Security.setProperty("package.access", access + "org.apache.catalina.,org.apache.jasper."); String definition = Security.getProperty("package.definition"); if( definition != null && definition.length() > 0 ) definition += ","; else definition = "sun.,"; Security.setProperty("package.definition", // FIX ME package "javax." was removed to prevent HotSpot // fatal internal errors definition + "java.,org.apache.catalina.,org.apache.jasper."); } // 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 { //注册shutdownhook到系统中。如果程序异常关闭,就无法从server.await()返回,就会执行shutdownhook.run(),来进行清理 // 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. } //await()建立一个单独的线程,等待shutdown指令,收到shutdown指令后返回,就会执行下面的server.stop(),进行关闭操作 // 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); } } } }catalina.process收到关闭命令时,调用stop()进行清理工作。
“`
/** * Stop an existing server instance. */ protected void stop() { // Create and execute our Digester //这里只读取了server部分,是为了回收前一个Server吗?? 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 { //发送shutdown命令,await线程收到后,从await()返回,即可执行server.stop() 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); } } /** * Create and configure the Digester we will be using for shutdown. */ protected Digester createStopDigester() { // Initialize the digester Digester digester = new Digester(); if (debug) digester.setDebug(999); // Configure the rules we need for shutting down //只读取了server部分 digester.addObjectCreate("Server", "org.apache.catalina.core.StandardServer", "className"); digester.addSetProperties("Server"); digester.addSetNext("Server", "setServer", "org.apache.catalina.Server"); return (digester); }stop()中创建了一个新的digester,读取server.xml文件。不过读取的内容要少一些,只创建了一个简单的Server对象,没有创建内部的connector、container。然后从这个server中,拿到shutdown命令,并发送。这里为什么要创建一个新的server呢?
我的理解是,创建一个新的server,catalina就不再包含旧的server的引用。这样,等待shutdown命令的线程收到命令并结束,旧的server从await()返回后,旧的server就可以被回收了。
tomcat实际启动,不是手动运行Bootstrap.jar来执行的,而是sh catalina.sh start这样启动脚本来启动。tomcat下bin目录下有多个.sh文件,包括startup.sh catalina.sh shutdown.sh等。startup.sh shutdown.sh实际都是调用了catalina.sh,只是传递了不同的参数。这个可以通过Catalina类的代码也可以看出,传递不同的参数,会执行不同的操作,包括开始和结束。