轉(zhuǎn)自:http://blog.csdn.net/cm4ever/article/details/616782
-
為什么寫這篇文檔?
使用過hibernate, spring或其他大型組件,寫過50個類以上的網(wǎng)絡(luò)應(yīng)用程序(web application)的開發(fā)者應(yīng)該知道,當系統(tǒng)中有很多類時,如果開啟了Tomcat的reloadable=true,那么每當相關(guān)文件改變時,Tomcat會停止web app并釋放內(nèi)存,然后重新加載web app.這實在是個浩大的工程。
所以我總是在想如果能有只重載某幾個類的功能,將極大的滿足我這個即時調(diào)試狂。
去年我在論壇上發(fā)帖,才發(fā)現(xiàn)已經(jīng)有一些應(yīng)用服務(wù)器具有了這個功能,比如WebLogic, WebSphere, 等等。好像還有一個很酷的名字,叫開發(fā)模式。看來我還是孤陋寡聞了點。
當然很多人都是在Tomcat上開發(fā),包括我。我很喜歡它的輕小,那些大內(nèi)存和高CPU消耗的應(yīng)用服務(wù)器不愧為硬件殺手,沒理由不改進Tomcat :)。
-
最終實現(xiàn)功能
我沒有時間去研究Tomcat的文件監(jiān)聽機制,也沒時間去把他寫成”開發(fā)模式”這么完整的功能,我最終實現(xiàn)的是,實現(xiàn)重載功能的測試jsp--很抱歉我還是沒辦法寫得更完整。當然,你可以在這個基礎(chǔ)上進行改進。
-
閱讀須知
閱讀本文,你應(yīng)該具備以下知識
-
jvm 規(guī)范有關(guān)類加載器的章節(jié)
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/
(好象該網(wǎng)址被不定時封鎖,有時能上,有時不能)
最好在你的電腦上安裝ant,因為Tomcat源碼包使用ant從互聯(lián)網(wǎng)獲得依賴包。不過我也是修改了一個錯誤才使它完全編譯通過。
當然,你也可以用其他IDE工具檢查并添加依賴包,在IDE中,其實你只需要添加jar直到使org.apache.catalina.loader.WebappClassLoader無錯即可。
-
修改過程
-
說明
新添加的代碼請?zhí)砑拥絡(luò)ava文件的末尾,因為我在說明行數(shù)的時候,盡量符合原始行數(shù)
-
web app類加載器
在Tomcat中,org.apache.catalina.loader.WebappClassLoader是web app的類加載器,所以需要修改它實現(xiàn)重載功能。
-
資源列表
在WebappClassLoader中,有一個Map類型屬性resourceEntries,它記載了web app中WEB-INF/classes目錄下所加載的類,因此當我們需要重載一個類時,我們需要先將它在resourceEntries里刪除,我編寫了一個方法方便調(diào)用:
public boolean removeResourceEntry(String name) {
if (resourceEntries.containsKey(name)) {
resourceEntries.remove(name);
return true;
}
return false;
}
-
是否重載標志
讓WebappClassLoader需要知道加載一個類是否使用重載的方式。所以我建立一個boolean 類型的屬性和實現(xiàn)它的getter/setter方法:
private boolean isReload = false;
public boolean isReload() {
return isReload;
}
public void setReload(boolean isReload) {
this.isReload = isReload;
}
-
動態(tài)類加載器
根據(jù)jvm類加載器規(guī)范,一個類加載器對象只能加載一個類1次,所以重載實際上是創(chuàng)建出另一個類加載器對象來加載同一個類。當然,我們不需要再創(chuàng)建一個WebappClassLoader,他太大而且加載規(guī)則很復(fù)雜,不是我們想要的,所以我們創(chuàng)建一個簡單的類加載器類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.*;
/**
* 動態(tài)類加載器
*
* @author peter
*
*/
public class DynamicClassLoader extends URLClassLoader {
/* 父類加載器 */
private ClassLoader parent = null;
/* 已加載類名列表 */
private List classNames = null;
/**
* 構(gòu)造器
*
* @param parent
* 父類加載器,這里傳入的是WebappClassLoader
*/
public DynamicClassLoader(ClassLoader parent) {
super(new URL[0]);
classNames = new ArrayList();
this.parent = parent;
}
/**
* 從類的二進制數(shù)據(jù)中加載類.
*
* @param name
* 類名
* @param classData
* 類的二進制數(shù)據(jù)
* @param codeSource
* 數(shù)據(jù)來源
* @return 成功加載的類
* @throws ClassNotFoundException
* 加載失敗拋出未找到此類異常
*/
public Class loadClass(String name, byte[] classData, CodeSource codeSource) throws ClassNotFoundException {
if (classNames.contains(name)) {
// System.out.println("此類已存在,調(diào)用 loadClass 方法加載.");
return loadClass(name);
} else {
// System.out.println("新類, 記錄到類名列表,并用類定義方法加載類");
classNames.add(name);
return defineClass(name, classData, 0, classData.length, codeSource);
}
}
/* *
* 重載此方法,當要加載的類不在類名列表中時,調(diào)用父類加載器方法加載.
* @see java.lang.ClassLoader#loadClass(java.lang.String)
*/
public Class loadClass(String name) throws ClassNotFoundException {
if (!classNames.contains(name)) {
//System.out.println("不在類名列表中,調(diào)用父類加載器方法加載");
return parent.loadClass(name);
}
return super.loadClass(name);
}
}
-
在webappClassLoader中添加DynamicClassLoader
-
添加屬性
private DynamicClassLoader dynamicClassLoader = new DynamicClassLoader(this);
-
添加重建方法,以便需要再次重載時替換掉上次的類加載器對象
public void reCreateDynamicClassLoader() {
dynamicClassLoader = new DynamicClassLoader(this);
}
-
修改調(diào)用點
-
第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的核心類,所以我們需要用反射代碼調(diào)用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();
// 如果你使用下面這行代碼,當重編譯類時,請稍微修改一下調(diào)用它的jsp,讓jsp也重新編譯
//org.AClass a = (org.AClass)A.newInstance();
// 下面這些代碼是測試當一個類不在DynamicClassLoader類名列表時的反應(yīng)
//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() {
// 修改輸出內(nèi)容確認Tomcat重新加載了類
System.out.println("AClass v3");
}
public void createBClass() {
new BClass();
}
}
-
org.BClass
package org;
public class BClass {
public BClass() {
//修改輸出內(nèi)容確認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! :)))