本文重點關注啟動tomcat時會用到的兩個類,分別為Catalina類和Bootstrap類,它們都位于org.apachae.catalina.startup包下;Catalina類用于啟動或關閉Server對象,并負責解析server.xml配置文件;Bootstrap類是一個入口點,負責創(chuàng)建Catalina實例,并調(diào)用其process()方法。
org.apachae.catalina.startup.Catalina類是啟動類,它包含一個Digester對象,用于解析位于%CATALINE_HOME%/conf目錄的server.xml文件
Catalina類還封裝了一個Server對象,該對象持有一個Service對象(Service對象包含一個Servlet容器和一個或多個連接器)。可以使用Catalina類來啟動/關閉Server對象
可以通過實例化Catalina類,并調(diào)用其process()方法來運行Tomcat,但在調(diào)用該方法時,需要傳入適當?shù)膮?shù)(如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); } }
上面方法會進一步調(diào)用execute()方法,execute()方法會根據(jù)傳入?yún)?shù)調(diào)用start()方法或stop()方法
start方法會創(chuàng)建一個Digester對象來解析server.xml文件(Tomcat配置文件)。在解析server.xml文件之前,start方法會調(diào)用Digester對象的push方法,傳入當前的Catalina對象為參數(shù)。這樣,Catalina對象就成了Digester對象內(nèi)部對象棧的第一個對象。解析server.xml文件后,會將變量server指向一個Server對象(默認是org.apache.catalina.core.StandardServer類型的對象)。然后,start方法會調(diào)用server的initialize和start方法。Catalina對象的start方法會調(diào)用Server對象的await方法,server對象會使用一個專用的線程來等待關閉命令。await方法會循環(huán)等待,知道接收到正確的關閉命令。當await方法返回時,Catalina對象的start方法會調(diào)用server對象的stop方法,從而關閉server對象和其他的組件。此外,start方法還會注冊shutdown hook,確保服務器關閉時會執(zhí)行Server對象的stop方法。
start方法的實現(xiàn)如下:
/** * 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對象,其實現(xiàn)如下:
/** * 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方法通過調(diào)用createStopDigester方法創(chuàng)建一個Digester對象,然后將Catalina對象push到Digester對象的內(nèi)部對象棧中。
createStartDigester方法創(chuàng)建了一個Digester對象,然后將規(guī)則添加到其中,解析server.xml文件。添加到Digester對象中的規(guī)則是理解tomcat配置的關鍵。
createStartDigester方法的實現(xiàn)如下:
/** * 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對象的內(nèi)部棧中,并與棧中的下一個對象相關聯(lián);在這里,下一個對象是Catalina實例,調(diào)用其setServer()方法與Server對象相關聯(lián)。
createStopDigester方法返回一個Digester對象來關閉Server對象。createStopDigester方法實現(xiàn)如下:
/** * 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時,實際上會調(diào)用該類的main方法。在main方法中會創(chuàng)建三個loader,并實例化Catalina對象,然后調(diào)用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類定義了四個靜態(tài)方法,其中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的內(nèi)部類,或CLASSPATH中的類)
在創(chuàng)建了三個loader之后,main方法會載入Catalina類,實例化,并將之賦值給startupInstance變量。然后調(diào)用setParentClassLoader方法。
最后,main方法調(diào)用Catalina對象的process對象。
---------------------------------------------------------------------------?
本系列How Tomcat Works系本人原創(chuàng)?
轉(zhuǎn)載請注明出處 博客園 刺猬的溫馴?
本人郵箱: ? chenying998179 # 163.com ( #改為@ )
更多文章、技術交流、商務合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
