tomcat瞭解:系統架構
前言
Tomcat是Apache基金組織下的開源專案,性質是一個Web伺服器。下面這種情況很普遍:在eclipse床架一個web專案並部署到Tomcat中,啟動tomcat,在瀏覽器中輸入一個類似http://localhost:8080/webproject/anyname.jsp
的url,然後就可以看到我們寫好的jsp頁面的內容了。一切都是那麼自然和順理成章,然而這一切都是源於tomcat帶給我們的,那麼在tomcat背後,這一切又是怎麼樣發生的呢?帶著對tomcat工作原理的好奇心,我決定研究一下tomcat的原始碼,然而部署原始碼環境的過程卻讓我心灰意冷,本著搞不定我還真不信的熱情,折騰了一個晚上+一個早上,終於把原始碼原始碼環境搭建好了。
為了讓文章顯得更有條理性,我將從以下幾個方面說明Tomcat的工作流程:
- 搭建Tomcat原始碼環境指導
- Tomcat的系統架構
- Tomcat中的核心元件說明
- Servlet工作原理
- 一個例子
Tomcat的系統架構
首先我們從一個巨集觀的角度來看一下Tomcat的系統的架構:
從這張圖中可以看到,Tomcat的核心元件就兩個Connector和Container(後面還有詳細說明),一個Connector+一個Container構成一個Service,Service就是對外提供服務的元件,有了Service元件Tomcat就可以對外提供服務了,但是光有服務還不行,還得有環境讓你提供服務才行,所以最外層的Server就為Service提供了生存的土壤。那麼這些個元件到底是幹嘛用的呢?Connector是一個聯結器,主要負責接收請求並把請求交給Container,Container就是一個容器,主要裝的是具體處理請求的元件。Service主要是為了關聯Container與Connector,一個單獨的Container或者一個單獨的Connector都不能完整處理一個請求,只有兩個結合在一起才能完成一個請求的處理。Server這是負責管理Service集合,從圖中我們看到一個Tomcat可以提供多種服務,那麼這些Serice就是由Server來管理的,具體的工作包括:對外提供一個介面訪問Service,對內維護Service集合,維護Service集合又包括管理Service的生命週期、尋找一個請求的Service、結束一個Service等。以上就是對Tomcat的核心元件的簡要說明,下面我們詳細看看每一個元件的執行流程:
Server
上面說Server是管理Service介面的,Server是Tomcat的頂級容器,是一個介面,Server介面的標準實現類是StandardServer類,在Server介面中有許多方法,我們重點關注兩個方法:addService()和findService(String)。我們先來看看Server介面的全貌:
接著看看addService()和findService(String)的實現程式碼:
程式碼清單1-1:
/** * Add a new Service to the set of defined Services. * * @param service The Service to be added */ @Override public void addService(Service service) { service.setServer(this); synchronized (services) { Service results[] = new Service[services.length + 1]; System.arraycopy(services, 0, results, 0, services.length); results[services.length] = service; services = results; if (getState().isAvailable()) { try { service.start(); } catch (LifecycleException e) { // Ignore } } // Report this property change to interested listeners support.firePropertyChange("service", null, service); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
可以看到,Server使用一個數組來管理Service的,每新增一個Service就把原來的Service拷貝到一個新的陣列中,再把新的Service放入Service陣列中。所以Server與Service是關聯在一起的,那麼後面的getState().isAvailable()
是幹嘛的呢?判斷狀態是否無效,從而決定是否執行service方法。這裡說到了狀態,就不得不說Tomcat管理各元件生命週期的Lifecycle介面了:
Lifecycle介面
Tomcat中的元件都交給這個介面管理,但是具體元件的生命週期是由包含元件的父容器來管理的,Tomcat中頂級容器管理著Service的生命週期,Service容器又是Connector和Container的父容器,所以這兩個元件的生命週期是由Service管理的,Container也有子容器,所以管理著這些子容器的生命週期。這樣,只要所有元件都實現了Lifecycle介面,從頂層容器Server開始,就可以控制所有容器的生命週期了。Lifecycle介面中定義了很多狀態,在api中詳細說明了呼叫不同方法後的狀態轉變,同時定義了不同的方法,這些方法在執行後狀態會發生相應的改變,在Lifecycle介面中定義瞭如下方法:
在StandServer中實現了startInernal()方法,就是迴圈啟動StandServer管理的Service的過程,Tomcat的Service都實現了Lifecycle介面,所以被管理的Service都將被通知到,從而執行start()方法,startIntenal()方法是這樣的:
程式碼清單1-2:
/**
* Start nested components ({@link Service}s) 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 void startInternal() throws LifecycleException {
fireLifecycleEvent(CONFIGURE_START_EVENT, null);
setState(LifecycleState.STARTING);
globalNamingResources.start();
// Start our defined Services
synchronized (services) {
for (int i = 0; i < services.length; i++) {
services[i].start();
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
現在所有的Service就會收到通知繼而執行start方法。如果一個Service不允許被使用將會丟擲一個LifecycleException異常。
stopIntenal()會通知所有Service執行stop方法,具體處理流程與startIntenal()方法類似。這個執行過程涉及一個非常重要的設計模式,就是觀察者模式。
現在我們已經能夠知道了容器通過Lifecycle介面管理容器的生命週期,那麼在父容器的狀態改變具體是怎麼樣通知給子容器的呢?回到程式碼清單1-2,我們注意到有一個fireLifecycleEvent()
方法,fireLifecycleEvent()的執行流程如下:
- 呼叫LifecycleBase的fireLifecycleEvent(LifecycleListener listener)方法,LifecycleBase是一個抽象類,實現了Lifecycle介面
- 繼續呼叫LifecycleSupport(是一個輔助完成對已經註冊監聽器的事件通知類,不可被繼承,使用final)的fireLifecycleEvent(String type, Object data)方法
- 完成事件通知
fireLifecycleEvent(String type, Object data)的方法如下:
程式碼清單1-3:
/**
* Notify all lifecycle event listeners that a particular event has
* occurred for this Container. The default implementation performs
* this notification synchronously using the calling thread.
*
* @param type Event type
* @param data Event data
*/
public void fireLifecycleEvent(String type, Object data) {
LifecycleEvent event = new LifecycleEvent(lifecycle, type, data);
LifecycleListener interested[] = listeners;
for (int i = 0; i < interested.length; i++)
interested[i].lifecycleEvent(event);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
所以,具體事件的通知是由LifecycleListener介面的lifecycleEvent方法完成的,各實現類可以根據不同的情況實現不同的事件監聽邏輯
Service
Service是具體提供服務的介面,一個Service包裝了Connector和一個Container,在Tomcat中這點是如何實現的呢?Service是一個介面,其標準實現類是StandardService,下面是這兩個類的鳥瞰圖:
這裡,我們只關心與Connector和Container最緊密的方法:setContainer()和addConnector()方法,先看一下setContainer()方法的原始碼:
程式碼清單2-1:
/**
* Set the <code>Container</code> that handles requests for all
* <code>Connectors</code> associated with this Service.
*
* @param container The new Container
*/
@Override
public void setContainer(Container container) {
Container oldContainer = this.container;
if ((oldContainer != null) && (oldContainer instanceof Engine))
((Engine) oldContainer).setService(null);
this.container = container;
if ((this.container != null) && (this.container instanceof Engine))
((Engine) this.container).setService(this);
if (getState().isAvailable() && (this.container != null)) {
try {
this.container.start();
} catch (LifecycleException e) {
// Ignore
}
}
if (getState().isAvailable() && (oldContainer != null)) {
try {
oldContainer.stop();
} catch (LifecycleException e) {
// Ignore
}
}
// Report this property change to interested listeners
support.firePropertyChange("container", oldContainer, this.container);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
從程式碼中可以看到這個方法主要的任務是設定一個Container容器來處理一個或者多個Connector傳送過來的請求。首先判斷當前的Service是否已經關聯了Container容器,如果已經關聯了就去除這個關聯關係。如果原來的Container容器已經啟動了就終止其生命週期,結束執行並設定新的關聯關係,這個新的Container容器開始新的生命週期。最後把這個過程通知給感興趣的事件監聽程式。
下面看看addConnector的方法:
程式碼清單2-2:
/**
* Add a new Connector to the set of defined Connectors, and associate it
* with this Service's Container.
*
* @param connector The Connector to be added
*/
@Override
public void addConnector(Connector connector) {
synchronized (connectors) {
connector.setService(this);
Connector results[] = new Connector[connectors.length + 1];
System.arraycopy(connectors, 0, results, 0, connectors.length);
results[connectors.length] = connector;
connectors = results;
if (getState().isAvailable()) {
try {
connector.start();
} catch (LifecycleException e) {
log.error(sm.getString(
"standardService.connector.startFailed",
connector), e);
}
}
// Report this property change to interested listeners
support.firePropertyChange("connector", null, connector);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
執行過程也比較清楚:用一個同步程式碼塊包住connectors陣列,首先設定connector與container和service的關聯關係,然後讓connector開始新的生命週期,最後通知感興趣的事件監聽程式。注意到Connector的管理和Server管理Service一樣都使用了陣列拷貝並把新的陣列賦給當前的陣列,從而間接實現了動態陣列。之所以使用陣列我想可能是出於效能的考慮吧。