国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 服務器 > Web服務器 > 正文

Tomcat 熱部署的實現原理詳解

2024-09-01 13:50:07
字體:
來源:轉載
供稿:網友

Tomcat熱部署機制

 對于Java應用程序來說,熱部署就是在運行時更新Java類文件。在基于Java的應用服務器實現熱部署的過程中,類裝入器扮演著重要的角色。大多數基于Java的應用服務器,包括EJB服務器和Servlet容器,都支持熱部署。類裝入器不能重新裝入一個已經裝入的類,但只要使用一個新的類裝入器實例,就可以將類再次裝入一個正在運行的應用程序。

我們知道,現在大多數的web服務器都支持熱部署,而對于熱部署的實現機制,網上講的卻不夠完善,下面我們就Tomcat的熱部署實現機制,講解一下它是如何實現的:

 Tomcat的容器實現熱部署使用了兩種機制:

Classloader重寫,通過自定義classloader加載相應的jsp編譯后的class到JVM中。 通過動態修改內存中的字節碼,將修改過的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<String> clsnames=new TreeSet<String>();   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中的方法實現,將在不同時間看到不同的輸出結果,關于技術細節也沒什么好講的了,相信大家都能明白。

Tomcat 熱部署配置

<Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true" xmlNamespaceAware="false" xmlValidation="false">   <Context docBase="CPCWeb" path="/CPCWeb" reloadable="true" source="org.<span class="wp_keywordlink"><a href="http://res.importnew.com/eclipse" title="Eclipse ImportNew主頁" target="_blank">Eclipse</a></span>.jst.j2ee.server:CPCWeb"/></Host>

autoDeploy=”true” — 自動部署 reloadable=”true” — 自動加載

感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 噶尔县| 盐池县| 浮梁县| 兴义市| 平昌县| 凤城市| 巴林右旗| 平远县| 金秀| 建德市| 周宁县| 凯里市| 桂林市| 香港 | 开阳县| 定州市| 济南市| 宝丰县| 五指山市| 罗江县| 肇庆市| 安吉县| 元氏县| 平远县| 天柱县| 北安市| 普安县| 邹平县| 绿春县| 鹤岗市| 蓝山县| 洛川县| 嘉峪关市| 宝坻区| 隆化县| 巴东县| 广水市| 北宁市| 雷州市| 宾川县| 台中市|