本系列文章將整理到我在GitHub上的《Java面試指南》倉庫,更多精彩內容請到個人倉庫裏查看前端
https://github.com/h2pl/Java-Tutorialjava
喜歡的話麻煩點下Star哈python
文章首發於個人我的博客:git
www.how2playlife.com程序員
本文是微信公衆號【Java技術江湖】的《走進JavaWeb技術世界》其中一篇,本文部份內容來源於網絡,爲了把本文主題講得清晰透徹,也整合了不少我認爲不錯的技術博客內容,引用其中了一些比較好的博客文章,若有侵權,請聯繫做者。github
該系列博文會告訴你如何從入門到進階,從servlet到框架,從ssm再到SpringBoot,一步步地學習JavaWeb基礎知識,並上手進行實戰,接着瞭解JavaWeb項目中常常要使用的技術和組件,包括日誌組件、Maven、Junit,等等內容,以便讓你更完整地瞭解整個Java Web技術體系,造成本身的知識框架。web
爲了更好地總結和檢驗你的學習成果,本系列文章也會提供每一個知識點對應的面試題以及參考答案。面試
若是對本系列文章有什麼建議,或者是有什麼疑問的話,也能夠關注公衆號【Java技術江湖】聯繫做者,歡迎你參與本系列博文的創做和修訂。數據庫
文末贈送8000G的Java架構師學習資料,須要的朋友能夠到文末了解領取方式,資料包括Java基礎、進階、項目和架構師等免費學習資料,更有數據庫、分佈式、微服務等熱門技術學習視頻,內容豐富,兼顧原理和實踐,另外也將贈送做者原創的Java學習指南、Java程序員面試指南等乾貨資源) apache
Tomcat 的結構很複雜,可是 Tomcat 也很是的模塊化,找到了 Tomcat 最核心的模塊,您就抓住了 Tomcat 的「七寸」。下面是 Tomcat 的整體結構圖:
從上圖中能夠看出 Tomcat 的心臟是兩個組件:Connector 和 Container,關於這兩個組件將在後面詳細介紹。Connector 組件是能夠被替換,這樣能夠提供給服務器設計者更多的選擇,由於這個組件是如此重要,不只跟服務器的設計的自己,並且和不一樣的應用場景也十分相關,因此一個 Container 能夠選擇對應多個 Connector。多個 Connector 和一個 Container 就造成了一個 Service,Service 的概念你們都很熟悉了,有了 Service 就能夠對外提供服務了,可是 Service 還要一個生存的環境,必需要有人可以給她生命、掌握其生死大權,那就非 Server 莫屬了。因此整個 Tomcat 的生命週期由 Server 控制。
咱們將 Tomcat 中 Connector、Container 做爲一個總體比做一對情侶的話,Connector 主要負責對外交流,能夠比做爲 Boy,Container 主要處理 Connector 接受的請求,主要是處理內部事務,能夠比做爲 Girl。那麼這個 Service 就是鏈接這對男女的結婚證了。是 Service 將它們鏈接在一塊兒,共同組成一個家庭。固然要組成一個家庭還要不少其它的元素。
說白了,Service 只是在 Connector 和 Container 外面多包一層,把它們組裝在一塊兒,向外面提供服務,一個 Service 能夠設置多個 Connector,可是隻能有一個 Container 容器。這個 Service 接口的方法列表以下:
從 Service 接口中定義的方法中能夠看出,它主要是爲了關聯 Connector 和 Container,同時會初始化它下面的其它組件,注意接口中它並無規定必定要控制它下面的組件的生命週期。全部組件的生命週期在一個 Lifecycle 的接口中控制,這裏用到了一個重要的設計模式,關於這個接口將在後面介紹。
Tomcat 中 Service 接口的標準實現類是 StandardService 它不只實現了 Service 藉口同時還實現了 Lifecycle 接口,這樣它就能夠控制它下面的組件的生命週期了。StandardService 類結構圖以下:
從上圖中能夠看出除了 Service 接口的方法的實現以及控制組件生命週期的 Lifecycle 接口的實現,還有幾個方法是用於在事件監聽的方法的實現,不只是這個 Service 組件,Tomcat 中其它組件也一樣有這幾個方法,這也是一個典型的設計模式,將在後面介紹。
下面看一下 StandardService 中主要的幾個方法實現的代碼,下面是 setContainer 和 addConnector 方法的源碼:
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 沒有雙向關聯,否則這個關聯關係將會很難維護。
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 的管理更加合理。
前面說一對情侶由於 Service 而成爲一對夫妻,有了可以組成一個家庭的基本條件,可是它們還要有個實體的家,這是它們在社會上生存之本,有了家它們就能夠安心的爲人民服務了,一塊兒爲社會創造財富。
Server 要完成的任務很簡單,就是要可以提供一個接口讓其它程序可以訪問到這個 Service 集合、同時要維護它所包含的全部 Service 的生命週期,包括如何初始化、如何結束服務、如何找到別人要訪問的 Service。還有其它的一些次要的任務,如您住在這個地方要向當地政府去登記啊、可能還有要配合當地公安機關平常的安全檢查什麼的。
Server 的類結構圖以下:
它的標準實現類 StandardServer 實現了上面這些方法,同時也實現了 Lifecycle、MbeanRegistration 兩個接口的全部方法,下面主要看一下 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);複製代碼
}複製代碼
}
複製代碼
從上面第一句就知道了 Service 和 Server 是相互關聯的,Server 也是和 Service 管理 Connector 同樣管理它,也是將 Service 放在一個數組中,後面部分的代碼也是管理這個新加進來的 Service 的生命週期。Tomcat6 中也是沒有什麼變化的。
前面一直在說 Service 和 Server 管理它下面組件的生命週期,那它們是如何管理的呢?
Tomcat 中組件的生命週期是經過 Lifecycle 接口來控制的,組件只要繼承這個接口並實現其中的方法就能夠統一被擁有它的組件控制了,這樣一層一層的直到一個最高級的組件就能夠控制 Tomcat 中全部組件的生命週期,這個最高的組件就是 Server,而控制 Server 的是 Startup,也就是您啓動和關閉 Tomcat。
下面是 Lifecycle 接口的類結構圖:
除了控制生命週期的 Start 和 Stop 方法外還有一個監聽機制,在生命週期開始和結束的時候作一些額外的操做。這個機制在其它的框架中也被使用,如在 Spring 中。關於這個設計模式會在後面介紹。
Lifecycle 接口的方法的實現都在其它組件中,就像前面中說的,組件的生命週期由包含它的父組件控制,因此它的 Start 方法天然就是調用它下面的組件的 Start 方法,Stop 方法也是同樣。如在 Server 中 Start 方法就會調用 Service 組件的 Start 方法,Server 的 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 接口,這樣作會更加靈活。
Server 的 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 組件是 Tomcat 中兩個核心組件之一,它的主要任務是負責接收瀏覽器的發過來的 tcp 鏈接請求,建立一個 Request 和 Response 對象分別用於和請求端交換數據,而後會產生一個線程來處理這個請求並把產生的 Request 和 Response 對象傳給處理這個請求的線程,處理這個請求的線程就是 Container 組件要作的事了。
因爲這個過程比較複雜,大致的流程能夠用下面的順序圖來解釋:
(查看清晰大圖)
Tomcat5 中默認的 Connector 是 Coyote,這個 Connector 是能夠選擇替換的。Connector 最重要的功能就是接收鏈接請求而後分配線程讓 Container 來處理這個請求,因此這必然是多線程的,多線程的處理是 Connector 設計的核心。Tomcat5 將這個過程更加細化,它將 Connector 劃分紅 Connector、Processor、Protocol, 另外 Coyote 也定義本身的 Request 和 Response 對象。
下面主要看一下 Tomcat 中如何處理多線程的鏈接請求,先看一下 Connector 的主要類圖:
(查看清晰大圖)
看一下 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 方法中,這個方法是代碼以下 :
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 方法代碼以下:
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 方法的代碼片斷以下:
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 來處理了。
Container 是容器的父接口,全部子容器都必須實現這個接口,Container 容器的設計用的是典型的責任鏈的設計模式,它有四個子容器組件構成,分別是:Engine、Host、Context、Wrapper,這四個組件不是平行的,而是父子關係,Engine 包含 Host,Host 包含 Context,Context 包含 Wrapper。一般一個 Servlet class 對應一個 Wrapper,若是有多個 Servlet 就能夠定義多個 Wrapper,若是有多個 Wrapper 就要定義一個更高的 Container 了,如 Context,Context 一般就是對應下面這個配置:
<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 處理。下面是這個過程的時序圖:
(查看清晰大圖)
這裏看到了 Valve 是否是很熟悉,沒錯 Valve 的設計在其餘框架中也有用的,一樣 Pipeline 的原理也基本是類似的,它是一個管道,Engine 和 Host 都會執行這個 Pipeline,您能夠在這個管道上增長任意的 Valve,Tomcat 會挨個執行這些 Valve,並且四個組件都會有本身的一套 Valve 集合。您怎麼才能定義本身的 Valve 呢?在 server.xml 文件中能夠添加,如給 Engine 和 Host 增長一個 Valve 以下:
<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 容器時如何處理請求的。下面是處理請求的時序圖:
(查看清晰大圖)
從 Tomcat5 開始,子容器的路由放在了 request 中,request 中保存了當前請求正在處理的 Host、Context 和 wrapper。
Engine 容器比較簡單,它只定義了一些基本的關聯關係,接口類圖以下:
它的標準實現類是 StandardEngine,這個類注意一點就是 Engine 沒有父容器了,若是調用 setParent 方法時將會報錯。添加子容器也只能是 Host 類型的,代碼以下:
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 是 Engine 的字容器,一個 Host 在 Engine 中表明一個虛擬主機,這個虛擬主機的做用就是運行多個應用,它負責安裝和展開這些應用,而且標識這個應用以便可以區分它們。它的子容器一般是 Context,它除了關聯子容器外,還有就是保存一個主機應該有的信息。
下面是和 Host 相關的類關聯圖:
(查看清晰大圖)
從上圖中能夠看出除了全部容器都繼承的 ContainerBase 外,StandardHost 還實現了 Deployer 接口,上圖清楚的列出了這個接口的主要方法,這些方法都是安裝、展開、啓動和結束每一個 web application。
Deployer 接口的實現是 StandardHostDeployer,這個類實現了的最要的幾個方法,Host 能夠調用這些方法完成應用的部署等。
Context 表明 Servlet 的 Context,它具有了 Servlet 運行的基本環境,理論上只要有 Context 就能運行 Servlet 了。簡單的 Tomcat 能夠沒有 Engine 和 Host。
Context 最重要的功能就是管理它裏面的 Servlet 實例,Servlet 實例在 Context 中是以 Wrapper 出現的,還有一點就是 Context 如何才能找到正確的 Servlet 來執行它呢? Tomcat5 之前是經過一個 Mapper 類來管理的,Tomcat5 之後這個功能被移到了 request 中,在前面的時序圖中就能夠發現獲取子容器都是經過 request 來分配的。
Context 準備 Servlet 的運行環境是在 Start 方法開始的,這個方法的代碼片斷以下:
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 屬性,以下面配置:
複製代碼
<Context複製代碼
path="/library"複製代碼
docBase="D:\projects\library\deploy\target\library.war"複製代碼
reloadable="true"複製代碼
/>
複製代碼
當這個 reloadable 設爲 true 時,war 被修改後 Tomcat 會自動的從新加載這個應用。如何作到這點的呢 ? 這個功能是在 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 表明一個 Servlet,它負責管理一個 Servlet,包括的 Servlet 的裝載、初始化、執行以及資源回收。Wrapper 是最底層的容器,它沒有子容器了,因此調用它的 addChild 將會報錯。
Wrapper 的實現類是 StandardWrapper,StandardWrapper 還實現了擁有一個 Servlet 初始化信息的 ServletConfig,由此看出 StandardWrapper 將直接和 Servlet 的各類信息打交道。
下面看一下很是重要的一個方法 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 與它們的關係圖以下:
Servlet 能夠得到的信息都在 StandardWrapperFacade 封裝,這些信息又是在 StandardWrapper 對象中拿到的。因此 Servlet 能夠經過 ServletConfig 拿到有限的容器的信息。
當 Servlet 被初始化完成後,就等着 StandardWrapperValve 去調用它的 service 方法了,調用 service 方法以前要調用 Servlet 全部的 filter。
Tomcat 還有其它重要的組件,如安全組件 security、logger 日誌組件、session、mbeans、naming 等其它組件。這些組件共同爲 Connector 和 Container 提供必要的服務。
黃小斜是 985 碩士,阿里巴巴Java工程師,在自學編程、技術求職、Java學習等方面有豐富經驗和獨到看法,但願幫助到更多想要從事互聯網行業的程序員們。做者專一於 JAVA 後端技術棧,熱衷於分享程序員乾貨、學習經驗、求職心得,以及自學編程和Java技術棧的相關乾貨。黃小斜是一個斜槓青年,堅持學習和寫做,相信終身學習的力量,但願和更多的程序員交朋友,一塊兒進步和成長!
原創電子書:關注微信公衆號【程序員黃小斜】後回覆【原創電子書】便可領取我原創的電子書《菜鳥程序員修煉手冊:從技術小白到阿里巴巴Java工程師》這份電子書總結了我2年的Java學習之路,包括學習方法、技術總結、求職經驗和麪試技巧等內容,已經幫助不少的程序員拿到了心儀的offer!
程序員3T技術學習資源: 一些程序員學習技術的資源大禮包,關注公衆號後,後臺回覆關鍵字 「資料」 便可免費無套路獲取,包括Java、python、C++、大數據、機器學習、前端、移動端等方向的技術資料。
若是你們想要實時關注我更新的文章以及分享的乾貨的話,能夠關注個人微信公衆號【Java技術江湖】
這是一位阿里 Java 工程師的技術小站。做者黃小斜,專一 Java 相關技術:SSM、SpringBoot、MySQL、分佈式、中間件、集羣、Linux、網絡、多線程,偶爾講點Docker、ELK,同時也分享技術乾貨和學習經驗,致力於Java全棧開發!
Java工程師必備學習資源:關注公衆號後回覆」Java「便可領取 Java基礎、進階、項目和架構師等免費學習資料,更有數據庫、分佈式、微服務等熱門技術學習視頻,內容豐富,兼顧原理和實踐,另外也將贈送做者原創的Java學習指南、Java程序員面試指南等乾貨資源
黃小斜是 985 碩士,阿里巴巴Java工程師,在自學編程、技術求職、Java學習等方面有豐富經驗和獨到看法,但願幫助到更多想要從事互聯網行業的程序員們。做者專一於 JAVA 後端技術棧,熱衷於分享程序員乾貨、學習經驗、求職心得,以及自學編程和Java技術棧的相關乾貨。黃小斜是一個斜槓青年,堅持學習和寫做,相信終身學習的力量,但願和更多的程序員交朋友,一塊兒進步和成長!
原創電子書:關注微信公衆號【程序員黃小斜】後回覆【原創電子書】便可領取我原創的電子書《菜鳥程序員修煉手冊:從技術小白到阿里巴巴Java工程師》這份電子書總結了我2年的Java學習之路,包括學習方法、技術總結、求職經驗和麪試技巧等內容,已經幫助不少的程序員拿到了心儀的offer!
程序員3T技術學習資源: 一些程序員學習技術的資源大禮包,關注公衆號後,後臺回覆關鍵字 「資料」 便可免費無套路獲取,包括Java、python、C++、大數據、機器學習、前端、移動端等方向的技術資料。
若是你們想要實時關注我更新的文章以及分享的乾貨的話,能夠關注個人微信公衆號【Java技術江湖】
這是一位阿里 Java 工程師的技術小站。做者黃小斜,專一 Java 相關技術:SSM、SpringBoot、MySQL、分佈式、中間件、集羣、Linux、網絡、多線程,偶爾講點Docker、ELK,同時也分享技術乾貨和學習經驗,致力於Java全棧開發!
Java工程師必備學習資源:關注公衆號後回覆」Java「便可領取 Java基礎、進階、項目和架構師等免費學習資料,更有數據庫、分佈式、微服務等熱門技術學習視頻,內容豐富,兼顧原理和實踐,另外也將贈送做者原創的Java學習指南、Java程序員面試指南等乾貨資源