熱部署就是在服務器運行時從新部署項目,熱加載即在在運行時從新加載class,從而升級應用。html
一般狀況下在開發環境中咱們使用的是熱加載,由於熱加載的實現的方式在Web容器中啓動一個後臺線程,按期檢測相關文件的變化,若是有變化就從新加載類,這個過程不會清空Session。而在生產環境咱們通常應用的是熱部署,熱部署也是在Web應用後臺線程按期檢測,發現有變化就會從新加載整個Web應用,這種方式更加完全會清空Session。web
熱加載其實咱們在開發過程當中常常使用,例如咱們使用Idea開發時,咱們在設置頁面能夠進行設置,當修改文件時,咱們能夠選擇不重啓項目,選擇從新加載此文件。而在Tomcat中也能設置,Tomcat默認狀況下是不開啓熱加載的。須要在Tomcat路徑下的Context.xml
中配置reloadable
參數來開啓這個功能。服務器
<Context reloadable="true"/>
咱們演示一下Tomcat是如何熱加載的。在webapp下咱們新建了一個項目,裏面的Servlet文件以下架構
public class MyServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("MyServlet 在處理 get()請求..."); PrintWriter out = response.getWriter(); response.setContentType("text/html;charset=utf-8"); out.println("<strong>>My Servlet Version1!</strong><br>"); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("MyServlet 在處理 post()請求..."); PrintWriter out = response.getWriter(); response.setContentType("text/html;charset=utf-8"); out.println("<strong>>My Servlet Version1!</strong><br>"); } }
目錄結構以下app
-webapp -- mywebapp -- WEB-INF -- web.xml -- classes -- MyServlet.class
爲了演示Tomcat運行時能修改class文件可以動態加載。咱們分爲如下三步webapp
http://localhost:8080/mywebapp/myservlet
,觀察頁面輸出MyServlet
文件後覆蓋原來的class文件下面直接用動態圖演示效果,更直觀一些。ide
咱們能夠看到在Tomcat運行的狀況下,直接替換class文件是可以直接生效的。那麼Tomcat是如何作到的呢?其實咱們能夠本身推導一下。post
那麼接下來咱們來驗證咱們的結論,看一下在Tomcat中是如何實現熱加載的。Tomcat要監聽class文件是否變化應該是新起了一個線程來觀測。那麼看到在Context的啓動方法中,看到調用了threadStart
的方法。this
protected void threadStart() { backgroundProcessorFuture = Container.getService(this).getServer().getUtilityExecutor() .scheduleWithFixedDelay(new ContainerBackgroundProcessor(),//要執行的Runnable backgroundProcessorDelay, //第一次執行延遲多久 backgroundProcessorDelay, //以後每次隔多久執行一次 TimeUnit.SECONDS); //時間單位 } }
其中在後臺開啓週期性的任務,使用了Java提供的ScheduledThreadPoolExecutor
。除了能週期性執行任務之外還有線程池的功能。上面代碼中調用了scheduleWithFixedDelay
方法,第一個傳入的參數就是要執行的任務。咱們接下來看任務類ContainerBackgroundProcessor
是如何實現的。.net
protected class ContainerBackgroundProcessor implements Runnable { @Override public void run() { // 請注意這裏傳入的參數是 " 宿主類 " 的實例 processChildren(ContainerBase.this); } protected void processChildren(Container container) { try { //1. 調用當前容器的 backgroundProcess 方法。 container.backgroundProcess(); //2. 遍歷全部的子容器,遞歸調用 processChildren, // 這樣當前容器的子孫都會被處理 Container[] children = container.findChildren(); for (int i = 0; i < children.length; i++) { // 這裏會判斷子容器若是已經啓動了後臺線程,那麼這裏就不會啓動了 if (children[i].getBackgroundProcessorDelay() <= 0) { processChildren(children[i]); } } } catch (Throwable t) { ... }
上面代碼中咱們能夠知道具體的後臺監聽代碼是在backgroundProcess
方法中實現的。那麼咱們看Context
容器的backgroundProcess
方法是如何實現的。
public void backgroundProcess() { //WebappLoader 週期性的檢查 WEB-INF/classes 和 WEB-INF/lib 目錄下的類文件 Loader loader = getLoader(); if (loader != null) { loader.backgroundProcess(); } ............省略 }
進去loader.backgroundProcess();
中咱們能夠看到
public void backgroundProcess() { //此處判斷熱加載開關是否開啓和監控的文件夾中文件是否有修改 if (reloadable && modified()) { try { Thread.currentThread().setContextClassLoader (WebappLoader.class.getClassLoader()); if (context != null) { //Context重啓 context.reload(); } } finally { if (context != null && context.getLoader() != null) { Thread.currentThread().setContextClassLoader (context.getLoader().getClassLoader()); } } } }
咱們能夠發現Tomcat熱加載的步驟
在這個過程當中,最重要的部分其實就是類加載器了。由於一個Context容器對應一個類加載器。因此在銷燬Context容器的時候也連帶着將其類加載器一併銷燬了。Context在重啓的過程當中也會建立新的類加載器來加載咱們新建的文件。
若是仍是不懂熱部署是什麼的,下面演示一遍應該就明白了。Tomcat在啓動的時候會將其目錄下webapp中war包解壓後而後封裝爲一個Context供外部訪問。那麼熱部署就是在程序運行時,若是咱們修改了War包中的東西。那麼Tomcat就會刪除以前的War包解壓的文件夾,從新解壓新的War包。
咱們發現上面動圖中在Tomcat運行時,咱們修改了War包的信息,它就會將原來的刪除而後從新生成一份。
咱們從上面的動圖中其實就看出了熱部署和熱加載的區別了。熱部署是將文件夾刪除而後從新解壓包。那麼熱加載是由Context容器負責的。那麼熱部署又是由哪一個容器負責呢?由於一個文件夾對應一個Context。既然文件夾都刪除了,那麼確定不是由Context容器負責了。那麼應該就是Context的父容器Host來負責。
咱們能夠看到Host
容器並無實現本身的backgroundProcess
方法。那麼它是如何監聽的呢?既然它沒有實現方法,確定是調用了父類的backgroundProcess
方法。咱們能夠看到在父類的backgroundProcess
中
@Override public void backgroundProcess() { . ...........省略 fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null); }
能夠看到週期事件的監聽器。而Host
的事件監聽器是HostConfig
類的lifecycleEvent
方法
@Override public void lifecycleEvent(LifecycleEvent event) { if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {// 週期事件 check(); } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {// 開始以前事件 beforeStart(); } else if (event.getType().equals(Lifecycle.START_EVENT)) { // 開始事件 start(); } else if (event.getType().equals(Lifecycle.STOP_EVENT)) { // 結束事件 stop(); } }
咱們能夠看check
方法
protected void check() { if (host.getAutoDeploy()) { // 檢查Host下全部已經部署的web應用 DeployedApplication[] apps = deployed.values().toArray(new DeployedApplication[0]); for (int i = 0; i < apps.length; i++) { if (!isServiced(apps[i].name)) checkResources(apps[i], false); } // 檢查Web應用是否有變化 if (host.getUndeployOldVersions()) { checkUndeploy(); } // 執行部署 deployApps(); } }
熱部署的步驟其實也能夠簡化爲三步驟