粗淺看 Tomcat系統架構分析

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

整體結構

Tomcat 總體結構圖


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

以Service  作爲“婚姻”

我們將 Tomcat ConnectorContainer 作爲一個整體比作一對情 侶的話,Connector 主要負責對外交流,可以比作爲 BoyContainer 主要處理 Connector 接受的請求,主要是處理內部事務,可以比作Girl那麼這個 Service就是連接這對男女的結婚證了。是Service將它們連接在一起,共同組成一個家庭。當然要組成一個家庭還要很多其它的元素。

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

①Service接口


Service接口中定義的方法中可以看出,它主要是爲了關聯ConnectorContainer,同時會初始化它下面的其它組件,注意接 口中它並沒有規定一定要控制它下面的組件的生命週期。所有組件的 生命週期在一個 Lifecycle 接口中控制,這裏用到了一個重要的設 計模式,關於這個接口將在後面介紹。

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

②StandardService的類結構圖


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

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

StandardService. SetContainer

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

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 開始 ServiceServer 和容器類都繼承了MBeanRegistration接口,Mbeans 的管理更加合理。

以 Server  爲“居”

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

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

Server的類結構圖如下:

①Server的類結構圖


它的標準實現類 StandardServer 實現了上面這些方法,同時也實現 了 LifecycleMbeanRegistration 兩個接口的所有方法,下面主要看 一下 StandardServer 重要的一個方法 addService的實現:

②StandardServer.addService

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);

}

}

從上面第一句就知道了 ServiceServer是相互關聯的,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

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接口,這樣做會更加靈活。

ServerStop 方法代碼如下:

③StandardServer.Stop

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劃分成 ConnectorProcessorProtocol, 另外Coyote也定義自己的Request Response對象。

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

② Connector的主要類圖


看一下HttpConnectorStart 方法:

③HttpConnector.Start

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

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 HttpProcessorrun方法將被激活,接下去將會處理這次請求。

Run方法代碼如下:

⑤HttpProcessor.Run

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

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;

}

Connectorsocket 連接封裝成 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

<Context

path="/library"
 

docBase="D:\projects\library\deploy\target\library.war"

reloadable="true"

/>
②容器的總體設計

Context 還可以定義在父容器Host中,Host 不是必須的,但是要運行 war 序,就必須要 Hostwar 必有 web.xml 文件, 這個文件的解析就需要 Host 了,如果要有多個 Host 就要定義一個 top 容器 Engine 了。Engine 沒有父容器了,一個 Engine 代表 一個完整的 Servlet 引擎。

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

 四個容器的關係圖


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

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


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

③Server.xml

<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>

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

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

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


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

③Engine 容器

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

①Engine 接口的類結構


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

②StandardEngine. addChild

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容器

HostEngine 的字容器,一個HostEngine中代表一個虛擬主機,這個虛擬主機的作用就是運行多個應用,它負責安裝和展開這些應用,並且標識這個應用以便能夠區分它們。它的子容器通常是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

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

<Context

path="/library"
 
—————————————————————————————
docBase="D:\projects\library\deploy\target\library.war"

reloadable="true"

/>

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

③StandardContext. backgroundProcess

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 的實現類是 StandardWrapperStandardWrapper 實現 了擁有一個 Servlet 初始化信息的ServletConfig,由此看出 StandardWrapper 將直接和Servlet的各種信息打交道。

下面看一下非常重要的一個方法loadServlet,代碼片段如下:

①StandardWrapper.loadServlet
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系統架構的學習和總結,算是個人的理解,寫一寫總結總感覺很有必要,收穫頗多。多加使用,方感頗深。大家有什麼好的理解,歡迎交流!


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章