超越reloadable=true, 在Tomcat運行時動態重載

爲什麼寫這篇文檔?

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


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

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

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

最終實現功能

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

閱讀須知

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

jvm  規範有關類加載器的章節

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

Tomcat  類加載機制

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

java  反射機制

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

ant

http://ant.apache.org/

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

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

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

修改過程

說明

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

web  app類加載器

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

資源列表

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

public  boolean  removeResourceEntry(String  name)  {

         if  (resourceEntries.containsKey(name))  {

                 resourceEntries.remove(name);

                 return  true;

         }

         return  false;

}

是否重載標誌

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

private  boolean  isReload  =  false;




           public  boolean  isReload()  {

                   return  isReload;

           }




           public  void  setReload(boolean  isReload)  {

                   this.isReload  =  isReload;

           }

動態類加載器

根據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);

       }

}

在webappClassLoader中添加DynamicClassLoader

添加屬性

private  DynamicClassLoader  dynamicClassLoader  =  new  DynamicClassLoader(this);

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

public  void  reCreateDynamicClassLoader()  {

                               dynamicClassLoader  =  new  DynamicClassLoader(this);

                       }

修改調用點

第832行,公開findClass方法

public  Class  findClass(String  name)  throws  ClassNotFoundException  {

第1569行,添加如下一行代碼。

if  (isReload)  removeResourceEntry(name);

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

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

改爲

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

第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);

測試代碼

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);

%>

org.AClass

package  org;




               public  class  AClass  {

                       public  AClass()  {

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

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

                       }




                       public  void  createBClass()  {

                               new  BClass();

                       }

               }

org.BClass

package  org;




               public  class  BClass  {

                       public  BClass()  {

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

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

                       }

               }

測試步驟

按照上述步驟修改Tomcat源碼並編譯。

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

編譯org.AClass和org.BClass

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

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

按下F5按鍵刷新瀏覽器。

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

Good  Luck!  :)))
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章