作為java 開發者,從開始學習java 便知道tomcat 這個容器了,但是一直卻沒有怎么研究過它的內部結構,以前對tomcat的認識也僅僅局限在那幾個常用的目錄放什么東西,那幾個常用的配置文件應該寫說明內容,卻很少研究其內部的組件以及啟動過程,另外,去網上找相關的資料博客,也發現不是很多很全面,所以這幾天特意了解了下tomcat 的內部工作的原理,簡單總結了tomcat比較核心的一些組件,僅供學習交流,今天這篇博客主要是研究下tomcat 的大體組件有什么以及它們的啟動過程,后面會繼續總結tomcat 處理請求的過程。下面是本篇博客的題綱:
1、tomcat 主要組件
2、tomcat 啟動過程
3、從tomcat 中得到的編程啟迪
一、tomcat主要組件
下面,先簡單介紹下tomcat的主要組件,讓各位讀者初步認識tomcat的組織結構。
一直覺得java的抽象建模能力超6,而在研究優秀開源框架的時候,我們也會感到作者將這種語言的建模能力發揮到了極致,tomcat 的組織結構正是這樣一個非常生動的例子,它非常巧妙地地將服務器處理的過程抽象成一個個類。
先簡單粗略說說常規web應用客戶請求與服務器響應的整個過程吧:客戶發起request-->服務器收到request-->服務器調用服務應用-->業務處理-->返回結果。在這個過程,tomcat把各個涉及到的實體抽象為一個個對象,下圖是tomcat 的大概組織結構圖:
tomcat內部是如何抽象類的呢?由上面的圖片大概知道,tomcat 把處理請求的的過程分別抽象為如下的類(接口):server -->對應容器本身,代表一個tomcat容器;service-->服務,代表容器下可以提供的服務,service 可以簡單理解為獨立的一個提供服務的項目,tomcat 支持同時運行多個服務,所以一個server 可以有多個service;Connector 和Container 共同構成Service的核心組件;Connector是tomcat的對外連接器,主要負責處理連接相關,它實現了http協議,把數據封裝好為request 對象和response 對象;而Container 是管理容器,它主要負責容器tomcat內部各種servlet。
上面的圖片說的是大多tomcat的核心組件,他們主要負責處理客戶端請求並返回結果,姑且稱他們為工作組件;另外,還有一些組件,他們主要負責管理這些工作組件的創建、銷毀等管理工作,姑且稱他們為控制組件,這些組件在tomcat里面主要有以下三個:前面提到的代表容器本身的server,server控制了所有工作組件的啟動、停止工作;的Catalina以及Catalina的一個適配器Bootstrap,Catalina 主要是用來啟動tomcat 的server,它是tomcat的總開關,而Bootstrap又控制着Catalina,即用戶點擊startup的啟動程序時,實際上是調用Bootstrap來啟動tomcat的,至於控制類組件中為什么要這么蛋疼硬是分出這么多類(接口)來,后面會說到。
總的來說,tomcat 中組件他們之間的控制關系(注意,下圖說是控制關系,不是調用關系或者繼承關系)大概如下,總的來說有這樣一種關系,底層的組件的開關由上一層控制。
大概了解了tomcat的組織結構之后,下面總結下tomcat的啟動/初始化(銷毀)機制即tomcat 管理組件生命周期的主要方法。
二、tomcat如何管理組件生命周期
下面總結下tomcat是如何管理內部組件的聲明周期的,主要分以下部分進行總結:一是三個核心控制類的組件如何控制啟動;二是tomcat控制聲明周期的核心接口LifeCycle的講解,下面我就開車了,各位趕緊上車了。
1、BootStrap 的啟動過程。
bootStrap 相當於一個Adaptor 即適配器,它通過調用Catalina 來進行tomcat 容器的啟動,其實,Bootstrap 中的main方法就是整個容器的執行入口處,它的源碼也不難看懂,如下(代碼太長折疊了):

public static void main(String args[]) { if (daemon == null) { // Don't set daemon until init() has completed Bootstrap bootstrap = new Bootstrap(); try { bootstrap.init(); } catch (Throwable t) { handleThrowable(t); t.printStackTrace(); return; } daemon = bootstrap; } else { // When running as a service the call to stop will be on a new // thread so make sure the correct class loader is used to prevent // a range of class not found exceptions. Thread.currentThread().setContextClassLoader(daemon.catalinaLoader); } try { String command = "start"; if (args.length > 0) { command = args[args.length - 1]; } if (command.equals("startd")) { args[args.length - 1] = "start"; daemon.load(args); daemon.start(); } else if (command.equals("stopd")) { args[args.length - 1] = "stop"; daemon.stop(); } else if (command.equals("start")) { daemon.setAwait(true); daemon.load(args); daemon.start(); } else if (command.equals("stop")) { daemon.stopServer(args); } else if (command.equals("configtest")) { daemon.load(args); if (null==daemon.getServer()) { System.exit(1); } System.exit(0); } else { log.warn("Bootstrap: command \"" + command + "\" does not exist."); } } catch (Throwable t) { // Unwrap the Exception for clearer error reporting if (t instanceof InvocationTargetException && t.getCause() != null) { t = t.getCause(); } handleThrowable(t); t.printStackTrace(); System.exit(1); } }
總的來說,Bootstrap main方法啟動時,會新建一個Bootstrap對象,然后調用該對象的init方法,這個方法會獲取Catalina 的ClassLoder,然后利用反射進行Catalina 對象的創建,創建完對象之后,便會通過傳進來的args[]的參數,調用Catalina對象進行tomcat的start 或者stop等操作。
由代碼可以看到,start 操作時,Bootstrap 會調用Catalina對象的三個方法:setAwait 、load、以及start方法,Catalina的三個方法解釋如下:setAwait的方法的設置使得Catalina 在啟動Server的時候,Server一直在一個循環中執行wait操作等待請求的到來,load則是加載tomcat啟動必須的數據(例如配置文件等等),最后start 方法則是正真調用Server的啟動方法。
下面我們再看看Catalina又是如何調用Server的。
2、Catalina的啟動過程
上面總結Bootstrap 啟動過程式,有提到Catalina的三個方法:setAwait 、load、以及start,那么,這三個方法在啟動的時候,又是如何工作的呢?首先看下setAwait的源碼是怎么工作的,如下:
public void setAwait(boolean b) { await = b; }
好吧,其實它就是設置一個標記量,而這個標記量,主要用在start方法中,我們看在start 方法中,await 有什么用,下面是start 的部分代碼:
if (await) { await(); stop();
}
這段代碼是在start方法最后的,所以可以知道,setAwait 的作用就是使得執行完start 方法之后,調用本身的await 方法,而查看下面的源碼可知,await 方法的作用就是調用server(getServer 返回server對象)的await 方法。而其實在server中,await方法的作用就是在tomcat 啟動完成之后,處於一種等待請求狀態。
public void await() { getServer().await(); }
然后,我們再看load方法又做了些什么工作,還是自己看源碼(代碼可能有點長,所以我折起來了):

public void load() { long t1 = System.nanoTime(); initDirs(); // Before digester - it may be needed initNaming(); // Create and execute our Digester Digester digester = createStartDigester(); InputSource inputSource = null; InputStream inputStream = null; File file = null; try { try { file = configFile(); inputStream = new FileInputStream(file); inputSource = new InputSource(file.toURI().toURL().toString()); } catch (Exception e) { if (log.isDebugEnabled()) { log.debug(sm.getString("catalina.configFail", file), e); } } if (inputStream == null) { try { inputStream = getClass().getClassLoader() .getResourceAsStream(getConfigFile()); inputSource = new InputSource (getClass().getClassLoader() .getResource(getConfigFile()).toString()); } catch (Exception e) { if (log.isDebugEnabled()) { log.debug(sm.getString("catalina.configFail", getConfigFile()), e); } } } // This should be included in catalina.jar // Alternative: don't bother with xml, just create it manually. if( inputStream==null ) { try { inputStream = getClass().getClassLoader() .getResourceAsStream("server-embed.xml"); inputSource = new InputSource (getClass().getClassLoader() .getResource("server-embed.xml").toString()); } catch (Exception e) { if (log.isDebugEnabled()) { log.debug(sm.getString("catalina.configFail", "server-embed.xml"), e); } } } if (inputStream == null || inputSource == null) { if (file == null) { log.warn(sm.getString("catalina.configFail", getConfigFile() + "] or [server-embed.xml]")); } else { log.warn(sm.getString("catalina.configFail", file.getAbsolutePath())); if (file.exists() && !file.canRead()) { log.warn("Permissions incorrect, read permission is not allowed on the file."); } } return; } try { inputSource.setByteStream(inputStream); digester.push(this); digester.parse(inputSource); } catch (SAXParseException spe) { log.warn("Catalina.start using " + getConfigFile() + ": " + spe.getMessage()); return; } catch (Exception e) { log.warn("Catalina.start using " + getConfigFile() + ": " , e); return; } } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { // Ignore } } } getServer().setCatalina(this); // Stream redirection initStreams(); // Start the new server try { getServer().init(); } catch (LifecycleException e) { if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) { throw new java.lang.Error(e); } else { log.error("Catalina.start", e); } } long t2 = System.nanoTime(); if(log.isInfoEnabled()) { log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms"); } }
上面代碼,我們可以看到,load 其實就是分為以下幾個步驟:首先讀取各種啟動所需要的配置文件(context.xml/server.xml等),讀取完之后,創建server對象,當創建server 完成之后,會調用server 的init 方法進行容器的進一步初始化工作,至於init 方法,后面總結server 的時候再詳細總結。
最后的start 方法,其實就是開啟應用了,下面是start 方法的源碼,由於太長,也折起來了,需要查看請自己展開:

public void start() { if (getServer() == null) { load(); } if (getServer() == null) { log.fatal("Cannot start server. Server instance is not configured."); return; } long t1 = System.nanoTime(); // Start the new server try { getServer().start(); } catch (LifecycleException e) { log.fatal(sm.getString("catalina.serverStartFail"), e); try { getServer().destroy(); } catch (LifecycleException e1) { log.debug("destroy() failed for failed Server ", e1); } return; } long t2 = System.nanoTime(); if(log.isInfoEnabled()) { log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms"); } // Register shutdown hook if (useShutdownHook) { if (shutdownHook == null) { shutdownHook = new CatalinaShutdownHook(); } Runtime.getRuntime().addShutdownHook(shutdownHook); // If JULI is being used, disable JULI's shutdown hook since // shutdown hooks run in parallel and log messages may be lost // if JULI's hook completes before the CatalinaShutdownHook() LogManager logManager = LogManager.getLogManager(); if (logManager instanceof ClassLoaderLogManager) { ((ClassLoaderLogManager) logManager).setUseShutdownHook( false); } } if (await) { await(); stop(); } }
start 方法其實主要的步驟就是調用server 的start方法進行容器的開啟,相信讀者看源碼也不難理解,這里不累贅說了。
3、server 的啟動過程
server 是 tomcat 控制類組件的核心組件,它控制着tomcat service 的啟動和關閉,從而達到控制容器開關的目的。server 實際上以一個接口,在tomcat 中,默認實現了這個接口的類是 StandardServer ,在這個server 中,關於啟動階段,主要有兩個方法:initInternal 和startInternal ,兩個方法都是分別調用所有service 的init方法和start 方法,具體可以查看下面的源代碼:
這個是StandrfServer 的initInternal 的源碼:

protected void initInternal() throws LifecycleException { super.initInternal(); // Register global String cache // Note although the cache is global, if there are multiple Servers // present in the JVM (may happen when embedding) then the same cache // will be registered under multiple names onameStringCache = register(new StringCache(), "type=StringCache"); // Register the MBeanFactory MBeanFactory factory = new MBeanFactory(); factory.setContainer(this); onameMBeanFactory = register(factory, "type=MBeanFactory"); // Register the naming resources globalNamingResources.init(); // Populate the extension validator with JARs from common and shared // class loaders if (getCatalina() != null) { ClassLoader cl = getCatalina().getParentClassLoader(); // Walk the class loader hierarchy. Stop at the system class loader. // This will add the shared (if present) and common class loaders while (cl != null && cl != ClassLoader.getSystemClassLoader()) { if (cl instanceof URLClassLoader) { URL[] urls = ((URLClassLoader) cl).getURLs(); for (URL url : urls) { if (url.getProtocol().equals("file")) { try { File f = new File (url.toURI()); if (f.isFile() && f.getName().endsWith(".jar")) { ExtensionValidator.addSystemResource(f); } } catch (URISyntaxException e) { // Ignore } catch (IOException e) { // Ignore } } } } cl = cl.getParent(); } } // Initialize our defined Services for (int i = 0; i < services.length; i++) { services[i].init(); } }
可以看到,在源碼里面,initInternal 依次調用了 Service 的init方法,而startInternal 也是類似的,不累贅講了,具體有興趣的讀者可以展開下面的源碼查看:

protected void startInternal() throws LifecycleException { fireLifecycleEvent(CONFIGURE_START_EVENT, null); setState(LifecycleState.STARTING); globalNamingResources.start(); // Start our defined Services synchronized (servicesLock) { for (int i = 0; i < services.length; i++) { services[i].start(); } } }
startInternal 和 initInternal的方法都較為簡單,下面重點研究下Server 的await 方法,也是上面被Catalina 調用的方法,具體請先看一下await 的源碼(代碼過長先折疊起來了):

public void await() { // Negative values - don't wait on port - tomcat is embedded or we just don't like ports if( port == -2 ) { // undocumented yet - for embedding apps that are around, alive. return; } if( port==-1 ) { try { awaitThread = Thread.currentThread(); while(!stopAwait) { try { Thread.sleep( 10000 ); } catch( InterruptedException ex ) { // continue and check the flag } } } finally { awaitThread = null; } return; } // Set up a server socket to wait on try { awaitSocket = new ServerSocket(port, 1, InetAddress.getByName(address)); } catch (IOException e) { log.error("StandardServer.await: create[" + address + ":" + port + "]: ", e); return; } try { awaitThread = Thread.currentThread(); // Loop waiting for a connection and a valid command while (!stopAwait) { ServerSocket serverSocket = awaitSocket; if (serverSocket == null) { break; } // Wait for the next connection Socket socket = null; StringBuilder command = new StringBuilder(); try { InputStream stream; long acceptStartTime = System.currentTimeMillis(); try { socket = serverSocket.accept(); socket.setSoTimeout(10 * 1000); // Ten seconds stream = socket.getInputStream(); } catch (SocketTimeoutException ste) { // This should never happen but bug 56684 suggests that // it does. log.warn(sm.getString("standardServer.accept.timeout", Long.valueOf(System.currentTimeMillis() - acceptStartTime)), ste); continue; } catch (AccessControlException ace) { log.warn("StandardServer.accept security exception: " + ace.getMessage(), ace); continue; } catch (IOException e) { if (stopAwait) { // Wait was aborted with socket.close() break; } log.error("StandardServer.await: accept: ", e); break; } // Read a set of characters from the socket int expected = 1024; // Cut off to avoid DoS attack while (expected < shutdown.length()) { if (random == null) random = new Random(); expected += (random.nextInt() % 1024); } while (expected > 0) { int ch = -1; try { ch = stream.read(); } catch (IOException e) { log.warn("StandardServer.await: read: ", e); ch = -1; } // Control character or EOF (-1) terminates loop if (ch < 32 || ch == 127) { break; } command.append((char) ch); expected--; } } finally { // Close the socket now that we are done with it try { if (socket != null) { socket.close(); } } catch (IOException e) { // Ignore } } // Match against our command string boolean match = command.toString().equals(shutdown); if (match) { log.info(sm.getString("standardServer.shutdownViaPort")); break; } else log.warn("StandardServer.await: Invalid command '" + command.toString() + "' received"); } } finally { ServerSocket serverSocket = awaitSocket; awaitThread = null; awaitSocket = null; // Close the server socket and return if (serverSocket != null) { try { serverSocket.close(); } catch (IOException e) { // Ignore } } } }
這個方法可以看出,該方法的作用就是監聽關閉端口,在接受到請求的時候進行持續的關閉操作,如果端口號是-1,代表不能從外部關閉應用,如果是-2 直接退出;而后面的代碼是我們很熟悉的Socket 編程,主要是監聽對應的關閉端口,如果接受到對應的關閉指令,則會關閉應用。
4、Service 的啟動過程。
Service 是代表向外提供的服務,它其實也是一個接口,在tomcat中有一個標准實現StandardService,下面我們就看看這個StandardService的啟動部分的代碼。由上面的server 啟動過程可知,Service 的啟動過程主要是init方法以及start 方法,但是,如果讀者細心閱讀StandardService 的源碼的話,會發現找不到對應service 的init 方法,只是找到了initInternal 方法,其實這時我們大概就可以猜到:StandardService 應該是有父類的,init 方法應該是在父類中,查看StandardService父類LifecycleBase(StandardService 繼承了LifecycleMBeanBase ,LifecycleMBeanBase 繼承了LifecycleBase,而init 方法是在LifecycleBase中的)還真發現有這樣一個方法,同時,我們可以看到,父類的init 方法還調用了一個方法initInternal ,只是這個initInternal 什么都不干,純粹是個模板方法,它由子類(也就是這里的StandardService 實現),這個模板方法的手段在很多開源框架里面都可以看到,也是我們研究開源框架要學習的精粹之一。下面粘上LifecycleBase的源碼:

public final synchronized void init() throws LifecycleException { if (!state.equals(LifecycleState.NEW)) { invalidTransition(Lifecycle.BEFORE_INIT_EVENT); } try { setStateInternal(LifecycleState.INITIALIZING, null, false); initInternal(); setStateInternal(LifecycleState.INITIALIZED, null, false); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); setStateInternal(LifecycleState.FAILED, null, false); throw new LifecycleException( sm.getString("lifecycleBase.initFail",toString()), t); } }
所以,由上面的代碼可知,我們要研究Service 的啟動過程,其實主要就是研究StandardService中的initInternal 以及startInternal 方法即可,下面我們研究下這兩個方法到底干了什么。
首先,我們先看看initInternal的源碼:

protected void initInternal() throws LifecycleException { super.initInternal(); if (container != null) { container.init(); } // Initialize any Executors for (Executor executor : findExecutors()) { if (executor instanceof LifecycleMBeanBase) { ((LifecycleMBeanBase) executor).setDomain(getDomain()); } executor.init(); } // Initialize our defined Connectors synchronized (connectorsLock) { for (Connector connector : connectors) { try { connector.init(); } catch (Exception e) { String message = sm.getString( "standardService.connector.initFailed", connector); log.error(message, e); if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) throw new LifecycleException(message); } } } }
由其源碼可以看到,其實initInternal 主要做了兩件事情:一個是調用Container 的init 方法,另外一個是調用了connectors 的init方法,初始化了所有的connectors類。Container 和Connectors 的作用上面也大概提到了下,兩者分別對應的是Connectors 處理外部請求,Container 則是調用Servlet ,是內部業務的入口,更詳細的后面再展開了。
然后,我們再看看startInternal 的源碼(其實估計我們不用看源碼都大概可以猜到它要干什么了):

protected void startInternal() throws LifecycleException { if(log.isInfoEnabled()) log.info(sm.getString("standardService.start.name", this.name)); setState(LifecycleState.STARTING); // Start our defined Container first if (container != null) { synchronized (container) { container.start(); } } synchronized (executors) { for (Executor executor: executors) { executor.start(); } } // Start our defined Connectors second synchronized (connectorsLock) { for (Connector connector: connectors) { try { // If it has already failed, don't try and start it if (connector.getState() != LifecycleState.FAILED) { connector.start(); } } catch (Exception e) { log.error(sm.getString( "standardService.connector.startFailed", connector), e); } } } }
沒猜錯,startInternal 其實也是調用Connectors 和Container 的對應start 方法來啟動Connectors 和Container ,但是,我們還發現它調用了Executor的start 方法,其實這個Executor是對應Connectors 中管理線程的線程池,關於Connectors 的工作機制,后面再詳細進行講解。
總的來說,Service 的啟動過程也不復雜,就是對應的調用了Container 和Connectors 的init 方法和start 方法進行初始化和開啟動作。
5、tomcat 如何管理組件的生命周期?
由上面對幾個控制組件(BootStrap,Catalina 以及Server)的啟動過程,我們也大概可以知道,tomcat 的啟動流程是怎樣的了,那么,tomcat 又是如何控制組件的生命周期的呢?例如說,我要知道某個組件的狀態或者我想關閉tomcat ,這時候,組件是怎么工作的呢?其實,我們會發現,無論是StandardService 還是StandardServer ,它都有一個共同的父類--LifecycleMBeanBase,而LifecycleMBeanBase又繼承了LifecycleBase,LifecycleBase實現了一個接口:Lifecycle,他們之家你的關系如下圖(手動畫的圖可能會丑了點,各位將就看下,當然,也不太規范,因為傳說中,實現接口應該是用虛線的但是我在win畫圖板找了好久沒找到虛線就作罷了):
其實tomcat 就是通過LifeCycle 這個接口進行組件聲明周期的控制的,下面就研究下LifeCycle這個接口,直接拷貝源碼上來了:

/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.catalina; /** * Common interface for component life cycle methods. Catalina components * may implement this interface (as well as the appropriate interface(s) for * the functionality they support) in order to provide a consistent mechanism * to start and stop the component. * <br> * The valid state transitions for components that support {@link Lifecycle} * are: * <pre> * start() * ----------------------------- * | | * | init() | * NEW -»-- INITIALIZING | * | | | | ------------------«----------------------- * | | |auto | | | * | | \|/ start() \|/ \|/ auto auto stop() | * | | INITIALIZED --»-- STARTING_PREP --»- STARTING --»- STARTED --»--- | * | | | | | * | |destroy()| | | * | --»-----«-- ------------------------«-------------------------------- ^ * | | | | * | | \|/ auto auto start() | * | | STOPPING_PREP ----»---- STOPPING ------»----- STOPPED -----»----- * | \|/ ^ | ^ * | | stop() | | | * | | -------------------------- | | * | | | | | * | | | destroy() destroy() | | * | | FAILED ----»------ DESTROYING ---«----------------- | * | | ^ | | * | | destroy() | |auto | * | --------»----------------- \|/ | * | DESTROYED | * | | * | stop() | * ---»------------------------------»------------------------------ * * Any state can transition to FAILED. * * Calling start() while a component is in states STARTING_PREP, STARTING or * STARTED has no effect. * * Calling start() while a component is in state NEW will cause init() to be * called immediately after the start() method is entered. * * Calling stop() while a component is in states STOPPING_PREP, STOPPING or * STOPPED has no effect. * * Calling stop() while a component is in state NEW transitions the component * to STOPPED. This is typically encountered when a component fails to start and * does not start all its sub-components. When the component is stopped, it will * try to stop all sub-components - even those it didn't start. * * Attempting any other transition will throw {@link LifecycleException}. * * </pre> * The {@link LifecycleEvent}s fired during state changes are defined in the * methods that trigger the changed. No {@link LifecycleEvent}s are fired if the * attempted transition is not valid. * * @author Craig R. McClanahan */ public interface Lifecycle { // ----------------------------------------------------- Manifest Constants /** * The LifecycleEvent type for the "component after init" event. */ public static final String BEFORE_INIT_EVENT = "before_init"; /** * The LifecycleEvent type for the "component after init" event. */ public static final String AFTER_INIT_EVENT = "after_init"; /** * The LifecycleEvent type for the "component start" event. */ public static final String START_EVENT = "start"; /** * The LifecycleEvent type for the "component before start" event. */ public static final String BEFORE_START_EVENT = "before_start"; /** * The LifecycleEvent type for the "component after start" event. */ public static final String AFTER_START_EVENT = "after_start"; /** * The LifecycleEvent type for the "component stop" event. */ public static final String STOP_EVENT = "stop"; /** * The LifecycleEvent type for the "component before stop" event. */ public static final String BEFORE_STOP_EVENT = "before_stop"; /** * The LifecycleEvent type for the "component after stop" event. */ public static final String AFTER_STOP_EVENT = "after_stop"; /** * The LifecycleEvent type for the "component after destroy" event. */ public static final String AFTER_DESTROY_EVENT = "after_destroy"; /** * The LifecycleEvent type for the "component before destroy" event. */ public static final String BEFORE_DESTROY_EVENT = "before_destroy"; /** * The LifecycleEvent type for the "periodic" event. */ public static final String PERIODIC_EVENT = "periodic"; /** * The LifecycleEvent type for the "configure_start" event. Used by those * components that use a separate component to perform configuration and * need to signal when configuration should be performed - usually after * {@link #BEFORE_START_EVENT} and before {@link #START_EVENT}. */ public static final String CONFIGURE_START_EVENT = "configure_start"; /** * The LifecycleEvent type for the "configure_stop" event. Used by those * components that use a separate component to perform configuration and * need to signal when de-configuration should be performed - usually after * {@link #STOP_EVENT} and before {@link #AFTER_STOP_EVENT}. */ public static final String CONFIGURE_STOP_EVENT = "configure_stop"; // --------------------------------------------------------- Public Methods /** * Add a LifecycleEvent listener to this component. * * @param listener The listener to add */ public void addLifecycleListener(LifecycleListener listener); /** * Get the life cycle listeners associated with this life cycle. If this * component has no listeners registered, a zero-length array is returned. */ public LifecycleListener[] findLifecycleListeners(); /** * Remove a LifecycleEvent listener from this component. * * @param listener The listener to remove */ public void removeLifecycleListener(LifecycleListener listener); /** * Prepare the component for starting. This method should perform any * initialization required post object creation. The following * {@link LifecycleEvent}s will be fired in the following order: * <ol> * <li>INIT_EVENT: On the successful completion of component * initialization.</li> * </ol> * * @exception LifecycleException if this component detects a fatal error * that prevents this component from being used */ public void init() throws LifecycleException; /** * Prepare for the beginning of active use of the public methods other than * property getters/setters and life cycle methods of this component. This * method should be called before any of the public methods other than * property getters/setters and life cycle methods of this component are * utilized. The following {@link LifecycleEvent}s will be fired in the * following order: * <ol> * <li>BEFORE_START_EVENT: At the beginning of the method. It is as this * point the state transitions to * {@link LifecycleState#STARTING_PREP}.</li> * <li>START_EVENT: During the method once it is safe to call start() for * any child components. It is at this point that the * state transitions to {@link LifecycleState#STARTING} * and that the public methods other than property * getters/setters and life cycle methods may be * used.</li> * <li>AFTER_START_EVENT: At the end of the method, immediately before it * returns. It is at this point that the state * transitions to {@link LifecycleState#STARTED}. * </li> * </ol> * * @exception LifecycleException if this component detects a fatal error * that prevents this component from being used */ public void start() throws LifecycleException; /** * Gracefully terminate the active use of the public methods other than * property getters/setters and life cycle methods of this component. Once * the STOP_EVENT is fired, the public methods other than property * getters/setters and life cycle methods should not be used. The following * {@link LifecycleEvent}s will be fired in the following order: * <ol> * <li>BEFORE_STOP_EVENT: At the beginning of the method. It is at this * point that the state transitions to * {@link LifecycleState#STOPPING_PREP}.</li> * <li>STOP_EVENT: During the method once it is safe to call stop() for * any child components. It is at this point that the * state transitions to {@link LifecycleState#STOPPING} * and that the public methods other than property * getters/setters and life cycle methods may no longer be * used.</li> * <li>AFTER_STOP_EVENT: At the end of the method, immediately before it * returns. It is at this point that the state * transitions to {@link LifecycleState#STOPPED}. * </li> * </ol> * * Note that if transitioning from {@link LifecycleState#FAILED} then the * three events above will be fired but the component will transition * directly from {@link LifecycleState#FAILED} to * {@link LifecycleState#STOPPING}, bypassing * {@link LifecycleState#STOPPING_PREP} * * @exception LifecycleException if this component detects a fatal error * that needs to be reported */ public void stop() throws LifecycleException; /** * Prepare to discard the object. The following {@link LifecycleEvent}s will * be fired in the following order: * <ol> * <li>DESTROY_EVENT: On the successful completion of component * destruction.</li> * </ol> * * @exception LifecycleException if this component detects a fatal error * that prevents this component from being used */ public void destroy() throws LifecycleException; /** * Obtain the current state of the source component. * * @return The current state of the source component. */ public LifecycleState getState(); /** * Obtain a textual representation of the current component state. Useful * for JMX. */ public String getStateName(); /** * Marker interface used to indicate that the instance should only be used * once. Calling {@link #stop()} on an instance that supports this interface * will automatically call {@link #destroy()} after {@link #stop()} * completes. */ public interface SingleUse { } }
查看源碼,我們可以看到,該接口定義了一組常量,用於 表示tomcat 目前的運行狀態,各個運行狀態之間的關系其實在注釋中有一個非常生動的示意圖,就下面這個截圖(大神簡直用處文本編輯器的新境界有木有!):
OK,具體各個狀態的含義我就不累贅了,這張圖說明了一切。啟動的機制上面我們已經很詳細地討論過了,下面就以stop tomcat這個過程為例,研究下tomcat 如何通過Lifecycle這個接口,控制組件的生命周期。查看源碼可以發現,我們提到的核心組件,包括Connectors 和Container 等組件,它都有有實現或者繼承(Container是個接口,它繼承了LifeCycle 這個接口)LifeCycle 這個接口,當我們從最外層即Bootstrap 停止容器工作的時候,組件之間會依次調用它所管理的組件(例如,Bootstrap 管理着Catalina ,Server 管理着Service)LifeCycle接口的stop 方法,和啟動的過程非常類似,當然,實際的關閉過程是非常復雜的,Connectors 要關閉連接,要銷毀線程,Container 要銷毀業務類以及涉及到的線程等等,所以我們在實際開發中會發現,如果我們強制stop tomcat (例如在eclipse等IDE中直接點擊停止或者直接強退eclipse),那么tomcat 的內部資源時不能有效釋放的,很多時候IDE出現所謂的Pemgen space 錯誤或者oracle中常見的鎖庫現象便有可能是沒有正常釋放資源造成的(反正我就試過很多次由於強退tomcat 導致oracle 的鎖庫的現象)。
廢話說了那么多,這里簡單總結下tomcat 管理組件生命周期的方法:各個組件都會實現LifeCycle這個接口,而組件之間便是通過這個接口進行組件的生命周期控制的,最頂層的控制類是Bootstrap ,由上而下控制所有組件的停止與開啟。
好了,到這里,我們可以討論下一開始那個問題了:為什么tomcat 要把Bootstrap 、Catalina 以及Server 這三個控制類獨立出來呢?其實,很多時候,我們都會覺得開源框架的組件或者接口划分有點莫名其妙:為什么要多出這部分組件處理?這個接口有什么用?其實造成這些錯覺的原因是,我們很多時候都沒有考慮擴展性等問題,我們看問題的角度和境界也沒有框架作者那么高,就上面這個例子,個人覺得(不一定對啊,歡迎指正交流)大概是這個原因吧:Tomcat 的啟動方式應該允許有很多個(只是平常我們接觸的可能就那么一種),Bootstrap 是一個適配器,對用戶來說,tomcat 啟動過程是透明的,我只需要調用Bootstrap 即可,如果Bootstrap 和Catalina 合在一起,那不同的啟動方式,肯定要對應不同的Catalina ,而這些啟動方式用戶無須知道,所以就抽象出Bootstrap 這個適配器進行調用不同的啟動方式了;至於Server 和 Catalina 為什么要分開不能合在一起呢?我理解是,其實這也很好理解了,不同的啟動方式,啟動都是同一個Server ,所以要分開。
當然,其實理解開源框架的那些抽象類、接口,我們最后一面向對象的思維理解,比方說上面的tomcat 類組織結構中,如果把tomcat 比作一個電器,Bootstrap 是一個總開關,Catalina 是開關到電器之間的控制電路,Server代表這個電器,那么我們就知道為什么不能將三者合在一起了:分開更符合現實人的思維,更符合面向對象的思維,因為,開關,開關和電器之間的電路以及電器本身,三者是完全不同的對象。
三、研究tomcat 組織結構中得到的一點啟迪
1、模板方法。
我們通過上面研究可以發現,類之間的繼承,父類很多時候會調用一個模板方法,這個模板方法具體實現會由子類決定,這就體現出一個很精粹的思想:總體流程在高層設定,而調用者或者繼承者只需要實現某個方法便可以達到某個目的,而這就是框架--框架規定了程序的整個大體架構流程,調用者靈活自己有不同實現
當然,模板方法是實現這種效果的常用手段,我們會發現也會利用接口來實現類似的功能,例如spring 中的攔截器,我們只需要實現Interceptor這個接口即可實現攔截功能,這便是類似於模板方法,spring 已經在運行的適當時刻調用Interceptor了,我們只需要專注於我們的Interceptor要干什么就好了,是不是很棒。
2、面向對象的思維
剛學java 的時候,覺得,面向過程?面向對象?好像沒什么區別嘛,就是一個封裝了一下,在一個class 里面,一個就是封裝在函數function 里面,還不一樣是代碼?還不一樣地按部就班一步步走?但是,當我們使用了一些開源框架並研究它內部的原理時,你才會正真領悟到面向對象的精粹,才會發現:我靠,還有這種操作!就如上面研究tomcat 的啟動過程,我們如果以面向對象的思維去理解,那就好理解了:tomcat 啟動,那還不簡單,就三個類,一個給客戶用的,開關,Bootstrap;一個tomcat 本身,抽象為server;一個就是連接兩者的Catalina ,它負責具體去如何關tomcat。
當然,面向對象的思維強大之處還有很多地方,個人也僅僅了解了皮毛中的皮毛,不足之處,各位大佬指正下吧!
本來還想寫完tomcat 處理請求的過程,不過這篇博客太長了,所以這部分還是放到下一篇博客中討論吧,歡迎繼續關注我的下一篇關於tomcat 處理請求的博客。
end之前,喊下口號,秋招雄起!所有學生黨老鐵拿到好offer!