本文重点关注启动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 ( #改为@ )