本文重点关注启动tomcat时会用到的两个类,分别为Catalina类和Bootstrap类,它们都位于org.apachae.catalina.startup包下;Catalina类用于启动或关闭Server对象,并负责解析server.xml配置文件;Bootstrap类是一个入口点,负责创建Catalina实例,并调用其process()方法。
org.apachae.catalina.startup.Catalina类是启动类,它包含一个Digester对象,用于解析位于%CATALINE_HOME%/conf目录的server.xml文件
Catalina类还封装了一个Server对象,该对象持有一个Service对象(Service对象包含一个Servlet容器和一个或多个连接器)。可以使用Catalina类来启动/关闭Server对象
可以通过实例化Catalina类,并调用其process()方法来运行Tomcat,但在调用该方法时,需要传入适当的参数(如start或stop -help -config -debug -nonaming等)。
/** * 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()方法,execute()方法会根据传入参数调用start()方法或stop()方法
start方法会创建一个Digester对象来解析server.xml文件(Tomcat配置文件)。在解析server.xml文件之前,start方法会调用Digester对象的push方法,传入当前的Catalina对象为参数。这样,Catalina对象就成了Digester对象内部对象栈的第一个对象。解析server.xml文件后,会将变量server指向一个Server对象(默认是org.apache.catalina.core.StandardServer类型的对象)。然后,start方法会调用server的initialize和start方法。Catalina对象的start方法会调用Server对象的await方法,server对象会使用一个专用的线程来等待关闭命令。await方法会循环等待,知道接收到正确的关闭命令。当await方法返回时,Catalina对象的start方法会调用server对象的stop方法,从而关闭server对象和其他的组件。此外,start方法还会注册shutdown hook,确保服务器关闭时会执行Server对象的stop方法。
start方法的实现如下:
/** * 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 { // 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); } } } }
其中的CatalinaShutdownHook类为关闭钩子
/** * 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对象的stop方法会关闭Server对象,其实现如下:
/** * Stop an existing server instance. */ 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); } }
注意,stop方法通过调用createStopDigester方法创建一个Digester对象,然后将Catalina对象push到Digester对象的内部对象栈中。
createStartDigester方法创建了一个Digester对象,然后将规则添加到其中,解析server.xml文件。添加到Digester对象中的规则是理解tomcat配置的关键。
createStartDigester方法的实现如下:
/** * 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"); digester.addObjectCreate("Server/GlobalNamingResources", "org.apache.catalina.deploy.NamingResources"); digester.addSetProperties("Server/GlobalNamingResources"); digester.addSetNext("Server/GlobalNamingResources", "setGlobalNamingResources", "org.apache.catalina.deploy.NamingResources"); digester.addObjectCreate("Server/Listener", null, // MUST be specified in the element "className"); digester.addSetProperties("Server/Listener"); digester.addSetNext("Server/Listener", "addLifecycleListener", "org.apache.catalina.LifecycleListener"); digester.addObjectCreate("Server/Service", "org.apache.catalina.core.StandardService", "className"); digester.addSetProperties("Server/Service"); digester.addSetNext("Server/Service", "addService", "org.apache.catalina.Service"); digester.addObjectCreate("Server/Service/Listener", null, // MUST be specified in the element "className"); digester.addSetProperties("Server/Service/Listener"); digester.addSetNext("Server/Service/Listener", "addLifecycleListener", "org.apache.catalina.LifecycleListener"); digester.addObjectCreate("Server/Service/Connector", "org.apache.catalina.connector.http.HttpConnector", "className"); digester.addSetProperties("Server/Service/Connector"); digester.addSetNext("Server/Service/Connector", "addConnector", "org.apache.catalina.Connector"); digester.addObjectCreate("Server/Service/Connector/Factory", "org.apache.catalina.net.DefaultServerSocketFactory", "className"); digester.addSetProperties("Server/Service/Connector/Factory"); digester.addSetNext("Server/Service/Connector/Factory", "setFactory", "org.apache.catalina.net.ServerSocketFactory"); digester.addObjectCreate("Server/Service/Connector/Listener", null, // MUST be specified in the element "className"); digester.addSetProperties("Server/Service/Connector/Listener"); digester.addSetNext("Server/Service/Connector/Listener", "addLifecycleListener", "org.apache.catalina.LifecycleListener"); // Add RuleSets for nested elements digester.addRuleSet(new NamingRuleSet("Server/GlobalNamingResources/")); digester.addRuleSet(new EngineRuleSet("Server/Service/")); digester.addRuleSet(new HostRuleSet("Server/Service/Engine/")); digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Default")); digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/DefaultContext/")); digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/Default")); digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/DefaultContext/")); digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/")); digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/")); digester.addRule("Server/Service/Engine", new SetParentClassLoaderRule(digester, parentClassLoader)); return (digester); }
这里需要注意的是 digester.addSetNext("Server", "setServer","org.apache.catalina.Server")方法,该方法将Server对象压入到Digester对象的内部栈中,并与栈中的下一个对象相关联;在这里,下一个对象是Catalina实例,调用其setServer()方法与Server对象相关联。
createStopDigester方法返回一个Digester对象来关闭Server对象。createStopDigester方法实现如下:
/** * 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 digester.addObjectCreate("Server", "org.apache.catalina.core.StandardServer", "className"); digester.addSetProperties("Server"); digester.addSetNext("Server", "setServer", "org.apache.catalina.Server"); return (digester); }
与启动Digester对象不同,关闭Digester对象只对XML文件的根元素感兴趣
org.apache.catalina.startup.Bootstrap类提供了启动tomcat的切入点(还有一些其他的类也有此功能)。当使用bat或sh启动tomcat时,实际上会调用该类的main方法。在main方法中会创建三个loader,并实例化Catalina对象,然后调用Catalina对象的process方法。
Bootstrap类的定义如下所示:
public final class Bootstrap { // ------------------------------------------------------- Static Variables /** * Debugging detail level for processing the startup. */ private static int debug = 0; // ----------------------------------------------------------- Main Program /** * The main program for the bootstrap. * * @param args Command line arguments to be processed */ public static void main(String args[]) { // Set the debug flag appropriately for (int i = 0; i < args.length; i++) { if ("-debug".equals(args[i])) debug = 1; } // Configure catalina.base from catalina.home if not yet set if (System.getProperty("catalina.base") == null) System.setProperty("catalina.base", getCatalinaHome()); // Construct the class loaders we will need ClassLoader commonLoader = null; ClassLoader catalinaLoader = null; ClassLoader sharedLoader = null; try { File unpacked[] = new File[1]; File packed[] = new File[1]; File packed2[] = new File[2]; ClassLoaderFactory.setDebug(debug); unpacked[0] = new File(getCatalinaHome(), "common" + File.separator + "classes"); packed2[0] = new File(getCatalinaHome(), "common" + File.separator + "endorsed"); packed2[1] = new File(getCatalinaHome(), "common" + File.separator + "lib"); commonLoader = ClassLoaderFactory.createClassLoader(unpacked, packed2, null); unpacked[0] = new File(getCatalinaHome(), "server" + File.separator + "classes"); packed[0] = new File(getCatalinaHome(), "server" + File.separator + "lib"); catalinaLoader = ClassLoaderFactory.createClassLoader(unpacked, packed, commonLoader); unpacked[0] = new File(getCatalinaBase(), "shared" + File.separator + "classes"); packed[0] = new File(getCatalinaBase(), "shared" + File.separator + "lib"); sharedLoader = ClassLoaderFactory.createClassLoader(unpacked, packed, commonLoader); } catch (Throwable t) { log("Class loader creation threw exception", t); System.exit(1); } Thread.currentThread().setContextClassLoader(catalinaLoader); // Load our startup class and call its process() method try { SecurityClassLoad.securityClassLoad(catalinaLoader); // Instantiate a startup class instance if (debug >= 1) log("Loading startup class"); Class startupClass = catalinaLoader.loadClass ("org.apache.catalina.startup.Catalina"); Object startupInstance = startupClass.newInstance(); // Set the shared extensions class loader if (debug >= 1) log("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); // Call the process() method if (debug >= 1) log("Calling startup class process() method"); methodName = "process"; paramTypes = new Class[1]; paramTypes[0] = args.getClass(); paramValues = new Object[1]; paramValues[0] = args; method = startupInstance.getClass().getMethod(methodName, paramTypes); method.invoke(startupInstance, paramValues); } catch (Exception e) { System.out.println("Exception during startup processing"); e.printStackTrace(System.out); System.exit(2); } } /** * Get the value of the catalina.home environment variable. */ private static String getCatalinaHome() { return System.getProperty("catalina.home", System.getProperty("user.dir")); } /** * Get the value of the catalina.base environment variable. */ private static String getCatalinaBase() { return System.getProperty("catalina.base", getCatalinaHome()); } /** * Log a debugging detail message. * * @param message The message to be logged */ private static void log(String message) { System.out.print("Bootstrap: "); System.out.println(message); } /** * Log a debugging detail message with an exception. * * @param message The message to be logged * @param exception The exception to be logged */ private static void log(String message, Throwable exception) { log(message); exception.printStackTrace(System.out); } }
Bootstrap类定义了四个静态方法,其中getCatalinaHome返回catalina.home属性的值,若没有,则返回user.dir属性的值。getCatalinaBase方法与getCatalinaHome方法类似。
Bootstrap类的main方法构造了三个loader,之所以如此做是为了防止运行WEB-INF/classes和WEB-INF/lib目录外的类。
其中commonLoader允许从%CATALINA_HOME%/common/classes,%CATALINA_HOME%/common/endorsed和%CATALINA_HOME%/common/lib目录下载入类。
catalinaLoader负责载入servlet容器需要使用的类,它只会从%CATALINA_HOME%/server/classes和%CATALINA_HOME%/server/lib目录下查找。
sharedLoader会从%CATALINA_HOME%/shared/classes和%CATALJNA_HOME%/shared/lib目录,以及对commonLoader可用的目录下查找需要的类。
然后,将sharedLoader设置为每个web应用的类载入器的父类载入器。(注意,sharedLoader并不访问catalina的内部类,或CLASSPATH中的类)
在创建了三个loader之后,main方法会载入Catalina类,实例化,并将之赋值给startupInstance变量。然后调用setParentClassLoader方法。
最后,main方法调用Catalina对象的process对象。
---------------------------------------------------------------------------
本系列How Tomcat Works系本人原创
转载请注明出处 博客园 刺猬的温驯
本人邮箱: chenying998179#163.com (#改为@)