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

首頁 > 網站 > 建站經驗 > 正文

Tomcat 熱部署的實現原理詳解

2019-11-02 16:50:25
字體:
來源:轉載
供稿:網友

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("http://.","/");        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;  }}
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 吴堡县| 济南市| 富蕴县| 菏泽市| 桑日县| 渝中区| 将乐县| 玉树县| 四子王旗| 宁化县| 穆棱市| 乳源| 泗阳县| 金华市| 深圳市| 唐山市| 新宾| 尚志市| 左贡县| 玉林市| 安岳县| 合江县| 舒城县| 开鲁县| 合阳县| 九江市| 巍山| 岢岚县| 建阳市| 凤翔县| 舒兰市| 肥城市| 洛川县| 镇宁| 龙陵县| 娱乐| 桐乡市| 公安县| 盐山县| 辽阳县| 陆丰市|