粗淺看 Tomcat系統架構分析

原文出處: 吳士龍 http://www.importnew.com/21112.htmlhtml

Tomcat的結構很複雜,可是Tomcat也很是的模塊化,找到了Tomcat最核心的模塊,就抓住了Tomcat的七寸java

總體結構

Tomcat 整體結構圖web


從上圖中能夠看出Tomcat的心臟是兩個組件:Connector 和 Container,關於這兩個組件將在後面詳細介紹。Connector 組件是能夠被替換,這樣能夠提供給服務器設計者更多的選擇,由於這個組件是如此重要,不只跟服務器的設計的自己,並且和不一樣的應用場景也十分相關,因此一個Container 能夠選擇對應多個Connector。多個Connector和一個Container 就造成了一個Service,Service 的概念你們都很熟悉了,有了Service 就能夠對外提供服務了,可是Service還要一個生存的環境,必需要有人可以給她生命、掌握其生死大權,那就非Server莫屬了。因此整個Tomcat的生命週期由Server控制。apache

以Service  做爲「婚姻」設計模式

咱們將 Tomcat 中 Connector、Container 做爲一個總體比做一對情 侶的話,Connector主要負責對外交流,能夠比做爲 Boy,Container 主要處理 Connector 接受的請求,主要是處理內部事務,能夠比做爲 Girl。那麼這個 Service就是鏈接這對男女的結婚證了。是Service將它們鏈接在一塊兒,共同組成一個家庭。固然要組成一個家庭還要不少其它的元素。數組

說白了,Service 只是在Connector 和 Container外面多包一層,把它們組裝在一塊兒,向外面提供服務,一個Service能夠設置多個Connector,可是隻能有一個 Container 容器。這個 Service 接口的 方法列表以下:瀏覽器

①Service接口安全

從 Service接口中定義的方法中能夠看出,它主要是爲了關聯Connector和 Container,同時會初始化它下面的其它組件,注意接 口中它並無規定必定要控制它下面的組件的生命週期。全部組件的 生命週期在一個 Lifecycle 的接口中控制,這裏用到了一個重要的設 計模式,關於這個接口將在後面介紹。服務器

Tomcat 中 Service接口的標準實現類是StandardService它不只實現了 Service 藉口同時還實現了 Lifecycle 接口,這樣它就能夠控 制它下面的組件的生命週期了。StandardService 類結構圖以下:session

②StandardService的類結構圖

從上圖中能夠看出除了 Service接口的方法的實現以及控制組件生命週期的 Lifecycle 接口的實現,還有幾個方法是用於在事件監聽的 方法的實現,不只是這個 Service 組件,Tomcat 中其它組件也一樣 有這幾個方法,這也是一個典型的設計模式,將在後面介紹。

下面看一下 StandardService 中主要的幾個方法實現的代碼,下面是setContainer和addConnector 方法的源碼:

③StandardService. SetContainer

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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 (started && ( this .container != null ) && ( this .container instanceof Lifecycle))
 
{
 
try {
 
((Lifecycle) this .container).start();
 
} catch (LifecycleException e) {
 
;
 
}
 
}
 
synchronized (connectors) {
 
for ( int i = 0 ; i < connectors.length; i++)
 
connectors[i].setContainer( this .container);
 
}
 
if (started && (oldContainer != null ) && (oldContainer instanceof Lifecycle)) {
 
try {
 
((Lifecycle)  oldContainer).stop();
 
} catch (LifecycleException e) {
 
;
 
}
 
}
 
support.firePropertyChange( "container" , oldContainer, this .container);
—————————————————————————————
}

這段代碼很簡單,其實就是先判斷當前的這個 Service 有沒有已經關 聯了 Container,若是已經關聯了,那麼去掉這個關聯關係——oldContainer.setService(null)。若是這個oldContainer 已經被啓動 了,結束它的生命週期。而後再替換新的關聯、再初始化並開始這個新的 Container 的生命週期。最後將這個過程通知感興趣的事件監聽程序。這裏值得注意的地方就是,修改Container 時要將新的 Container關聯到每一個Connector,還好Container 和 Connector 沒有雙向關聯,否則這個關聯關係將會很難維護。

④StandardService. addConnector

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public void addConnector(Connector connector) {
 
synchronized (connectors) {
 
connector.setContainer( this .container);
 
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 (initialized) {
 
try {
 
connector.initialize();
 
} catch (LifecycleException e) {
 
e.printStackTrace(System.err);
 
}
 
}
 
if (started && (connector instanceof Lifecycle)) {
 
try {
 
((Lifecycle) connector).start();
 
} catch (LifecycleException e) {
 
;
 
}
 
}
 
support.firePropertyChange( "connector" , null , connector);
 
}
 
}

上面是 addConnector 方法,這個方法也很簡單,首先是設置關聯關 系,而後是初始化工做,開始新的生命週期。這裏值得一提的是,注 意 Connector 用的是數組而不是 List集合,這個從性能角度考慮可 以理解,有趣的是這裏用了數組可是並無向咱們日常那樣,一開始 就分配一個固定大小的數組,它這裏的實現機制是:從新建立一個當 前大小的數組對象,而後將原來的數組對象 copy 到新的數組中,這 種方式實現了相似的動態數組的功能,這種實現方式,值得咱們之後 拿來借鑑。

最新的 Tomcat6 中 StandardService也基本沒有變化,可是從Tomcat5 開始Service、Server 和容器類都繼承了MBeanRegistration接口,Mbeans 的管理更加合理。

以 Server  爲「居」

前面說一對情侶由於 Service 而成爲一對夫妻,有了可以組成一個家 庭的基本條件,可是它們還要有個實體的家,這是它們在社會上生存 之本,有了家它們就能夠安心的爲人民服務了,一塊兒爲社會創造財富。

Server要完成的任務很簡單,就是要可以提供一個接口讓其它程序可以訪問到這個Service 集合、同時要維護它所包含的全部 Service 的生命週期,包括如何初始化、如何結束服務、如何找到別人要訪問的 Service。還有其它的一些次要的任務,如您住在這個地方要向當 地政府去登記啊、可能還有要配合當地公安機關平常的安全檢查什麼 的。

Server的類結構圖以下:

①Server的類結構圖

它的標準實現類 StandardServer 實現了上面這些方法,同時也實現 了Lifecycle、MbeanRegistration 兩個接口的全部方法,下面主要看 一下 StandardServer重要的一個方法 addService的實現:

②StandardServer.addService

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
35
36
37
38
39
40
41
42
43
44
45
46
47
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 (initialized) {
 
try {
 
service.initialize();
 
} catch (LifecycleException e) {
 
e.printStackTrace(System.err);
 
}
 
}
 
if (started && (service instanceof Lifecycle)) {
 
try {
 
((Lifecycle) service).start();
 
} catch (LifecycleException e) {
 
;
 
}
 
}
 
support.firePropertyChange( "service" , null , service);
 
}
 
}

從上面第一句就知道了 Service和 Server是相互關聯的,Server也是和 Service 管理 Connector 同樣管理它,也是將 Service 放在 一個數組中,後面部分的代碼也是管理這個新加進來的 Service 的生 命週期。Tomcat6 中也是沒有什麼變化的。

組件的生命線「Lifecycle」

前面一直在說 Service 和 Server 管理它下面組件的生命週期,那它 們是如何管理的呢?

Tomcat 中組件的生命週期是經過Lifecycle 接口來控制的,組件只 要繼承這個接口並實現其中的方法就能夠統一被擁有它的組件控制 了,這樣一層一層的直到一個最高級的組件就能夠控制 Tomcat 中 全部組件的生命週期,這個最高的組件就是 Server,而控制Server的是 Startup,也就是您啓動和關閉Tomcat。

下面是 Lifecycle 接口的類結構圖:

①Lifecycle類結構圖

除了控制生命週期的 Start 和 Stop 方法外還有一個監聽機制,在生命週期開始和結束的時候作一些額外的操做。這個機制在其它的框架中也被使用,如在Spring 中。關於這個設計模式會在後面介紹。

Lifecycle接口的方法的實現都在其它組件中,就像前面中說的,組件的生命週期由包含它的父組件控制,因此它的 Start 方法天然就是調用它下面的組件的 Start 方法,Stop 方法也是同樣。如在 Server 中 Start 方法就會調用Service組件的 Start方法,Server 的 Start方法代碼以下:

②StandardServer.Start

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
public void start() throws LifecycleException {
 
if (started) {
 
log.debug(sm.getString( "standardServer.start.started" ));
 
return ;
 
}
 
lifecycle.fireLifecycleEvent(BEFORE_START_EVENT,  null );
 
lifecycle.fireLifecycleEvent(START_EVENT,  null );
 
started = true ;
 
synchronized (services) {
 
for ( int i = 0 ; i < services.length; i++) {
 
if (services[i] instanceof Lifecycle)
 
((Lifecycle) services[i]).start();
 
}
 
}
 
lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null );
 
}

監聽的代碼會包圍Service組件的啓動過程,就是簡單的循環啓動全部Service組件的Start方法,可是全部Service必需要實現Lifecycle接口,這樣作會更加靈活。

Server的 Stop 方法代碼以下:

③StandardServer.Stop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public void stop() throws LifecycleException {
 
if (!started)
 
return ;
 
lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null );
 
lifecycle.fireLifecycleEvent(STOP_EVENT,  null );
 
started = false ;
 
for ( int i = 0 ; i < services.length; i++) {
 
if (services[i] instanceof Lifecycle)
 
((Lifecycle) services[i]).stop();
 
}
 
lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null );
 
}

它所要作的事情也和Start方法差很少。

Connector組件

Connector組件是Tomcat中兩個核心組件之一,它的主要任務是負責接收瀏覽器的發過來的tcp鏈接請求,建立個Request 和處理這個請求並把產生的Request 和 Response對象傳給處理這個請求的線程,處理這個請求的線程就是Container 組件要作的事了。

因爲這個過程比較複雜,大致的流程能夠用下面的順序圖來解釋:

①Connector處理一次請求順序圖

Tomcat5 中默認的 Connector 是 Coyote,這個 Connector 是能夠選擇替換的。Connector 最重要的功能就是接收鏈接請求而後分配線 程讓 Container 來處理這個請求,因此這必然是多線程的,多線程的處理是 Connector 設計的核心。Tomcat5將這個過程更加細化,它將 Connector劃分紅 Connector、Processor、Protocol, 另外Coyote也定義本身的Request 和 Response對象。

下面主要看一下 Tomcat 中如何處理多線程的鏈接請求,先看一下Connector的主要類圖:

② Connector的主要類圖

看一下HttpConnector的Start 方法:

③HttpConnector.Start

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
public void start() throws LifecycleException {
 
if (started)
 
throw new LifecycleException
 
(sm.getString( "httpConnector.alreadyStarted" ));
 
threadName = "HttpConnector[" + port + "]" ;
 
lifecycle.fireLifecycleEvent(START_EVENT,  null );
 
started = true ;
 
threadStart();
 
while (curProcessors < minProcessors) {
 
if ((maxProcessors > 0 ) && (curProcessors >= maxProcessors))
 
break ;
 
HttpProcessor processor = newProcessor();
 
recycle(processor);
 
}
 
}

threadStart()執行就會進入等待請求的狀態,直到一個新的請求到來纔會激活它繼續執行,這個激活是在HttpProcessor 的 assign 方法中,這個方法是代碼以下 :

④ HttpProcessor.assign

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
synchronized void assign(Socket socket) {
 
while (available) {
 
try {
 
wait();
 
} catch (InterruptedException e) {
 
—————————————————————————————
}
 
}
 
this .socket = socket;
 
available = true ;
 
notifyAll();
 
if ((debug >= 1 ) && (socket != null ))
 
log( " An incoming request is being assigned" );
 
}

建立 HttpProcessor 對象是會把 available 設爲 false,因此當請求 到來時不會進入 while循環,將請求的socket 賦給當期處理的 socket,並將 available設爲true,當 available設爲true 是 HttpProcessor的 run方法將被激活,接下去將會處理此次請求。

Run方法代碼以下:

⑤HttpProcessor.Run

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
public void run() {
 
while (!stopped) {
 
Socket socket = await();
 
if (socket == null )
 
continue ;
 
try {
 
process(socket);
 
} catch (Throwable t) {
 
log( "process.invoke" , t);
 
}
 
connector.recycle( this );
 
}
 
—————————————————————————————
synchronized (threadSync) {
 
threadSync.notifyAll();
 
}
 
}

解析 socket 的過程在 process 方法中,process 方法的代碼片斷如 下:

⑥HttpProcessor.process

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
private void process(Socket socket) {
 
boolean ok = true ;
 
boolean finishResponse = true ;
 
SocketInputStream input = null ;
 
OutputStream output = null ;
 
try {
 
input = new SocketInputStream(socket.getInputStream(),connector.getBufferSize());
} catch (Exception e) {
 
log( "process.create" , e);
 
ok = false ;
 
}
 
keepAlive = true ;
 
while (!stopped && ok && keepAlive) {
 
finishResponse = true ;
 
try {
 
request.setStream(input);
 
request.setResponse(response);
 
output = socket.getOutputStream();
 
response.setStream(output);
 
response.setRequest(request);
 
((HttpServletResponse)  response.getResponse())
 
—————————————————————————————
.setHeader( "Server" , SERVER_INFO);
 
} catch (Exception e) {
 
log( "process.create" , e);
 
ok = false ;
 
}
 
try {
 
if (ok) {
 
parseConnection(socket);
 
parseRequest(input, output);
 
if (!request.getRequest().getProtocol().startsWith( "HTTP/0" ))
 
parseHeaders(input);
 
if (http11) {
 
ackRequest(output);
 
if  (connector.isChunkingAllowed())
 
response.setAllowChunking( true );
 
}
 
}
 
try {
 
((HttpServletResponse)  response).setHeader
 
( "Date" ,  FastHttpDateFormat.getCurrentDate());
 
if (ok) {
 
connector.getContainer().invoke(request, response);
 
}
 
}
 
try {
 
shutdownInput(input);
 
socket.close();
 
} catch (IOException e) {
 
;
 
} catch (Throwable e) {
 
log( "process.invoke" , e);
 
}
 
socket = null ;
 
}

當 Connector將 socket 鏈接封裝成 request 和 response 對象後 接下來的事情就交給Container 來處理了。

Servlet容器「Container」

Container是容器的父接口,全部子容器都必須實現這個接口,Container容器的設計用的是典型的責任鏈的設計模式,它有四個子 容器組件構成,分別是:Engine、Host、Context、Wrapper,這四個組件不是平行的,而是父子關係,Engine包含 Host,Host 包含 Context,Context 包含 Wrapper。一般一個 Servlet class 對應一個 Wrapper,若是有多個 Servlet 就能夠定義多個 Wrapper,若是有多 個 Wrapper 就要定義一個更高的Container 了,如 Context, Context 一般就是對應下面這個配置:

①Server.xml

1
2
3
4
5
6
7
8
9
< Context
 
path = "/library"
 
docBase = "D:\projects\library\deploy\target\library.war"
 
reloadable = "true"
 
/>

②容器的整體設計

Context 還能夠定義在父容器Host中,Host 不是必須的,可是要運行 war 程序,就必需要 Host,由於 war 中必有 web.xml 文件, 這個文件的解析就須要 Host 了,若是要有多個 Host 就要定義一個 top 容器 Engine 了。而 Engine 沒有父容器了,一個 Engine 表明 一個完整的 Servlet 引擎。

那麼這些容器是如何協同工做的呢?先看一下它們之間的關係圖:

 四個容器的關係圖

當 Connector接受到一個鏈接請求時,將請求交給Container, Container是如何處理這個請求的?這四個組件是怎麼分工的,怎麼 把請求傳給特定的子容器的呢?又是如何將最終的請求交給 Servlet處理。下面是這個過程的時序圖:

②Engine和Host  處理請求的時序圖

這裏看到了 Valve 是否是很熟悉,沒錯 Valve 的設計在其餘框架中 也有用的,一樣Pipeline的原理也基本是類似的,它是一個管道,Engine和 Host都會執行這個 Pipeline,您能夠在這個管道上增長 任意的 Valve,Tomcat 會挨個執行這些Valve,並且四個組件都會 有本身的一套 Valve 集合。您怎麼才能定義本身的Valve 呢?在server.xml 文件中能夠添加,如給 Engine 和 Host 增長一個 Valve以下:

③Server.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<Engine defaultHost= "localhost" name= "Catalina" >
 
<Valve   className= "org.apache.catalina.valves.RequestDumperValve" />
 
………
 
<Host appBase= "webapps" autoDeploy= "true" name= "localhost" unpackWARs= "true"
 
xmlNamespaceAware= "false"  xmlValidation= "false" >
 
<Valve   className= "org.apache.catalina.valves.FastCommonAccessLogValve"
 
directory= "logs" prefix= "localhost_access_log." suffix= ".txt"
 
pattern= "common" resolveHosts= "false" />
 
…………
 
</Host>
 
</Engine>

StandardEngineValve和 StandardHostValve是 Engine和 Host的默認的 Valve,它們是最後一個Valve 負責將請求傳給它們的子 容器,以繼續往下執行。

前面是 Engine和 Host容器的請求過程,下面看Context 和Wrapper 容器時如何處理請求的。下面是處理請求的時序圖:

④Context 和wrapper  的處理請求時序圖

從 Tomcat5 開始,子容器的路由放在了 request 中,request 中保 存了當前請求正在處理的 Host、Context 和 wrapper。

③Engine 容器

Engine容器比較簡單,它只定義了一些基本的關聯關係,接口類圖以下:

①Engine 接口的類結構

它的標準實現類是StandardEngine,這個類注意一點就是 Engine沒有父容器了,若是調用 setParent 方法時將會報錯。添加子容器也 只能是 Host 類型的,代碼以下:

②StandardEngine. addChild

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void addChild(Container child) {
 
if (!(child instanceof Host))
 
throw new IllegalArgumentException
 
(sm.getString( "standardEngine.notHost" ));
 
super .addChild(child);
 
}
 
public void setParent(Container container) {
 
throw new IllegalArgumentException
 
(sm.getString( "standardEngine.notParent" ));
 
}

它的初始化方法也就是初始化和它相關聯的組件,以及一些事件的監聽。

④Host容器

Host是 Engine 的字容器,一個Host在 Engine中表明一個虛擬主機,這個虛擬主機的做用就是運行多個應用,它負責安裝和展開這些應用,而且標識這個應用以便可以區分它們。它的子容器一般是Context,它除了關聯子容器外,還有就是保存一個主機應該有的信 息。

①Host 相關的類圖

從上圖中能夠看出除了全部容器都繼承的ContainerBase外, StandardHost還實現了Deployer 接口,上圖清楚的列出了這個接口的主要方法,這些方法都是安裝、展開、啓動和結束每一個web application。

Deployer 接口的實現是 StandardHostDeployer,這個類實現了的最要的幾個方法,Host能夠調用這些方法完成應用的部署等。

⑤Context容器

Context 表明 Servlet 的 Context,它具有了 Servlet 運行的基本環 境,理論上只要有Context 就能運行Servlet 了。簡單的 Tomcat能夠沒有 Engine 和 Host。

Context 最重要的功能就是管理它裏面的Servlet實例,Servlet 實 例在 Context 中是以Wrapper 出現的,還有一點就是 Context 如 何才能找到正確的Servlet 來執行它呢?Tomcat5之前是經過一 個 Mapper 類來管理的,Tomcat5 之後這個功能被移到了request 中,在前面的時序圖中就能夠發現獲取子容器都是經過request 來分配的。

Context 準備 Servlet 的運行環境是在 Start 方法開始的,這個方法 的代碼片斷以下:

①StandardContext.start

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
public synchronized void start() throws LifecycleException {
 
………
 
if ( !initialized ) {
 
try {
 
init();
 
} catch ( Exception ex ) {
 
throw new LifecycleException( "Error initializaing " , ex);
 
}
 
}
 
………
 
lifecycle.fireLifecycleEvent(BEFORE_START_EVENT,   null );
 
setAvailable( false );
 
setConfigured( false );
 
boolean ok = true ;
 
File configBase = getConfigBase();
 
if (configBase != null ) {
 
if (getConfigFile() == null ) {
 
File file = new File(configBase, getDefaultConfigFile());
 
setConfigFile(file.getPath());
 
try {
 
File appBaseFile = new File(getAppBase());
 
if (!appBaseFile.isAbsolute()) {
 
appBaseFile = new File(engineBase(), getAppBase());
 
}
 
String appBase = appBaseFile.getCanonicalPath();
 
String basePath =
 
( new  File(getBasePath())).getCanonicalPath();
 
if (!basePath.startsWith(appBase)) {
 
Server server = ServerFactory.getServer();
 
((StandardServer)  server).storeContext( this );
 
}
 
} catch (Exception e) {
 
log.warn( "Error storing config file" , e);
 
}
 
} else {
 
try {
 
String canConfigFile =  ( new File(getConfigFile())).getCanonicalPath();
if (!canConfigFile.startsWith (configBase.getCanonicalPath())) {
 
File file = new File(configBase, getDefaultConfigFile());
 
if (copy( new File(canConfigFile), file)) {
 
—————————————————————————————
setConfigFile(file.getPath());
 
}
 
}
 
} catch (Exception e) {
 
log.warn( "Error setting config file" , e);
 
}
 
}
 
}
………
 
Container children[] = findChildren();
 
for ( int i = 0 ; i < children.length; i++) {
 
if (children[i] instanceof Lifecycle)
 
((Lifecycle)  children[i]).start();
 
}
 
if (pipeline instanceof Lifecycle)
 
((Lifecycle) pipeline).start();
 
………
 
}

它主要是設置各類資源屬性和管理組件,還有很是重要的就是啓動子容器和 Pipeline。

咱們知道 Context 的配置文件中有個 reloadable 屬性,以下面配置:

②Server.xml

1
2
3
4
5
6
7
8
9
10
< Context
 
path = "/library"
 
—————————————————————————————
docBase = "D:\projects\library\deploy\target\library.war"
 
reloadable = "true"
 
/>

當這個 reloadable 設爲 true 時,war被修改後 Tomcat 會自動的從新加載這個應用。如何作到這點的呢? 這個功能是在StandardContext的 backgroundProcess 方法中實現的,這個方法的代碼以下:

③StandardContext. backgroundProcess

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public void backgroundProcess() {
 
if (!started) return ;
 
count = (count + 1 ) % managerChecksFrequency;
 
if ((getManager() != null ) && (count == 0 )) {
 
try {
 
getManager().backgroundProcess();
 
} catch ( Exception x ) {
 
log.warn( "Unable to perform background process on manager" ,x);
 
}
 
}
 
if (getLoader() != null ) {
 
if (reloadable && (getLoader().modified())) {
 
try {
 
Thread.currentThread().setContextClassLoader
 
(StandardContext. class .getClassLoader());
 
reload();
 
} finally {
 
if (getLoader() != null ) {
 
Thread.currentThread().setContextClassLoader
 
(getLoader().getClassLoader());
 
}
 
}
 
}
 
if (getLoader() instanceof WebappLoader) {
 
((WebappLoader)  getLoader()).closeJARs( false );
 
}
 
}
 
}

它會調用 reload 方法,而 reload方法會先調用 stop方法而後再調用 Start 方法,完成Context 的一次從新加載。能夠看出執行reload方法的條件是reloadable 爲 true 和應用被修改,那麼這個backgroundProcess 方法是怎麼被調用的呢?

這個方法是在 ContainerBase 類中定義的內部類ContainerBackgroundProcessor被週期調用的,這個類是運行在一個後臺線程中,它會週期的執行 run 方法,它的 run 方法會週期調 用全部容器的 backgroundProcess 方法,由於全部容器都會繼承ContainerBase類,因此全部容器都可以在backgroundProcess 方 法中定義週期執行的事件。

⑥Wrapper容器

Wrapper 表明一個Servlet,它負責管理一個 Servlet,包括的 Servlet的裝載、初始化、執行以及資源回收。Wrapper是最底層的 容器,它沒有子容器了,因此調用它的addChild 將會報錯。

Wrapper 的實現類是 StandardWrapper,StandardWrapper 還實現 了擁有一個 Servlet初始化信息的ServletConfig,由此看出 StandardWrapper 將直接和Servlet的各類信息打交道。

下面看一下很是重要的一個方法loadServlet,代碼片斷以下:

①StandardWrapper.loadServlet

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
public synchronized Servlet loadServlet() throws ServletException {
 
………
 
Servlet servlet;
 
try {
 
………
 
ClassLoader classLoader = loader.getClassLoader();
 
………
 
Class classClass = null ;
 
………
 
servlet = (Servlet) classClass.newInstance();
 
if ((servlet instanceof ContainerServlet) &&
 
(isContainerProvidedServlet(actualClass)  ||
 
((Context)getParent()).getPrivileged() )) {
 
((ContainerServlet)  servlet).setWrapper( this );
 
}
 
classLoadTime=( int ) (System.currentTimeMillis() -t1);
 
try {
 
instanceSupport.fireInstanceEvent(InstanceEvent.BEFORE_INIT_EVENT,servlet);
 
if ( System.getSecurityManager() != null ) {
 
Class[] classType = new Class[]{ServletConfig. class };
 
Object[] args = new Object[]{((ServletConfig)facade)};
 
SecurityUtil.doAsPrivilege( "init" ,servlet,classType,args);
 
} else {
 
servlet.init(facade);
 
}
 
if ((loadOnStartup >= 0 ) && (jspFile != null )) {
 
………
 
if ( System.getSecurityManager() != null ) {
 
Class[] classType = new Class[]{ServletRequest. class ,
 
ServletResponse. class };
 
Object[] args = new Object[]{req, res};
 
SecurityUtil.doAsPrivilege( "service" ,servlet,classType,args);
 
} else {
 
servlet.service(req, res);
 
}
 
}
 
instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,servlet);
 
………
 
return servlet;
 
}

它基本上描述了對Servlet 的操做,當裝載了Servlet後就會調用Servlet的 init方法,同時會傳一個StandardWrapperFacade對象給Servlet,這個對象包裝了StandardWrapper,ServletConfig 與它們的關係圖以下:

②ServletConf 與StandardWrapperFacade、StandardWrapper的關係

Servlet能夠得到的信息都在StandardWrapperFacade封裝,這些信息又是在StandardWrapper 對象中拿到的。因此 Servlet 能夠通 過 ServletConfig 拿到有限的容器的信息。

當 Servlet 被初始化完成後,就等着 StandardWrapperValve 去調用 它的 service 方法了,調用 service 方法以前要調用 Servlet 全部的 filter。

Tomcat中其它組件

Tomcat 還有其它重要的組件,如安全組件security、logger 日 志組件、session、mbeans、naming 等其它組件。這些組件共同爲Connector和 Container 提供必要的服務。

業務思想

關於Tomcat服務器的瞭解,算是很長時間的瞭解了,很好用。本博文中關於Tomcat系統架構的學習和總結,算是我的的理解,寫一寫總結總感受頗有必要,收穫頗多。多加使用,方感頗深。你們有什麼好的理解,歡迎交流!

相關文章
相關標籤/搜索