淺讀tomcat架構設計和tomcat啟動過程(1)

  https://www.cnblogs.com/piaomiaohongchen/p/14977272.html

  淺讀tomcat架構設計之tomcat生命週期(2)

  https://www.cnblogs.com/piaomiaohongchen/p/14982770.html

  Container是tomcat容器的介面,介面位置org.apache.catalina.Container

  Container一共有4個子介面,分別是:Engine,Host,Context,Wrapper和一個預設實現類ContainerBase:

  截圖看下就會清晰很多:

    不知道為啥,截圖後畫質那麼的馬賽克...    

  每個子介面都是一個容器,這四個子容器都有一個對應的Standard***實現類:

    

  紅線標出來的Standard***對應的是具體子容器的的實現類,並且這些子容器實現類都繼承自ContainerBase類,以其中一個子容器實現類為例:

  

  而ContainerBase又繼承自LifecycleMBeanBase

    

  所以四個子容器都是tomcat生命週期管理模式,看完子容器和子容器的介面實現類後,給我的感覺就是無限套娃

  下面講講四個子容器的作用含義,它們和我們的tomcat部署專案/釋出專案息息相關,具有很強的關聯性

    Engine:引擎,用於管理多個站點,一個Service最多隻能有一個Engine

      Host:代表一個站點,也可以叫虛擬主機,通過配置host就可以新增站點  www.xxx.com就是Host,代表我們的站點,oa.xxx.com是我們的另外一個Host,他指向了另一個站點

    Context:代表一個應用程式,對應著我們開發的系統,或者是WEB-INF目錄及下面的web.xml檔案,通俗易懂點來說www.xxx.com/xxx,xxx應用就是Context

    Wrapper:每個Wrapper封裝著一個Servlet  其實就是web.xml中配置的Servlet  

   4種容器的配置方法:

    Engine和Host的配置都在tomcat conf/server.xml中,server.xml是tomcat最重要的配置檔案,tomcat大部分功能都可以在這個檔案中配置

    

<Engine name="Catalina" defaultHost="localhost">

    

  我安裝tomcat,預設Engine的設定是Catalina,預設的Host站點使用是localhost,這裡可以自己設定成別的Host站點.

  Context有三種配置方法 (1)通過檔案配置 (2)war包上傳放到Host目錄下,tomcat會自動查詢自解壓 (3)將應用的資料夾放在Host目錄下,tomcat會自動查詢並新增到Host中

  tocmat部署不想長篇大論講了, tomcat部署參考:https://www.cnblogs.com/ysocean/p/6893446.html ,裡面講了好幾種方法.

    Wrapper的配置就是我們web.xml中配置的Servlet,一個Servlet對應一個Wrapper.

  

  Container的啟動是通過init和start方法來完成的,Container的四個子容器的共同父類都是ConatinerBase,ConatinerBase類定義了兩個方法,分別是:startInternal和initInternal

  先聊聊ContainerBase類的這兩個方法的具體實現:

    org.apache.catalina.core.ContainerBase:

protected void initInternal() throws LifecycleException {
BlockingQueue<Runnable> startStopQueue = new LinkedBlockingQueue();
this.startStopExecutor = new ThreadPoolExecutor(this.getStartStopThreadsInternal(), this.getStartStopThreadsInternal(), 10L, TimeUnit.SECONDS, startStopQueue, new ContainerBase.StartStopThreadFactory(this.getName() + "-startStop-"));
this.startStopExecutor.allowCoreThreadTimeOut(true);
super.initInternal();
}

  initInternal方法主要是先是初始化,建立物件,然後建立一個執行緒池來管理啟動和關閉內部執行緒

  ThreadPoolExecutor繼承自Executor用於管理執行緒,一直往上回溯,會發現是Executor

  

  接著看startInternal方法:

protected synchronized void startInternal() throws LifecycleException {
this.logger = null;
this.getLogger();
Cluster cluster = this.getClusterInternal();
if (cluster != null && cluster instanceof Lifecycle) {
((Lifecycle)cluster).start();
} Realm realm = this.getRealmInternal();
if (realm != null && realm instanceof Lifecycle) {
((Lifecycle)realm).start();
} Container[] children = this.findChildren();
List<Future<Void>> results = new ArrayList(); for(int i = 0; i < children.length; ++i) {
results.add(this.startStopExecutor.submit(new ContainerBase.StartChild(children[i])));
} boolean fail = false;
Iterator i$ = results.iterator(); while(i$.hasNext()) {
Future result = (Future)i$.next(); try {
result.get();
} catch (Exception var9) {
log.error(sm.getString("containerBase.threadedStartFailed"), var9);
fail = true;
}
} if (fail) {
throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"));
} else {
if (this.pipeline instanceof Lifecycle) {
((Lifecycle)this.pipeline).start();
} this.setState(LifecycleState.STARTING);
this.threadStart();
}
}

    startInternal方法很長,拆分下程式碼講解:

   

    Cluster cluster = this.getClusterInternal();
if (cluster != null && cluster instanceof Lifecycle) {
((Lifecycle)cluster).start();
} Realm realm = this.getRealmInternal();
if (realm != null && realm instanceof Lifecycle) {
((Lifecycle)realm).start();
}

  如果cluster和realm不為null,並且cluster/realm引用指向LIfecycle,就呼叫start方法

    繼續往下拆分看:

        Container[] children = this.findChildren();
List<Future<Void>> results = new ArrayList(); for(int i = 0; i < children.length; ++i) {
results.add(this.startStopExecutor.submit(new ContainerBase.StartChild(children[i])));
}

  這部分程式碼是呼叫所有子容器的start方法來啟動子容器,跟進ContainerBase.StartChild函式看看:

    

  繼續往下拆分程式碼:

    

      if (fail) {
throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"));
} else {
if (this.pipeline instanceof Lifecycle) {
((Lifecycle)this.pipeline).start();
} this.setState(LifecycleState.STARTING);
this.threadStart();
}

  這段程式碼大致就是呼叫管道中start方法,啟動完成後將生命週期設定成LifecycleState.STARTING,最後啟動後臺執行緒:

  

  Engine:

    前面我們講了,四大子容器的實現類是Standard***類.那麼Engine的預設實現類是StandardEngine類:

    org.apache.catalina.core.StandardEngine:

   如果使用Engine就會呼叫Engine預設實現類StandardEngine的initInternal和startInternal方法:

   我們來看看:

    

  protected void initInternal() throws LifecycleException {
this.getRealm();
super.initInternal();
} protected synchronized void startInternal() throws LifecycleException {
if (log.isInfoEnabled()) {
log.info("Starting Servlet Engine: " + ServerInfo.getServerInfo());
} super.startInternal();
}

 程式碼不是很多,發現他是呼叫他父類的方法,他的父類是ContainerBase,我們一開始就分析過ContainerBase類的initInternal和startInternal方法了.

   

  Host:

  Host介面的預設實現類是org.apache.catalina.core.StandardHost:

  跟進類,發現StandardHost沒有重寫父類的initInternal,只有startInternal方法被重寫:

    如果他沒有,初始化會預設會呼叫它父類的initInternal

  startInternal方法程式碼如下:

    

     protected synchronized void startInternal() throws LifecycleException {
String errorValve = this.getErrorReportValveClass();
if (errorValve != null && !errorValve.equals("")) {
try {
boolean found = false;
Valve[] valves = this.getPipeline().getValves();
Valve[] arr$ = valves;
int len$ = valves.length; for(int i$ = 0; i$ < len$; ++i$) {
Valve valve = arr$[i$];
if (errorValve.equals(valve.getClass().getName())) {
found = true;
break;
}
} if (!found) {
Valve valve = (Valve)Class.forName(errorValve).newInstance();
this.getPipeline().addValve(valve);
}
} catch (Throwable var8) {
ExceptionUtils.handleThrowable(var8);
log.error(sm.getString("standardHost.invalidErrorReportValveClass", new Object[]{errorValve}), var8);
}
} super.startInternal();
}

  程式碼也是好多一大塊.  

    拆開一部分:

    截圖的是重點,主要做的就是獲取管道的值,然後判斷管理裡面是否有指定的Valve

    

  如果沒找到管道值,就呼叫addValve方法,加入到管道中,程式碼如下:

  

      if (!found) {
Valve valve = (Valve)Class.forName(errorValve).newInstance();
this.getPipeline().addValve(valve);
}

  這是個重點,對我們後續講解tomcat 記憶體馬提供很大的幫助,valve的記憶體馬注入是一定要呼叫addValue的,否則匹配不到!

  Host的啟動除了呼叫StandardHost的startInternal方法,還會呼叫HostConfig中的一些方法:

    對應位置:org.apache.catalina.startup.HostConfig

    這個類主要作用就是Host站點的配置

    比較重要的就是部署應用了,deployApps方法,程式碼如下

  protected void deployApps() {
File appBase = this.host.getAppBaseFile();
File configBase = this.host.getConfigBaseFile();
String[] filteredAppPaths = this.filterAppPaths(appBase.list());
this.deployDescriptors(configBase, configBase.list());
this.deployWARs(appBase, filteredAppPaths);
this.deployDirectories(appBase, filteredAppPaths);
}

  上面介紹了三種方法來部署tomcat專案,

   (1)通過xml:

    

  (2):war包部署

  

 (3)資料夾部署:

   

  Context:

  Context的預設實現類是org.apache.catalina.core.StandardContext:

  startInternal方法如下:

    拆取比較重要部分,方法內容實在是太多了:    

      if (ok && !this.listenerStart()) {
log.error("Error listenerStart");
ok = false;
} if (ok) {
this.checkConstraintsForUncoveredMethods(this.findConstraints());
} try {
Manager manager = this.getManager();
if (manager != null && manager instanceof Lifecycle) {
((Lifecycle)manager).start();
}
} catch (Exception var19) {
log.error("Error manager.start()", var19);
ok = false;
} if (ok && !this.filterStart()) {
log.error("Error filterStart");
ok = false;
} if (ok && !this.loadOnStartup(this.findChildren())) {
log.error("Error loadOnStartup");
ok = false;
}

  主要含義就是呼叫了web.xml中定義的Listener,另外還初始化了filterStart和loadOnStartup

  filterStart:org.apache.catalina.core.StandardContext:

  

  使用filterConfigs儲存鍵值對,前面簡單看了hostConfig,filterConfig同理:

  他的例項類是:org.apache.catalina.core.ApplicationFilterConfig,等我講Filter記憶體馬的時候再詳細解釋它,這個先放一邊.

  

  Wrapper:

    Wrapper是封裝Servlet的包裝器,Wrapper的預設實現類,沒有重寫initInternal,他預設呼叫父類的initInternal方法,startInternal方法程式碼如下:

    org.apache.catalina.core.StandardWrapper:

   protected synchronized void startInternal() throws LifecycleException {
Notification notification;
if (this.getObjectName() != null) {
notification = new Notification("j2ee.state.starting", this.getObjectName(), (long)(this.sequenceNumber++));
this.broadcaster.sendNotification(notification);
} super.startInternal();
this.setAvailable(0L);
if (this.getObjectName() != null) {
notification = new Notification("j2ee.state.running", this.getObjectName(), (long)(this.sequenceNumber++));
this.broadcaster.sendNotification(notification);
} }

  程式碼很簡單,(1)使用broadcaster傳送訊息 (2)呼叫父類的startInternal (3)呼叫setAvailate方法,設定Servlet的有效時間