前言:
上篇《探秘Tomcat——啟動篇》粗線條的介紹了在tomcat在啟動過程中如何初始化Bootstrap類,加載並執行server,從而啟動整個tomcat服務,一直到我們看到控制台打印出如下信息
七月 16, 2016 4:42:18 下午 org.apache.catalina.core.AprLifecycleListener init
信息: The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: C:\Program Files\Java\jdk1.8.0_60\bin;C:\Windows\Sun\Java\bin;C:\Windows\system32;C:\Windows;C:\Program Files\Java\jdk1.8.0_60\jre\bin;C:/Program Files/Java/jdk1.8.0_60/bin/../jre/bin/server;C:/Program Files/Java/jdk1.8.0_60/bin/../jre/bin;C:/Program Files/Java/jdk1.8.0_60/bin/../jre/lib/amd64;C:\Program Files\Java\jdk1.8.0_60\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\ATI Technologies\ATI.ACE\Core-Static;C:\Program Files (x86)\Intel\OpenCL SDK\2.0\bin\x86;C:\Program Files (x86)\Intel\OpenCL SDK\2.0\bin\x64;C:\Program Files (x86)\Microsoft SQL Server\100\Tools\Binn\;C:\Program Files\Microsoft SQL Server\100\Tools\Binn\;C:\Program Files\Microsoft SQL Server\100\DTS\Binn\;C:\Program Files (x86)\Microsoft SQL Server\100\Tools\Binn\VSShell\Common7\IDE\;C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE\PrivateAssemblies\;C:\Program Files (x86)\Microsoft SQL Server\100\DTS\Binn\;C:\Program Files\nodejs;E:\software\apache-maven-3.1.0-bin\apache-maven-3.1.0\bin;E:\software\gradle-2.7\bin;C:\Program Files (x86)\Git\bin;C:\Program Files (x86)\Git\cmd;C:\Users\Administrator\Desktop\博客\20160410\android\android-sdk-windows\tools;E:\software\apache-ant-1.9.7-bin\apache-ant-1.9.7\bin;C:\Users\Administrator\AppData\Roaming\npm;E:\安裝包\學習軟件\eclipse-jee-mars-1-win32-x86_64\eclipse;;.
七月 16, 2016 4:42:41 下午 org.apache.coyote.http11.Http11Protocol init
信息: Initializing Coyote HTTP/1.1 on http-8080
七月 16, 2016 4:45:01 下午 org.apache.catalina.startup.Catalina load
信息: Initialization processed in 190850 ms
七月 16, 2016 4:45:07 下午 org.apache.catalina.core.StandardService start
信息: Starting service Catalina
七月 16, 2016 4:45:07 下午 org.apache.catalina.core.StandardEngine start
信息: Starting Servlet Engine: Apache Tomcat/@VERSION@
七月 16, 2016 4:45:12 下午 org.apache.catalina.startup.HostConfig deployDescriptor
信息: Deploying configuration descriptor host-manager.xml
七月 16, 2016 4:45:17 下午 org.apache.catalina.startup.HostConfig deployDescriptor
信息: Deploying configuration descriptor manager.xml
七月 16, 2016 4:45:18 下午 org.apache.catalina.startup.HostConfig deployDirectory
信息: Deploying web application directory docs
七月 16, 2016 4:45:19 下午 org.apache.catalina.startup.HostConfig deployDirectory
信息: Deploying web application directory examples
七月 16, 2016 4:45:21 下午 org.apache.catalina.core.ApplicationContext log
信息: ContextListener: contextInitialized()
七月 16, 2016 4:45:21 下午 org.apache.catalina.core.ApplicationContext log
信息: SessionListener: contextInitialized()
七月 16, 2016 4:45:21 下午 org.apache.catalina.startup.HostConfig deployDirectory
信息: Deploying web application directory ROOT
七月 16, 2016 4:47:47 下午 org.apache.coyote.http11.Http11Protocol start
信息: Starting Coyote HTTP/1.1 on http-8080
七月 16, 2016 4:48:36 下午 org.apache.jk.common.ChannelSocket init
信息: JK: ajp13 listening on /0.0.0.0:8009
七月 16, 2016 4:48:44 下午 org.apache.jk.server.JkMain start
信息: Jk running ID=0 time=7967/16219 config=null
七月 16, 2016 4:49:07 下午 org.apache.catalina.startup.Catalina start
信息: Server startup in 243017 ms
表示tomcat服務啟動成功。
從上面的tomcat啟動過程打印信息我們可以發現,在啟動tomcat時,我們做了很多工作,包括一些類加載器的初始化,server的加載和啟動等,本篇緊接着上篇來說說
七月 16, 2016 4:47:47 下午 org.apache.coyote.http11.Http11Protocol start
信息: Starting Coyote HTTP/1.1 on http-8080
七月 16, 2016 4:48:36 下午 org.apache.jk.common.ChannelSocket init
信息: JK: ajp13 listening on /0.0.0.0:8009
七月 16, 2016 4:48:44 下午 org.apache.jk.server.JkMain start
信息: Jk running ID=0 time=7967/16219 config=null
這幾行console信息背后的故事……
正文:
我們還是從Bootstrap類的main方法說起
1 public static void main(String args[]) { 2 3 if (daemon == null) { 4 daemon = new Bootstrap(); 5 try { 6 daemon.init(); 7 } catch (Throwable t) { 8 t.printStackTrace(); 9 return; 10 } 11 } 12 13 try { 14 String command = "start"; 15 if (args.length > 0) { 16 command = args[args.length - 1]; 17 } 18 19 if (command.equals("startd")) { 20 args[args.length - 1] = "start"; 21 daemon.load(args); 22 daemon.start(); 23 } else if (command.equals("stopd")) { 24 args[args.length - 1] = "stop"; 25 daemon.stop(); 26 } else if (command.equals("start")) { 27 daemon.setAwait(true); 28 daemon.load(args); 29 daemon.start(); 30 } else if (command.equals("stop")) { 31 daemon.stopServer(args); 32 } else { 33 log.warn("Bootstrap: command \"" + command + "\" does not exist."); 34 } 35 } catch (Throwable t) { 36 t.printStackTrace(); 37 } 38 39 }
在line28~29可以看出依次執行deamon的load和start方法,而實際上這兩個方法的具體實現是通過反射機制跳轉到類Catalina中找到相應的load和start方法的。
load方法執行的是誰的load?load了那些服務組件?load的目的又是什么?
Catalina.load方法中一個很重要的方法就是createStartDigester,完成的工作是根據conf/server.xml文件中的數據,將相應的元素轉化 為對象,將元素中的屬性轉化為生成對象的屬性,並且理清楚各個元素之間的關聯關系。比如server.xml文件中最外層的元素是server,server中包含了子節點service,而在這個service里面又有很多元素節點如Connector、Engie、Host等等,這是他們之間的關系。簡單說就是先定義一個規則,好讓后面在實際解析這個xml文件的時候有章可循。
當在執行到load中的digester.parse(inputSource)方法時,會依次遍歷每個元素,當遍歷到Connector元素的時候,會依次調用Digester.startElement->Rule.begin->ConnectorCreateRule.begin.
1 public void begin(Attributes attributes) throws Exception { 2 Service svc = (Service)digester.peek(); 3 Executor ex = null; 4 if ( attributes.getValue("executor")!=null ) { 5 ex = svc.getExecutor(attributes.getValue("executor")); 6 } 7 Connector con = new Connector(attributes.getValue("protocol")); 8 if ( ex != null ) _setExecutor(con,ex); 9 10 digester.push(con); 11 }
line7獲取到server.xml中Connector的protocol屬性之后,以此傳值並創建一個Connetor對象。
備注:server.xml中有聲明了兩個Connetor元素,分別是:

1 <!-- A "Connector" represents an endpoint by which requests are received 2 and responses are returned. Documentation at : 3 Java HTTP Connector: /docs/config/http.html (blocking & non-blocking) 4 Java AJP Connector: /docs/config/ajp.html 5 APR (HTTP/AJP) Connector: /docs/apr.html 6 Define a non-SSL HTTP/1.1 Connector on port 8080 7 --> 8 <Connector port="8080" protocol="HTTP/1.1" 9 connectionTimeout="20000" 10 redirectPort="8443" /> 11 <!-- A "Connector" using the shared thread pool--> 12 <!-- 13 <Connector executor="tomcatThreadPool" 14 port="8080" protocol="HTTP/1.1" 15 connectionTimeout="20000" 16 redirectPort="8443" /> 17 --> 18 <!-- Define a SSL HTTP/1.1 Connector on port 8443 19 This connector uses the JSSE configuration, when using APR, the 20 connector should be using the OpenSSL style configuration 21 described in the APR documentation --> 22 <!-- 23 <Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true" 24 maxThreads="150" scheme="https" secure="true" 25 clientAuth="false" sslProtocol="TLS" /> 26 --> 27 28 <!-- Define an AJP 1.3 Connector on port 8009 --> 29 <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
從Connetor類的構造函數可以看出,我們首先會執行Connetor類的setProtocol方法,這時候傳入的attributs.getValue("protocol")就會派上用場。
1 public Connector(String protocol) 2 throws Exception { 3 setProtocol(protocol); 4 // Instantiate protocol handler 5 try { 6 Class clazz = Class.forName(protocolHandlerClassName); 7 this.protocolHandler = (ProtocolHandler) clazz.newInstance(); 8 } catch (Exception e) { 9 log.error 10 (sm.getString 11 ("coyoteConnector.protocolHandlerInstantiationFailed", e)); 12 } 13 }
setProtocol方法如下
1 public void setProtocol(String protocol) { 2 3 if (AprLifecycleListener.isAprAvailable()) { 4 if ("HTTP/1.1".equals(protocol)) { 5 setProtocolHandlerClassName 6 ("org.apache.coyote.http11.Http11AprProtocol"); 7 } else if ("AJP/1.3".equals(protocol)) { 8 setProtocolHandlerClassName 9 ("org.apache.coyote.ajp.AjpAprProtocol"); 10 } else if (protocol != null) { 11 setProtocolHandlerClassName(protocol); 12 } else { 13 setProtocolHandlerClassName 14 ("org.apache.coyote.http11.Http11AprProtocol"); 15 } 16 } else { 17 if ("HTTP/1.1".equals(protocol)) { 18 setProtocolHandlerClassName 19 ("org.apache.coyote.http11.Http11Protocol"); 20 } else if ("AJP/1.3".equals(protocol)) { 21 setProtocolHandlerClassName 22 ("org.apache.jk.server.JkCoyoteHandler"); 23 } else if (protocol != null) { 24 setProtocolHandlerClassName(protocol); 25 } 26 } 27 28 }
這里首先遍歷到的server.xml中的Connector元素是protocol="HTTP/1.1",這時候將org.apache.coyote.http11.Http11Protocol賦值給Connetor的protocolHandlerClassName變量,之后在Connetor構造函數中完成以當前的protocolHandlerClassName值構造一個org.apache.coyote.http11.Http11Protocol對象,並賦值於Connetor的protocolHandler變量。在Http11Protocol類中我們可以發現其中的構造函數和聲明的fields如下:

1 // ------------------------------------------------------------ Constructor 2 3 public Http11Protocol() { 4 setSoLinger(Constants.DEFAULT_CONNECTION_LINGER); 5 setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT); 6 //setServerSoTimeout(Constants.DEFAULT_SERVER_SOCKET_TIMEOUT); 7 setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY); 8 } 9 10 11 // ----------------------------------------------------------------- Fields 12 13 protected Http11ConnectionHandler cHandler = new Http11ConnectionHandler(this); 14 protected JIoEndpoint endpoint = new JIoEndpoint();
這里初始化主要用於創建serviceSocket對象
這里的protocolHandler.init()會根據當前的protocolHandler的對象調用相應類的init方法,比如對於Http11Protocol,則會調用Http11Protocol中的init方法,而Http11Protocol.init又會調用endpiont.init方法,endpiont.init的具體實現在JIoEndpoint的init方法中,如下:
1 public void init() 2 throws Exception { 3 4 if (initialized) 5 return; 6 7 // Initialize thread count defaults for acceptor 8 if (acceptorThreadCount == 0) { 9 acceptorThreadCount = 1; 10 } 11 if (serverSocketFactory == null) { 12 serverSocketFactory = ServerSocketFactory.getDefault(); 13 } 14 if (serverSocket == null) { 15 try { 16 if (address == null) { 17 serverSocket = serverSocketFactory.createSocket(port, backlog); 18 } else { 19 serverSocket = serverSocketFactory.createSocket(port, backlog, address); 20 } 21 } catch (BindException orig) { 22 String msg; 23 if (address == null) 24 msg = orig.getMessage() + " <null>:" + port; 25 else 26 msg = orig.getMessage() + " " + 27 address.toString() + ":" + port; 28 BindException be = new BindException(msg); 29 be.initCause(orig); 30 throw be; 31 } 32 } 33 //if( serverTimeout >= 0 ) 34 // serverSocket.setSoTimeout( serverTimeout ); 35 36 initialized = true; 37 38 }
line17創建了serverSocket對象(這里的調用關系比較深,要結合代碼和debug來看)。
當Http11Protocol.init方法執行完后,console會打印如下信息:
七月 16, 2016 7:03:06 下午 org.apache.coyote.http11.Http11Protocol init
信息: Initializing Coyote HTTP/1.1 on http-8080
之后同理解析到"AJP/1.3"並生成JkCoyoteHandler對象並完成初始化的過程。
至此,就執行完成了load的所有工作。
start方法又是誰的start?誰為start提供了如此便捷的實現?start又啟動了那些服務組件?
下面就開始執行我們的start方法,也就是Catalina.start。

1 public void start() { 2 3 if (getServer() == null) { 4 load(); 5 } 6 7 if (getServer() == null) { 8 log.fatal("Cannot start server. Server instance is not configured."); 9 return; 10 } 11 12 long t1 = System.nanoTime(); 13 14 // Start the new server 15 if (getServer() instanceof Lifecycle) { 16 try { 17 ((Lifecycle) getServer()).start(); 18 } catch (LifecycleException e) { 19 log.error("Catalina.start: ", e); 20 } 21 } 22 23 long t2 = System.nanoTime(); 24 if(log.isInfoEnabled()) 25 log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms"); 26 27 try { 28 // Register shutdown hook 29 if (useShutdownHook) { 30 if (shutdownHook == null) { 31 shutdownHook = new CatalinaShutdownHook(); 32 } 33 Runtime.getRuntime().addShutdownHook(shutdownHook); 34 35 // If JULI is being used, disable JULI's shutdown hook since 36 // shutdown hooks run in parallel and log messages may be lost 37 // if JULI's hook completes before the CatalinaShutdownHook() 38 LogManager logManager = LogManager.getLogManager(); 39 if (logManager instanceof ClassLoaderLogManager) { 40 ((ClassLoaderLogManager) logManager).setUseShutdownHook( 41 false); 42 } 43 } 44 } catch (Throwable t) { 45 // This will fail on JDK 1.2. Ignoring, as Tomcat can run 46 // fine without the shutdown hook. 47 } 48 49 if (await) { 50 await(); 51 stop(); 52 } 53 54 }
首先執行到((Lifecycle) getServer()).start()的時候會進入StandarServer執行start方法。
1 public void start() throws LifecycleException { 2 3 // Validate and update our current component state 4 if (started) { 5 log.debug(sm.getString("standardServer.start.started")); 6 return; 7 } 8 9 // Notify our interested LifecycleListeners 10 lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null); 11 12 lifecycle.fireLifecycleEvent(START_EVENT, null); 13 started = true; 14 15 // Start our defined Services 16 synchronized (services) { 17 for (int i = 0; i < services.length; i++) { 18 if (services[i] instanceof Lifecycle) 19 ((Lifecycle) services[i]).start(); 20 } 21 } 22 23 // Notify our interested LifecycleListeners 24 lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null); 25 26 }
- 該方法喚醒所有LifecycleListeners,具體實現在LifeCycleSupport.fireLifecycleEvent中,包括NamingContextListener、AprLifecycleListener、JasperListener、JreMemoryLeakPreventionListener、ServerLifecycleListener和GlobalResourcesLifecycleListener。
- 通過循環遍歷,啟動所有的serivces。這里我們看看StandardService的start方法實現:
1 public void start() throws LifecycleException { 2 3 // Validate and update our current component state 4 if (started) { 5 if (log.isInfoEnabled()) { 6 log.info(sm.getString("standardService.start.started")); 7 } 8 return; 9 } 10 11 if( ! initialized ) 12 init(); 13 14 // Notify our interested LifecycleListeners 15 lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null); 16 if(log.isInfoEnabled()) 17 log.info(sm.getString("standardService.start.name", this.name)); 18 lifecycle.fireLifecycleEvent(START_EVENT, null); 19 started = true; 20 21 // Start our defined Container first 22 if (container != null) { 23 synchronized (container) { 24 if (container instanceof Lifecycle) { 25 ((Lifecycle) container).start(); 26 } 27 } 28 } 29 30 synchronized (executors) { 31 for ( int i=0; i<executors.size(); i++ ) { 32 executors.get(i).start(); 33 } 34 } 35 36 // Start our defined Connectors second 37 synchronized (connectors) { 38 for (int i = 0; i < connectors.length; i++) { 39 try { 40 ((Lifecycle) connectors[i]).start(); 41 } catch (Exception e) { 42 log.error(sm.getString( 43 "standardService.connector.startFailed", 44 connectors[i]), e); 45 } 46 } 47 } 48 49 // Notify our interested LifecycleListeners 50 lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null); 51 52 }
- line21~28用於遞歸啟動Containers,大致的調用層次為:大致為Server.start->Service.start->StandarEngine.start->StandardHost.start->StandardPipeline.start
- line36~47用於啟動Connetors,即如下圖所示的兩個connetors:
這里對於Http11Protocol的調用順序是StandardService.start->Connetor.start->Http11Protocol.start->JIoEndpoint.start,啟動成功后在console得到打印信息:
1 七月 16, 2016 7:30:50 下午 org.apache.coyote.http11.Http11Protocol start 2 信息: Starting Coyote HTTP/1.1 on http-8080
對於JkCoyoteHandler調用順序是StandardService.start->Connetor.start->JkCoyoteHandler.start->JkMain.start,啟動成功后在console得到打印信息:
1 七月 16, 2016 7:36:00 下午 org.apache.jk.common.ChannelSocket init 2 信息: JK: ajp13 listening on /0.0.0.0:8009 3 七月 16, 2016 7:36:16 下午 org.apache.jk.server.JkMain start 4 信息: Jk running ID=0 time=33100/45405 config=null
至此,我們算是理清楚了,如何從一個server的load和start能夠把所有的services啟動,以及service中的Connetor和Container啟動起來的。
其實讀tomcat的代碼還是很費勁的,主要的自己的功力還比較淺,其中用到的一些框架技術或者設計模式不能完全理解,所以閱讀過程中會經常卡住,但是從這塊啟動來看,主要的脈絡還是看明白了,讀完之后體會還是蠻深刻:
-
- 為什么tomcat能夠做到啟動一個server就能夠把存在其上面的serveices都啟動,我想這應該是得益於LifeCycle機制,正如上篇所說,所有的組件都實現了LifeCycle的接口,說白了這就是java的面向接口編程的思想的應用,每個組件都實現了LifeCycle接口,而這個接口中具有了start方法,從而可以通過遞歸調用實現牽一發而動全身的效果;
- 我們對於Connetor和Container的初始化和啟動的所有信息都是來源於配置文件,我們把這些可以靈活配置的信息放到了server.xml文件中,這樣下次如果我們想換個端口就可以直接改在文件中,而不需要動代碼,這也是降低了代碼的耦合性;
當然了,源碼中的奧妙肯定遠不止於此,還需要慢慢研讀^_^,最近有研究tomcat源碼的可以一起交流,畢竟一個人能看到的還是蠻有限的。
如果您覺得閱讀本文對您有幫助,請點一下“推薦”按鈕,您的“推薦”將是我最大的寫作動力!如果您想持續關注我的文章,請掃描二維碼,關注JackieZheng的微信公眾號,我會將我的文章推送給您,並和您一起分享我日常閱讀過的優質文章。
友情贊助
如果你覺得博主的文章對你那么一點小幫助,恰巧你又有想打賞博主的小沖動,那么事不宜遲,趕緊掃一掃,小額地贊助下,攢個奶粉錢,也是讓博主有動力繼續努力,寫出更好的文章^^。
1. 支付寶 2. 微信