超越reloadable=true, 在Tomcat運行時動態重載類(version 5.0.28)

          轉自:http://blog.csdn.net/cm4ever/article/details/616782

          1. 為什么寫這篇文檔?

          使用過hibernate, spring或其他大型組件,寫過50個類以上的網絡應用程序(web application)的開發者應該知道,當系統中有很多類時,如果開啟了Tomcat的reloadable=true,那么每當相關文件改變時,Tomcat會停止web app并釋放內存,然后重新加載web app.這實在是個浩大的工程。

          所以我總是在想如果能有只重載某幾個類的功能,將極大的滿足我這個即時調試狂。

          去年我在論壇上發帖,才發現已經有一些應用服務器具有了這個功能,比如WebLogic, WebSphere, 等等。好像還有一個很酷的名字,叫開發模式。看來我還是孤陋寡聞了點。

          當然很多人都是在Tomcat上開發,包括我。我很喜歡它的輕小,那些大內存和高CPU消耗的應用服務器不愧為硬件殺手,沒理由不改進Tomcat :)

          1. 最終實現功能

          我沒有時間去研究Tomcat的文件監聽機制,也沒時間去把他寫成”開發模式”這么完整的功能,我最終實現的是,實現重載功能的測試jsp--很抱歉我還是沒辦法寫得更完整。當然,你可以在這個基礎上進行改進。

          1. 閱讀須知

          閱讀本文,你應該具備以下知識

            1. jvm 規范有關類加載器的章節

              http://java.sun.com/docs/books/vmspec/2nd-edition/html/VMSpecTOC.doc.html

            2. Tomcat 類加載機制

              http://www.huihoo.org/apache/tomcat/

            3. java 反射機制

              http://java.sun.com/docs/books/tutorial/reflect/

            4. ant

              http://ant.apache.org/

              (好象該網址被不定時封鎖,有時能上,有時不能)

          最好在你的電腦上安裝ant,因為Tomcat源碼包使用ant從互聯網獲得依賴包。不過我也是修改了一個錯誤才使它完全編譯通過。

          當然,你也可以用其他IDE工具檢查并添加依賴包,在IDE中,其實你只需要添加jar直到使org.apache.catalina.loader.WebappClassLoader無錯即可。

          1. 修改過程

            1. 說明

          新添加的代碼請添加到java文件的末尾,因為我在說明行數的時候,盡量符合原始行數

            1. web app類加載器

          Tomcat中,org.apache.catalina.loader.WebappClassLoaderweb app的類加載器,所以需要修改它實現重載功能。

            1. 資源列表

          WebappClassLoader中,有一個Map類型屬性resourceEntries,它記載了web appWEB-INF/classes目錄下所加載的類,因此當我們需要重載一個類時,我們需要先將它在resourceEntries里刪除,我編寫了一個方法方便調用:

          public boolean removeResourceEntry(String name) {

               if (resourceEntries.containsKey(name)) {

                   resourceEntries.remove(name);

                   return true;

               }

               return false;

          }

            1. 是否重載標志

          WebappClassLoader需要知道加載一個類是否使用重載的方式。所以我建立一個boolean 類型的屬性和實現它的getter/setter方法:

          private boolean isReload = false;


                public boolean isReload() {

                    return isReload;

                }


                public void setReload(boolean isReload) {

                    this.isReload = isReload;

                }

            1. 動態類加載器

          根據jvm類加載器規范,一個類加載器對象只能加載一個類1次,所以重載實際上是創建出另一個類加載器對象來加載同一個類。當然,我們不需要再創建一個WebappClassLoader,他太大而且加載規則很復雜,不是我們想要的,所以我們創建一個簡單的類加載器類org.apache.catalina.loader.DynamicClassLoader

          package org.apache.catalina.loader;


          import java.net.URL;

          import java.net.URLClassLoader;

          import java.security.CodeSource;

          import java.util.*;


          /**

          * 動態類加載器

          *

          * @author peter

          *

          */

          public class DynamicClassLoader extends URLClassLoader {

              /* 父類加載器 */

              private ClassLoader parent = null;


              /* 已加載類名列表 */

              private List classNames = null;


              /**

              * 構造器

              *

              * @param parent

              * 父類加載器,這里傳入的是WebappClassLoader

              */

              public DynamicClassLoader(ClassLoader parent) {

                  super(new URL[0]);

                  classNames = new ArrayList();

                  this.parent = parent;

              }


              /**

              * 從類的二進制數據中加載類.

              *

              * @param name

              * 類名

              * @param classData

              * 類的二進制數據

              * @param codeSource

              * 數據來源

              * @return 成功加載的類

              * @throws ClassNotFoundException

              * 加載失敗拋出未找到此類異常

              */

              public Class loadClass(String name, byte[] classData, CodeSource codeSource) throws ClassNotFoundException {

                  if (classNames.contains(name)) {

                      // System.out.println("此類已存在,調用 loadClass 方法加載.");

                      return loadClass(name);

                  } else {

                      // System.out.println("新類, 記錄到類名列表,并用類定義方法加載類");

                      classNames.add(name);

                      return defineClass(name, classData, 0, classData.length, codeSource);

                  }

              }


              /* *

              * 重載此方法,當要加載的類不在類名列表中時,調用父類加載器方法加載.

              * @see java.lang.ClassLoader#loadClass(java.lang.String)

              */

              public Class loadClass(String name) throws ClassNotFoundException {

                  if (!classNames.contains(name)) {

                      //System.out.println("不在類名列表中,調用父類加載器方法加載");

                      return parent.loadClass(name);

                  }

                  return super.loadClass(name);

              }

          }

            1. webappClassLoader中添加DynamicClassLoader

              1. 添加屬性

          private DynamicClassLoader dynamicClassLoader = new DynamicClassLoader(this);

              1. 添加重建方法,以便需要再次重載時替換掉上次的類加載器對象

          public void reCreateDynamicClassLoader() {

                          dynamicClassLoader = new DynamicClassLoader(this);

                      }

            1. 修改調用點

              1. 832行,公開findClass方法

          public Class findClass(String name) throws ClassNotFoundException {

              1. 1569行,添加如下一行代碼。

          if (isReload) removeResourceEntry(name);

              1. 1577行,這里好像是一個bug,具體原因我忘了-_-||

          if ((entry == null) || (entry.binaryContent == null))

          改為

          if ((entry == null) || (entry.loadedClass == null && entry.binaryContent == null))

              1. 1633~1636

          if (entry.loadedClass == null) {

                          clazz = defineClass(name, entry.binaryContent, 0, entry.binaryContent.length,

                              codeSource);

                      改為

                      byte[] classData = new byte[entry.binaryContent.length];

                      System.arraycopy(entry.binaryContent, 0, classData, 0,

                      classData.length);

                      if (entry.loadedClass == null) {

                          clazz = isReload ?

                              dynamicClassLoader.loadClass(name,

                              classData, codeSource) :

                              defineClass(name,

                              classData, 0, classData.length, codeSource);

            1. 測試代碼

              1. test.jsp

          我測試用的jsp$CATALINA_HOME/webapps/ROOT/test.jsp,由于webapp里面并不會顯式加載tomcat的核心類,所以我們需要用反射代碼調用WebappClassLoader的方法。代碼如下:

          <%

          ClassLoader loader = (Thread.currentThread().getContextClassLoader());

          Class clazz = loader.getClass();

          java.lang.reflect.Method setReload = clazz.getMethod("setReload", new Class[]{boolean.class});

          java.lang.reflect.Method reCreate = clazz.getMethod("reCreateDynamicClassLoader", null);

          java.lang.reflect.Method findClass = clazz.getMethod("findClass", new Class[]{String.class});

          reCreate.invoke(loader, null);

          setReload.invoke(loader, new Object[]{true});

          Class A = (Class)findClass.invoke(loader, new Object[]{"org.AClass"});

          setReload.invoke(loader, new Object[]{false});

          A.newInstance();

          // 如果你使用下面這行代碼,當重編譯類時,請稍微修改一下調用它的jsp,讓jsp也重新編譯

          //org.AClass a = (org.AClass)A.newInstance();

          // 下面這些代碼是測試當一個類不在DynamicClassLoader類名列表時的反應

          //a.test();

          //java.lang.reflect.Method test = a.getClass().getMethod("test", null);

          //test.invoke(a, null);

          %>

              1. org.AClass

          package org;


                  public class AClass {

                      public AClass() {

                          // 修改輸出內容確認Tomcat重新加載了類

                          System.out.println("AClass v3");

                      }


                      public void createBClass() {

                          new BClass();

                      }

                  }

              1. org.BClass

          package org;


                  public class BClass {

                      public BClass() {

                          //修改輸出內容確認Tomcat重新加載了類

                          System.out.println("BClass v1");

                      }

                  }

            1. 測試步驟

              1. 按照上述步驟修改Tomcat源碼并編譯。

              2. winzip/winrar/file-roller打開$CATALINA_HOME/server/lib/catalina.jar。把前面編譯完成后的org.apache.catalina.loader目錄下的class文件覆蓋jar中同名文件。

              3. 編譯org.AClassorg.BClass

              4. 啟動Tomcat并在瀏覽器中打開測試頁http://localhost:8080/test.jsp

              5. 修改org.AClass中的System.out.println();語句并重編譯類。

              6. 按下F5按鍵刷新瀏覽器。

              7. 查看Tomcat控制臺是否輸出了不同的語句?

              8. Good Luck! :)))



           

          posted on 2011-07-19 23:25 hijackwust 閱讀(619) 評論(0)  編輯  收藏


          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          <2011年7月>
          262728293012
          3456789
          10111213141516
          17181920212223
          24252627282930
          31123456

          導航

          統計

          常用鏈接

          留言簿(6)

          隨筆檔案(57)

          友情鏈接

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 稻城县| 平舆县| 邮箱| 桂阳县| 永嘉县| 湘潭市| 怀宁县| 上杭县| 浦县| 新乡市| 吴川市| 晋中市| 民丰县| 平邑县| 彰化市| 安多县| 虹口区| 阳山县| 嘉义县| 葵青区| 丹凤县| 嘉峪关市| 贵溪市| 灵宝市| 泰州市| 锦州市| 巴彦淖尔市| 大渡口区| 康定县| 新野县| 积石山| 新龙县| 台州市| 宝应县| 博野县| 梧州市| 鄱阳县| 牙克石市| 临沧市| 东明县| 桦甸市|