tomcat熱部署的實現原理
一. 概述 名詞解釋:所謂熱部署,就是在應用正在執行的時候升級軟體,卻不需要重新啟動應用。 對於Java應用程式來說,熱部署就是在執行時更新Java類檔案。在基於Java的應用伺服器實現熱部署的過程中,類裝入器扮演著重要的角色。大多數基於Java的應用伺服器,包括EJB伺服器和Servlet容器,都支援熱部署。類裝入器不能重新裝入一個已經裝入的類,但只要使用一個新的類裝入器例項,就可以將類再次裝入一個正在執行的應用程式。 我們知道,現在大多數的web伺服器都支援熱部署,而對於熱部署的實現機制,網上講的卻不夠完善,下面我們就tomcat的熱部署實現機制,講解一下它是如何實現的: Tomcat的容器實現熱部署使用了兩種機制: 1. Classloader重寫,通過自定義classloader載入相應的jsp編譯後的class到JVM中。 2. 通過動態修改記憶體中的位元組碼,將修改過的class再次裝載到JVM中。 二. Classloader實現jsp的重新載入 Tomcat通過org.apache.jasper.servlet.JasperLoader實現了對jsp的載入,下面做個測試: 1. 新建一個web工程,並編寫一個jsp頁面,在jsp頁面中輸出該頁面的classloader,. 2. 啟動web伺服器,開啟jsp頁面,我們可以看到後臺輸出,該jsp的classloader是JasperLoader的一個例項。 3. 修改jsp,儲存並重新整理jsp頁面,再次檢視後臺輸出,此classloader例項已經不是剛才那個了,也就是說tomcat通過一個新的classloader再次裝載了該jsp。 4. 其實,對於每個jsp頁面tomcat都使用了一個獨立的classloader來裝載,每次修改完jsp後,tomcat都將使用一個新的classloader來裝載它。 關於如何使用自定義classloader來裝載一個class這裡就不說了,相信網上都能找到,JSP屬於一次性消費,每次呼叫容器將建立一個新的例項,屬於用完就扔的那種,但是對於這種實現方式卻很難用於其它情況下,如現在我們工程中很多都使用了單例,尤其是spring工程,在這種情況下使用新的classloader來載入修改後的類是不現實的,單例類將在記憶體中產生多個例項,而且這種方式無法改變當前記憶體中已有例項的行為,當然,tomcat也沒通過該方式實現class檔案的重新載入。 三. 通過代理修改記憶體中class的位元組碼 Tomcat中的class檔案是通過org.apache.catalina.loader. WebappClassLoader裝載的,同樣我們可以做個測試,測試過程與jsp測試類似,測試步驟就不說了,只說一下結果: 在熱部署的情況下,對於被該classloader 載入的class檔案,它的classloader始終是同一個WebappClassLoader,除非容器重啟了,相信做完這個實驗你就不會再認為tomcat是使用一個新的classloader來載入修改過的class了,而且對於有狀態的例項,之前該例項擁有的屬性和狀態都將儲存,並在下次執行時擁有了新的class的邏輯,這就是熱部署的神祕之處(其實每個例項只是儲存了該例項的狀態屬性,我們通過序列化物件就能看到物件中包含的狀態,最終的邏輯還是存在於class檔案中)。 下面的class重定義是通過:java.lang.instrument實現的,具體可參考相關文件。 下面我們看一下如何通過代理修改記憶體中的class位元組碼: 以下是一個簡單的熱部署代理實現類(程式碼比較粗糙,也沒什麼判斷): package agent; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; import java.util.Set; import java.util.Timer; import java.util.TreeSet; public class HotAgent { protected static Set public static void premain(String agentArgs, Instrumentation inst) throws Exception { ClassFileTransformer transformer =new ClassTransform(inst); inst.addTransformer(transformer); System.out.println("是否支援類的重定義:"+inst.isRedefineClassesSupported()); Timer timer=new Timer(); timer.schedule(new ReloadTask(inst),2000,2000); } } package agent; import java.lang.instrument.ClassFileTransformer; importjava.lang.instrument.IllegalClassFormatException; import java.lang.instrument.Instrumentation; import java.security.ProtectionDomain; public class ClassTransform. implements ClassFileTransformer { private Instrumentation inst; protected ClassTransform(Instrumentation inst){ this.inst=inst; } /** * 此方法在redefineClasses時或者初次載入時會被呼叫,也就是說在class被再次載入時會被呼叫, * 並且我們通過此方法可以動態修改class位元組碼,實現類似代理之類的功能,具體方法可使用ASM或者javasist, * 如果對位元組碼很熟悉的話可以直接修改位元組碼。 */ public byte[] transform(ClassLoader loader, String className, Class> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer)throws IllegalClassFormatException { byte[] transformed = null; HotAgent.clsnames.add(className); return null; } } package agent; import java.io.InputStream; import java.lang.instrument.ClassDefinition; import java.lang.instrument.Instrumentation; import java.util.TimerTask; public class ReloadTask extends TimerTask { private Instrumentation inst; protected ReloadTask(Instrumentation inst){ this.inst=inst; } @Override public void run() { try{ ClassDefinition[] cd=new ClassDefinition[1]; Class[] classes=inst.getAllLoadedClasses(); for(Class cls:classes){ if(cls.getClassLoader()==null||!cls.getClassLoader().getClass().getName().equals("sun.misc.Launcher$AppClassLoader")) continue; String name=cls.getName().replaceAll("\\.","/"); cd[0]=new ClassDefinition(cls,loadClassBytes(cls,name+".class")); inst.redefineClasses(cd); } }catch(Exception ex){ ex.printStackTrace(); } } private byte[] loadClassBytes(Class cls,String clsname) throws Exception{ System.out.println(clsname+":"+cls); InputStream is=cls.getClassLoader().getSystemClassLoader().getResourceAsStream(clsname); if(is==null)return null; byte[] bt=new byte[is.available()]; is.read(bt); is.close(); return bt; } } 以上是基本實現程式碼,需要元件為: 1. HotAgent(預載入) 2. ClassTransform(在載入class的時候可以修改class的位元組碼),本例中沒用到 3. ReloadTask(class定時載入器,以上程式碼僅供參考) 4. META-INF/MANIFEST.MF內容為:(引數一:支援class重定義;引數二:預載入類) Can-Redefine-Classes: true Premain-Class: agent.HotAgent 5. 將以上元件打包成jar檔案(到此,元件已經完成,下面為編寫測試類檔案)。 6. 新建一個java工程,編寫一個java邏輯類,並編寫一個Test類,在該測試類中呼叫邏輯類的方法,下面看下測試類程式碼: package test.redefine; public class Bean1 { public void test1(){ System.out.println("============================"); } } package test.redefine; public class Test { public static void main(String[] args)throws InterruptedException { Bean1 c1=new Bean1(); while(true){ c1.test1(); Thread.sleep(5000); } } } 執行測試類: java –javaagent:agent.jar test.redefine.Test 在測試類中,我們使用了一個死迴圈,定時呼叫邏輯類的方法。我們可以修改Bean1中的方法實現,將在不同時間看到不同的輸出結果,關於技術細節也沒什麼好講的了,相信大家都能明白。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/23071790/viewspace-702545/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Java伺服器熱部署的實現原理Java伺服器熱部署
- Eclipse/tomcat 如何實現應用熱部署和熱啟動EclipseTomcat熱部署
- Maven遠端Tomcat熱部署MavenTomcat熱部署
- springboot實現熱部署Spring Boot熱部署
- SpringBoot 實現熱部署Spring Boot熱部署
- idea中實現熱部署Idea熱部署
- Android熱更新實現原理Android
- 【Tomcat】Tomcat工作原理及簡單模擬實現Tomcat
- React Native 實現熱部署、差異化增量熱更新React Native熱部署
- 華納雲:如何實現tomcat自動化部署Tomcat
- 配置Eclpise+tomcat與實現JSP部署TomcatJS
- Keepalived - Keepalived 實現 tomcat雙機熱備Tomcat
- SpringBoot實現熱部署兩種方式!Spring Boot熱部署
- redission-tomcat:快速實現從單機部署到多機部署RedisTomcat
- ElasticSearch IK熱詞自動熱更新原理與Golang實現ElasticsearchGolang
- SpringBoot手動部署到Tomcat和自動化熱部署Spring BootTomcat熱部署
- 熱修復(一)原理與實現詳解
- 熱修復——深入淺出原理與實現
- Tomcat通過Redis實現session共享的完整部署記錄TomcatRedisSession
- Tomcat通過Memcached實現session共享的完整部署記錄TomcatSession
- 配置Eclpise+tomcat並實現JSP的編寫與部署TomcatJS
- jenkins+git+maven+tomcat 實現自動打包部署JenkinsGitMavenTomcat
- IOS熱更新-JSPatch實現原理+Patch現場恢復iOSJS
- Gradle裡配置jetty實現靜態資源的熱部署GradleJetty熱部署
- Android 熱更新實現原理及程式碼分析Android
- SpringBoot魔法堂:應用熱部署實踐與原理淺析Spring Boot熱部署
- 使用spring-loaded實現應用熱部署Spring熱部署
- SpringBoot整合devtools實現熱部署除錯Spring Bootdev熱部署除錯
- maven+jetty+idea+jrebel 實現專案熱部署MavenJettyIdea熱部署
- Spring-loaded實現熱部署-開發環境Spring熱部署開發環境
- Tomcat中的session實現TomcatSession
- 從Java的類載入機制談起:聊聊Java中如何實現熱部署(熱載入)Java熱部署
- tomcat原理Tomcat
- Tomcat 7 啟動分析(五)Lifecycle 機制和實現原理Tomcat
- Nginx+Tomcat實現負載均衡、動靜分離叢集部署NginxTomcat負載
- synchronized 的實現原理synchronized
- Category的實現原理Go
- nio的實現原理