在J2EE的項目中,容器給我們提供的熱部署功能使得我們不用重啟動容器而修改我們的代碼。比如使用Weblogic,我們可以在Weblogic-application.xml中配置是否支持熱部署Servlet。查閱Weblogc 文檔,其實在Weblogic中,EJB組件也是可以熱部署的,但如何要熱部署EJB組件,Weblogc要求必須自定義ClassLoder。
JVM規范中沒有指定JVM支持動態加載修改過的類。類的加載,卸載對于程序員是透明的。如果我們要實現類的動態加載我們就要理解JVM本身類的加載與卸載的原理,實現熱部署。對于JVM加載類方面的資料在網上很多的,在這里我做簡單概述:
(1)JVM加載時通過ClassLoader加載的。
(2)JVM有3層繼承關系的ClassLoder 分別是:
-----
BootStrap類加載器 加載JRE/lib
-----------------ExtClassLoader
加載 JRE/lib/ext
----------AppClassLoader 加載ClassPath/
(3)為了安全性,JVM加載采用了雙親委派機制,如何理解呢,就是當需要加載一個類時,當前的ClassLoader先請求父ClassLoader,依次
類推,直到父類的ClassLoader無法加載時,才通過當前的ClassLoser加載,這就保證了像String這樣的類型必須使用JRE里面的, 使得
JRE lib 下類不會被修改。同時避免了ClassCaseException。
(4)在JVM中,一個實例是通過本身的類名+加載它的ClassLoader識別的,也就是說 不同的ClassLoader 加載同一個類在JVM是不同的。
(5)同一個ClassLoader是不允許多次加載一個類的,否則會報java.lang.LinkageError。attempted duplicate class definition for
name XXX,在下面的例子中會指出。
既然JVM不支持熱部署,那么要實現熱部署,就必須自定義ClassLoader,當類被修改過后,加載該類。下面通過代碼說明:
package classloader;
/**
* @author vma
*/
// 自定義一個類加載器
public class DynamicClassLoader extends ClassLoader {
public Class<?> findClass(byte[] b) throws ClassNotFoundException {
return defineClass(null, b, 0, b.length);
}
package classloader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
/**
* @author vma
*/
public class ManageClassLoader {
DynamicClassLoader dc =null;
Long lastModified = 0l;
Class c = null;
//加載類, 如果類文件修改過加載,如果沒有修改,返回當前的
public Class loadClass(String name) throws ClassNotFoundException, IOException{
if (isClassModified(name)){
dc = new DynamicClassLoader();
return c = dc.findClass(getBytes(name));
}
return c;
}
//判斷是否被修改過
private boolean isClassModified(String filename) {
boolean returnValue = false;
File file = new File(filename);
if (file.lastModified() > lastModified) {
returnValue = true;
}
return returnValue;
}
// 從本地讀取文件
private byte[] getBytes(String filename) throws IOException {
File file = new File(filename);
long len = file.length();
lastModified = file.lastModified();
byte raw[] = new byte[(int) len];
FileInputStream fin = new FileInputStream(file);
int r = fin.read(raw);
if (r != len) {
throw new IOException("Can't read all, " + r + " != " + len);
}
fin.close();
return raw;
}
}
測試類;Main 每隔 5s 加載一次
package classloader;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
*
* @author vma
*/
public class Main {
/**
* @param args the command line arguments
*/
public static void main(String[] args) throws ClassNotFoundException, IOException, NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException, InterruptedException {
String path = "D:\\deploy\\JDBC\\ClassLoader\\build\\classes\\classloader\\LocalClass.class";
ManageClassLoader mc = new ManageClassLoader();
while(true){
Class c = mc.loadClass(path);
Object o = c.newInstance();
Method m = c.getMethod("getName");
m.invoke(o);
System.out.println(c.getClassLoader());
Thread.sleep(5000);
}
}
被加載的類
/**
*
* @author vma
*/
public class LocalClass {
public void getName() {
System.out.println("hahaha ");
}
}
運行時,每隔5s 輸出:
hahaha
classloader.DynamicClassLoader@61de33
當我們修改
System.out.println("hahaha "); ---> System.out.println("changed
"); 編譯LocalClass后
輸出變為:
changed
classloader.DynamicClassLoader@173a10f
在
loadClass中, 我們必須重新初始化一個ClassLoader,負責就會違背
同一個ClassLoader是不允許多次加載一個類的。
public Class loadClass(String name) throws ClassNotFoundException, IOException{
if (isClassModified(name)){
dc = new DynamicClassLoader();
return c = dc.findClass(getBytes(name));
}
return c;
}
當然,容器的實現機制肯定及其完善,不可能周期性的加載,可能回通過監聽機制,動態加載修改過的類。但它的實現機制肯定也是重新
實例化一個ClassLoder,加載需要加載的類。