一篇文章徹底搞懂Tomcat熱部署與熱載入

2020-10-22 12:00:44

熱部署和熱載入是類似的,都是在不重新啟動Tomcat的情況下,使得應用的最新程式碼生效。
熱部署表示重新部署應用,它的執行主體是Host,表示主機。
熱載入表示重新載入class,它的執行主體是Context,表示應用。

Tomcat中的後臺執行緒

熱部署和熱載入都需要監聽相應的檔案或資料夾是否發生了變化。它們都是由Tomcat的後臺執行緒觸發的。

BackgroundProcessor就表示後臺執行緒。

每個容器都可以擁有一個BackgroundProcessor,但是預設情況下只有Engine容器會在啟動的時候啟動一個BackgroundProcessor執行緒。

該執行緒會每隔一段時間(可以設定,單位為秒),去執行後臺任務,先執行本容器定義的後臺任務,然後再執行子容器的定義的後臺任務,子容器的任務執行完成後會繼續執行其子容器的任務,直到沒有子容器為止。從這裡可以看出就算每個容器自己開啟一個BackgroundProcessor,也只不過是多了一個執行相同任務的執行緒而已,執行任務的效率有所提升。

對於後臺任務,所有容器會有一些統一的任務需要執行:

1. 叢集伺服器心跳
2. 如果一個容器擁有自己的類載入器,那麼檢視是否需要進行熱載入
3. 檢查Session是否過期
4. 執行每個容器對於的Realm對應的後臺任務
5. 執行每個容器中pipeline中的每個valve的後臺任務
6. 釋出PERIODIC_EVENT事件

在這個過程中的第2步中會觸發熱載入,第6步中會觸發熱部署

熱載入

我們可以在Context上設定reloadable屬性為true,這樣就表示該應用開啟了熱載入功能,預設是false。

熱載入觸發的條件是:WEB-INF/classes目錄下的檔案發生了變化,WEB-INF/lib目錄下的jar包新增、刪除、修改都會觸發熱載入。

熱載入大致流程為:

1. 設定當前Context不能接受以及處理請求標誌為true
2. 停止當前Context
3. 啟動當前Context
4. 設定當前Context不能接受以及處理請求標誌為false

我們著重來分析一下第2、3步。

我們不妨先來分析第3步-啟動當前Context的過程中會發生什麼事情:

  1. 建立一個每個應用都單獨自定義的WebappClassLoader
  2. 解析web.xml檔案,這一步會做很多事情,但是主要的目的是尋找定義的Servlet並把它新增到Context中去,而對於尋找Servlet需要進行兩個方面的尋找,一是從web.xml中尋找定義的Servlet,二是從尋找class檔案中新增了@WebServlet註解的類。大家很有可能認為,此時是不是會去載入我們定義的Servlet類,可以告訴大家的是,這個時候不會,Servlet類的載入是在後面步驟發生的,那麼這裡就有疑問了,我們要看一個類上是不是存在一個@WebServlet註解,應該要先載入這個類呀?Tomcat並沒有這麼做,它是直接先把class檔案當做一個普通檔案,然後看這個檔案對應的地方是否存在一個WebServlet註解,如果存在,則認為這個class檔案是一個Servlet,然後把這個class的全名封裝到Servlet物件中去,然後將Servlet物件新增到Context物件中。在解析web.xml時也是類似了,對於我們定義的Servlet,最後都會生成一個Servlet物件,然後記錄一個這個Servlet物件對應的class的全名,最後把Servlet物件新增到Context中去。
  3. 我們在使用Servlet的時候還會用其他的一些註解比如@ServletSecurity、@RunAs等等,對於這些註解是有特定功能的,Tomcat為了識別這個註解,此時就要去真正載入我們的Servlet類了。當然要不要識別這些註解是可以設定的,如果不識別,那麼這一步就不會發生了,那麼Servlet類的載入就會在有請求過來時才會進行類的載入。

載入類過程:

  1. 呼叫WebappClassLoaderBase的loadClass方法進行類的載入,該方法傳遞一個類的全限定名。
  2. 要載入一個類,先得找到這個類在哪裡,對應的是哪個classs檔案,所以Tomcat中有一個快取物件,該物件儲存了一個類的全限定名對應的資源路徑。當然,在第一次載入這個類時,這個快取是空的,所以這個時候就要去尋找這個類對應的class檔案地址,找到之後再快取。接下來就來分析是怎麼找到這個class檔案地址的。
  3. 其實查詢很容易,現在WEB-INF/classes/目錄下是否存在這個類,如果不存在就看WEB-INF/lib/目錄下的JAR包中是否存在這個類,最終如果找到就將進行快取,儲存一個類的全限定名對應的class檔案地址或jar包地址。
  4. 當知道這個類在哪了之後,就可以defineClass了,最終得到一個class物件,並且也會將這個class物件設定到我們的快取中,所以上文說的快取中,其實是這麼一個對映關係,一個類的全限定名對應這個類的檔案地址以及這個類的class物件
  5. 所以當下次再有情況需要載入class時,就可以直接取快取中的對應的class物件了。

這是第3步,我們在來看第2步:
對於第2步-停止當前Context,其實所做的事情比較單一,就是清空和銷燬,而其中跟類載入相關就是清空上文中的快取物件。

這樣,我們的熱載入就是先清空所有東西,然後重新啟動我們應用,但是因為這個的觸發條件基本上是class類發生了變化,所以熱載入的過程中關於應用其他的一些屬性是沒有發生變化的,比如你現在想在Context中新增一個Vavle是不會觸發熱載入的,而如果要達到這個效果就要用到熱部署

注意:雖然我們在熱載入的過程發現它是先停止再啟動,做法看似粗暴,但是這樣是價效比比較高的,並且這種方式至少比重新啟動Tomcat效率要高很多。

注意:熱載入不能用於war包

關於類的載入,這裡有一點是需要注意的,對於一個class檔案所表示的類,同一個類載入器的不同範例,都可以載入這個類,並且得到的class物件是不同的,回到熱載入,我們舉一個例子,我們現在有一個A類,一個自定義的WebappClassloader類,一開始先用一個WebappClassloader範例載入A類,那麼在jvm中就會存在一個A類的class物件,然後進行熱載入,先停止,再啟動,在停止的時候會殺掉當前應用的所有執行緒(除開真正執行程式碼的執行緒),再啟動時又會生成一個WebappClassloader範例來載入A類,如果熱載入之前的那個A類的class物件還沒有被回收的話,那麼此時jvm中其實會存在兩個A類的class物件,這是不衝突,因為class物件的唯一標誌是類載入器範例物件+類的全限定名

熱部署

BackgroundProcessor執行緒第六步會發出一個PERIODIC_EVENT事件,而HostConfig監聽了此事件,當接收到此事件後就會執行熱部署的檢查與操作。

對於一個資料夾部署的應用,通常會檢查以下資源是否發生變動:

/tomcat-7/webapps/應用名.war
/tomcat-7/webapps/應用名
/tomcat-7/webapps/應用名/META-INF/context.xml
/tomcat-7/conf/Catalina/localhost/應用名.xml
/tomcat-7/conf/context.xml

對於一個War部署的應用,會檢查以下資源是否發生變動:

/tomcat-7/webapps/應用名.war
/tomcat-7/conf/Catalina/localhost/應用名.xml
/tomcat-7/conf/context.xml

對於一個描述符部署的應用,會檢查以下資源是否發生變動:

/tomcat-7/conf/Catalina/localhost/應用名.xml
指定的DocBase目錄
/tomcat-7/conf/context.xml

一旦這些檔案或目錄發生了變化,就會觸發熱部署,當然熱部署也是有開關的,在Host上,預設是開啟的。這裡需要注意的是,對於一個目錄是否發生了變化,Tomcat只判斷了這個目錄的修改時間是否發生了變化,所以和熱載入是不衝突的,因為熱載入監聽的是WEB-INF/classes和WEB-INF/lib目錄,而熱部署監聽的是應用名那一層的目錄。

在講熱部署的過程之前,我們要先講一下應用部署的優先順序,對於一個應用,我們可以在四個地方進行定義:

server.xml中的context節點
/tomcat-7/conf/Catalina/localhost/應用名.xml
/tomcat-7/webapps/應用名.war
/tomcat-7/webapps/應用名

優先順序就是上面所列的順序,意思是同一個應用名,如果你在這個四個地方都設定了,那麼優先順序低的將不起作用。因為Tomcat在部署一個應用的時候,會先查一下這個應用名是否已經被部署過了。

熱部署的過程:

如果發生改變的是資料夾,比如/tomcat-7/webapps/應用名,那麼不會做什麼事情,只是會更新一下記錄的修改時間,這是因為這個/tomcat-7/webapps/應用名目錄下的檔案,要麼是jsp檔案,要麼是其他檔案,而Tomcat只會管jsp檔案,而對於jsp檔案如果發生了修改,jsp自帶的機制會處理修改的。

如果發生改變的是/tomcat-7/conf/Catalina/localhost/應用名.xml檔案,那麼就是先undeploy,然後再deploy,和熱載入其實類似。對於undeploy就不多說了,就是講當前應用從host從移除,這就包括了當前應用的停止和銷燬,然後還會從已部署列表中移除當前應用,然後呼叫deployApps()就可以重新部署應用了。

如果對於文章中有疑問歡迎在留言區進行提問,博主看到會及時解答。如果本文對你有幫助,請點個三連支援吧!