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