Container是一個Tomcat容器的接口,Tomcat有四種容器html
· Enginejava
· Hostgit
· Contextweb
· Wrapperapache
Engine表明整個Catalina的Servlet引擎,Host則表明若干個上下文的虛擬主機。Context則表明一個Web應用,而一個Context則會用有多個Wrapper。Wrapper是一個單獨的Servlet。tomcat
下圖是幾種容器實現的類繼承圖,咱們能夠看到最下層以Standard開頭的幾個類安全
· StandardEnginesession
· StandardHostapp
· StandardContextdom
· StandardWrapper
以上幾個類是Tomcat對幾種容器的默認實現。
以上幾個類是Tomcat對幾種容器的默認實現。
Engine的屬性name,是Engine的名字,若是有多個Engine,Engine須要惟一。defaultHost也很是重要,若是一個Engine有多個Host時,若是匹配不到合適的Host時,則須要默認選取一個,也就是defaultHost定義的,它的值爲Host的name。
<Engine name="Catalina" defaultHost="localhost"> <RealmclassName="org.apache.catalina.realm.LockOutRealm"> <!--This Realm uses the UserDatabase configured in the global JNDI resources under the key"UserDatabase". Any edits that are performed against thisUserDatabase are immediately available for use by theRealm. --> <RealmclassName="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/> </Realm> <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"> <!--SingleSignOn valve, share authentication between web applications Documentation at: /docs/config/valve.html--> <!-- <ValveclassName="org.apache.catalina.authenticator.SingleSignOn" /> --> <!-- Access log processes allexample. Documentation at:/docs/config/valve.html Note: The pattern used isequivalent to using pattern="common" --> <ValveclassName="org.apache.catalina.valves.AccessLogValve"directory="logs" prefix="localhost_access_log" suffix=".txt" pattern="%h %l %u %t "%r" %s %b" /> </Host> </Engine>
Engine還有另一個很是重要的屬性叫jvmRoute,它通常用在Cluster裏。
假設Cluster是這麼配置的,Tomcat1的 conf/server.xml
<Engine name="Catalina" defaultHost="localhost" jvmRoute="tomcat1">
Tomcat2的conf/server.xml
<Engine name="Catalina" defaultHost="localhost" jvmRoute="tomcat2">
在生成SessionID時,jvmRoute會用到的,代碼以下:
public class StandardSessionIdGeneratorextends SessionIdGeneratorBase{ @Override publicString generateSessionId(String route) { byterandom[] = newbyte[16]; int sessionIdLength = getSessionIdLength(); //Render the result as a String of hexadecimal digits // Start with enough space forsessionIdLength and medium route size StringBuilderbuffer = new StringBuilder(2 * sessionIdLength + 20); int resultLenBytes = 0; while (resultLenBytes < sessionIdLength) { getRandomBytes(random); for (int j = 0; j < random.length && resultLenBytes < sessionIdLength; j++) { byte b1 = (byte) ((random[j] & 0xf0) >> 4); byte b2 = (byte) (random[j] & 0x0f); if (b1 < 10) buffer.append((char) ('0' + b1)); else buffer.append((char) ('A' + (b1 - 10))); if (b2< 10) buffer.append((char) ('0' + b2)); else buffer.append((char) ('A' + (b2 - 10))); resultLenBytes++; } } if(route != null&& route.length() > 0) { buffer.append('.').append(route); }else { String jvmRoute =getJvmRoute(); if (jvmRoute != null && jvmRoute.length() > 0) { buffer.append('.').append(jvmRoute); } } returnbuffer.toString(); } }
最後幾行代碼顯示若是在Cluster狀況下會將jvmRoute加在sessionID後面。
Host是表明虛擬主機,主要設置appbase目錄,例如webapps等。Host中的name表明域名,因此下面的例子中表明的localhost,能夠經過localhost來訪問。appBase是指該站點所在的目錄,默認通常是webapps。unpackWARs這個屬性也很重要,通常來講,一個webapp的發佈包有格式各樣,例如zip,war等,對於war包放到appBase
下是否自動解壓縮,顯而易見,當爲true時,自動解包。autoDeploy是指是指Tomcat在運行時應用程序是否自動部署。
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
Context能夠在如下幾個地方聲明:
1. Tomcat的server.xml配置文件中的<Context>節點用於配置Context,它直接在Tomcat解析server.xml的時候,就完成Context對象的建立。
2. Web應用的/META-INF/context.xml文件可用於配置Context,此配置文件用於配置Web應用對應的Context屬性。
3. 可用%CATALINA_HOME%/conf[EngineName]/[HostName]/[Web項目名].xml文件聲明建立一個Context。
4. Tomcat全局配置爲conf/context.xml,此文件配置的屬性會設置到全部的Context中
5. Tomcat的Host級別配置文件爲/conf[EngineName]/[HostName]/context.xml.default文件,它配置的屬性會設置到某Host下面全部的Context中。
以上5種方法有些是共享的,有些是獨享的。其中後面2種是被Tomcat共享的。在實際的應用中,我的很是推薦第三種方法。若是在採用第一種方法,這種方法是有侵入性的,不建議,並且該文件是在Tomcat啓動時才加載。對於共享的方法我我的也是不推薦使用的,畢竟在實際的應用中仍是但願本身的app配置單獨出來更合理一些。
Wrapper 表明一個Servlet,它負責管理一個Servlet,包括Servlet 的裝載、初始化、執行以及資源回收。Wrapper的父容器通常是Context,Wrapper是最底層的容器,它沒有子容器了,因此調用它的addChild 將會拋illegalargumentexception。Wrapper的實現類是StandardWrapper,StandardWrapper還實現了擁有一個Servlet 初始化信息的ServletConfig,由此看出StandardWrapper 將直接和Servlet 的各類信息打交道。
前面的類圖講過,前面提到的容容器都實現或繼承了LifeCycle,因此LifeCycle裏的幾個生命週期一樣適用於這裏。不過除了繼承自LifeCycle以外,幾個容器也繼承ContainerBase這個類。幾個Container的初始化和啓動都是經過initInternal和startInternal來實現的。須要的話,各個容器能夠實現本身的邏輯。
由於4大容器都繼承ContainerBase,咱們看看該類的initInternal和startInternal的實現。
@Override protected void initInternal() throws LifecycleException { reconfigureStartStopExecutor(getStartStopThreads()); super.initInternal(); } /* * Implementation note: If there is ademand for more control than this then * it is likely that the best solutionwill be to reference an external * executor. */ private void reconfigureStartStopExecutor(int threads) { if (threads == 1) { //Use a fake executor if(!(startStopExecutorinstanceof InlineExecutorService)) { startStopExecutor = new InlineExecutorService(); } } else{ //Delegate utility execution to the Service Serverserver = Container.getService(this).getServer(); server.setUtilityThreads(threads); startStopExecutor= server.getUtilityExecutor(); } }
咱們能夠看到這裏並無設置一些狀態。在初始化的過程當中,初始化statStopExecutor,它的類型是java.util.concurrent.ExecutorService。
下面是startInternal的代碼,咱們能夠看出這裏作的事情:
1. 若是cluster和realm都配置後,須要調用它們本身的啓動方法。
2. 調用子容器的啓動方法。
3. 啓動管道。
4. 設置生命週期的狀態。
5. 同時啓動一些background的監控線程。
@Override protected synchronized void startInternal() throws LifecycleException { // Start our subordinate components, if any logger = null; getLogger(); Cluster cluster = getClusterInternal(); if (cluster instanceof Lifecycle) { ((Lifecycle) cluster).start(); } Realm realm = getRealmInternal(); if (realm instanceof Lifecycle) { ((Lifecycle) realm).start(); } // Start our child containers, if any Container children[] = findChildren(); List<Future<Void>> results = new ArrayList<>(); for (int i = 0; i < children.length; i++) { results.add(startStopExecutor.submit(new StartChild(children[i]))); } MultiThrowable multiThrowable = null; for (Future<Void> result : results) { try { result.get(); } catch (Throwable e) { log.error(sm.getString("containerBase.threadedStartFailed"), e); if (multiThrowable == null) { multiThrowable = new MultiThrowable(); } multiThrowable.add(e); } } if (multiThrowable != null) { throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"), multiThrowable.getThrowable()); } // Start the Valves in our pipeline (including the basic), if any if (pipeline instanceof Lifecycle) { ((Lifecycle) pipeline).start(); } setState(LifecycleState.STARTING); // Start our thread if (backgroundProcessorDelay > 0) { monitorFuture = Container.getService(ContainerBase.this).getServer() .getUtilityExecutor().scheduleWithFixedDelay( new ContainerBackgroundProcessorMonitor(), 0, 60, TimeUnit.SECONDS); } }
這裏首先根據配置啓動了Cluster和Realm,啓動的方法也很直觀,直接調用它們的start方法。Cluster通常用於集羣,Realm是Tomcat的安全域,管理資源的訪問權限,例如身份認證,權限等。一個Tomcat能夠擁有多個Realm的。
根據代碼,子容器是使用startStopExecutor來實現的,startStopExecutor會使用新的線程來啓動,這樣可使用多個線程來同時啓動多個子容器,這樣在性能上更勝一籌。由於可能有多個子容器,把他們存入到Future的List裏,而後遍歷每一個Future並調用其get方法。
遍歷Future的做用是什麼?1,get方法是阻塞的,只有線程處理完後才能繼續往下走,這樣保證了Pipeline啓動以前容器確保調用完成。2,能夠處理啓動過程當中的異常,若是有容器啓動失敗,也不至於繼續執行下去。
啓動子容器調用了StartChild這麼一個相似,它的實現以下:
private static class StartChild implements Callable<Void> { private Container child; public StartChild(Container child) { this.child = child; } @Override public Void call() throws LifecycleException { child.start(); return null; } }
這個類也是定義在ContainerBase裏的,因此全部容器的啓動過程都對調用容器的start方法。
咱們能夠看到StartChild實現了Callable接口。咱們知道啓動線程,有Runnable和Callable等方式,那麼Runnable和Callable的區別在哪裏呢?我認爲的區別是:
1. 對於實現Runnable,run方法並不會返回任何東西,可是對於Callable,真是能夠實現當執行完成後返回結果的。但須要注意,一個線程並不能和Callable建立,盡能夠和Runnable一塊兒建立。
2. 另一個區別就是Callable的Call方式能夠拋出Exception,可是Runnable的run方法這不能夠。
根據以上,咱們能夠看出爲何要用Callable,前面說捕獲到異常也正是這個原理。
在這裏咱們也看到了Future這個東西。有必要在這裏詳細解釋一下Future的概念。Future用來表示異步計算的結果,它提供了一些方法用來檢查計算是否已經完成,或等待計算的完成以及獲取計算的結果。計算結束後的結果只能經過get方法來獲取。固然,也可使用Cancel方法來取消計算。在回到咱們這裏的代碼,以下,咱們能夠看到結果已經存在result裏,經過get方法來獲取,前面咱們分析Callable能夠拋出異常,這裏咱們能夠看到有捕獲到這些異常的代碼。
for (Future<Void> result : results) { try { result.get(); } catch (Throwable e) { log.error(sm.getString("containerBase.threadedStartFailed"), e); if (multiThrowable == null) { multiThrowable = new MultiThrowable(); } multiThrowable.add(e); } }
Engine的默認實現類是StandardEngine,它的初始化和啓動會調用initInternal和startInternal。下面是StandardEngine的結構圖
初始化和啓動的代碼分別以下:
@Override protected void initInternal() throws LifecycleException { // Ensure that a Realm is present before any attempt is made to start // one. This will create the default NullRealm if necessary. getRealm(); super.initInternal(); } /** * Start this component and implement the requirements * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}. * * @exception LifecycleException if this component detects a fatal error * that prevents this component from being used */ @Override protected synchronized void startInternal() throws LifecycleException { // Log our server identification information if (log.isInfoEnabled()) { log.info(sm.getString("standardEngine.start", ServerInfo.getServerInfo())); } // Standard container startup super.startInternal(); }
初始化和啓動仍是分別調用了ContainerBase的initInternal·和startInternal。特別要注意的是initInternal額外調用了getRealm獲取Realm的信息。那麼getRealm的實現以下:
@Override
public Realm getRealm() {
Realm configured = super.getRealm();
// If no set realm has been called - default to NullRealm
// This can be overridden at engine, context and host level
if (configured == null) {
configured = new NullRealm();
this.setRealm(configured);
}
return configured;
}
咱們能夠看出,若是沒有realm配置,直接返回默認的NullRealm。
Host的默認實現類是StandardHost,繼承圖以下。
下面代碼只有startInternal,並無initInternal,那是由於StandardHost並無重寫initInternal。
代碼比較簡單,除了調用ContainerBase的startInternal,前面還須要查詢Pipeline裏的Valve有沒有和ErrorReport相關的。若是沒有建立Valve一下,而後加到Pipeline裏。
protected synchronized void startInternal() throws LifecycleException { // Set error report valve String errorValve = getErrorReportValveClass(); if ((errorValve != null) && (!errorValve.equals(""))) { try { boolean found = false; Valve[] valves = getPipeline().getValves(); for (Valve valve : valves) { if (errorValve.equals(valve.getClass().getName())) { found = true; break; } } if(!found) { Valve valve = (Valve) Class.forName(errorValve).getConstructor().newInstance(); getPipeline().addValve(valve); } } catch (Throwable t) { ExceptionUtils.handleThrowable(t); log.error(sm.getString( "standardHost.invalidErrorReportValveClass", errorValve), t); } } super.startInternal(); }
其中默認的ErrorReport Valve是
/** * The Java class name of the default error reporter implementation class * for deployed web applications. */ private String errorReportValveClass = "org.apache.catalina.valves.ErrorReportValve"
下面是Context的初始化代碼,後面調用了NamingResource相關信息。
@Override protected void initInternal() throws LifecycleException { super.initInternal(); // Register the naming resources if (namingResources != null) { namingResources.init(); } // Send j2ee.object.created notification if (this.getObjectName() != null) { Notification notification = new Notification("j2ee.object.created", this.getObjectName(), sequenceNumber.getAndIncrement()); broadcaster.sendNotification(notification); } }
接下來看看startInternal,這個方法很是長,節選重要代碼.
若是resouce沒有啓動,須要調用resource的啓動,接下來是調用web.xml中定義的Listener,另外還須要初始化該配置文件定義的Filter以及load-on-startup的Servlet。
protected synchronized void startInternal() throws LifecycleException { //… … if (ok) { resourcesStart(); } //… … // Configure and call application event listeners if (ok) { if (!listenerStart()) { log.error(sm.getString("standardContext.listenerFail")); ok = false; } } //…… // Configure and call application filters if (ok) { if (!filterStart()) { log.error(sm.getString("standardContext.filterFail")); ok = false; } } // Load and initialize all "load on startup" servlets if (ok) { if (!loadOnStartup(findChildren())){ log.error(sm.getString("standardContext.servletFail")); ok = false; } }