Java之——類熱載入
一、問題闡述
有時候我們會遇到熱載入的需求,即在修改了類的程式碼後,不重啟的情況下動態的載入這個類
看到這個問題,我想到有兩種方式來實現:(能力有限,不知道還有沒有其他的方案)
1:把原來的類資訊解除安裝掉,然後重新載入此類。
2:新建一個類載入器(new),重新載入此類,不管原來的類資訊,等待垃圾回收它。
第一種方案是行不通的,因為java並沒有給我們提供手動解除安裝類資訊的功能,也就是執行時方法區內的類資訊是不能解除安裝的,除非這個類已經不再使用,這時GC會自動把它回收掉。所以我們只能通過第二種方案來實現。
幾個問題
在使用這種方案實現之前我們考慮幾個問題,注意:下面的問題是在這種方案下提出的,也在這種方案下回答,不適用與其他方案。
1:是不是所有的類都能進行熱載入呢?
我們程式的入口類都是系統類載入器載入的,也就是AppClassLoader載入的。當你重新使用系統類載入器載入這個類的時候是不會被重新載入的。因為虛擬機器會檢測這個類是否被載入過,如果已經被載入過,那麼就不會重新載入。所以由系統類載入器載入的類,是不能進行熱載入的。只有使用我們自定義的類載入器載入的類才能熱載入。
2:自定義類載入器的父載入器應該是哪個載入器?
我們自定義類載入器的父載入器有兩種選擇,一個是系統類載入器(AppClassLoader),另一種是擴充套件類載入器(ExtClassLoader)。首先我們要知道,擴充套件類載入器是系統類載入器的父載入器。我們先考慮第一種,如果父載入器是系統類載入器(當然如果你不指定父載入器,預設就是系統類載入器),那麼會出現一個現象,這個動態載入的類不能出現在系統類載入器的classpath下。因為如果在系統類載入器的classpath下的話,當你用自定義類載入器去載入的時候,會先使用父類載入器去載入這個類,如果父類載入器可以載入這個類就直接載入了,達不到熱載入的目的。所以我們必須把要熱載入的類從classpath下刪除。
在考慮第二種,如果父載入器是擴充套件類載入器,這時候熱載入的類就可以出現在classpath下,但又會出現另一種現象,這個類中不能引用由系統類載入器載入的類。因為這時候,自定義類載入器和系統類載入器是兄弟關係,他們兩個載入器載入的類是互不可見的。這個問題貌似是致命的。除非熱載入的類中只引用了擴充套件類載入器載入的類(大部分javax開頭的類)。所以我們自定義的類載入器的父載入器最好是系統類載入器。
3:熱載入的類要不要實現介面?
要不要實現介面,要根據由系統類載入器載入的類A中是否有熱載入類B的引用。如果有B的引用,那麼載入A的時候,系統類載入器就會去載入B,這時候B不在classpath下,就會載入報錯。這時候我們就需要為B定義一個介面,A類中只有介面的引用。這樣我們使用系統類載入器載入介面,使用自定義類載入器載入B類,這樣我們就可以熱載入B類。如果A中沒有B的引用的話,就靈活多了,可以實現介面,也可以不實現介面,都可以進行熱載入。
解決了這三個問題後,實現程式碼就已經在我們心中了。下面直接看程式碼:。
二、原始碼
具體程式碼如下:
1、自定義類載入器MyClassLoader
package com.lyz.hot.classloader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; /** * 自定義類載入器 * @author liuyazhuang * */ public class MyClassLoader extends ClassLoader { private String classpath; public MyClassLoader(String classpath){ super(ClassLoader.getSystemClassLoader()); this.classpath = classpath; } @Override public Class<?> findClass(String name) { System.out.println("載入類==="+name); byte[] data = loadClassData(name); return this.defineClass(name, data, 0, data.length); } public byte[] loadClassData(String name) { try { name = name.replace(".", "//"); FileInputStream is = new FileInputStream(new File(classpath + name + ".class")); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int b = 0; while ((b = is.read()) != -1) { baos.write(b); } is.close(); return baos.toByteArray(); } catch (Exception e) { e.printStackTrace(); } return null; } }
2、介面BaseManager
package com.lyz.hot.classloader;
/**
* @author liuyazhuang
* 此介面的子類需要動態更新
*/
public interface BaseManager {
public void logic();
}
3、LoadInfo
封裝類的載入資訊
package com.lyz.hot.classloader;
/**
* 封裝載入的類資訊
* @author liuyazhuang
*
*/
public class LoadInfo {
private MyClassLoader myLoader;
private long loadTime;
private BaseManager manager;
public LoadInfo(MyClassLoader myLoader, long loadTime) {
this.myLoader = myLoader;
this.loadTime = loadTime;
}
public MyClassLoader getMyLoader() {
return myLoader;
}
public void setMyLoader(MyClassLoader myLoader) {
this.myLoader = myLoader;
}
public long getLoadTime() {
return loadTime;
}
public void setLoadTime(long loadTime) {
this.loadTime = loadTime;
}
public BaseManager getManager() {
return manager;
}
public void setManager(BaseManager manager) {
this.manager = manager;
}
}
4、Logic
package com.lyz.hot.classloader;
/**
* 列印資訊
* @author liuyazhuang
*
*/
public class Logic {
public void logic() {
System.out.println("logic");
}
}
5、動態載入Magager的工廠類ManagerFactory
package com.lyz.hot.classloader;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
/**
* 動態載入Magager的工廠類
* @author liuyazhuang
*
*/
public class ManagerFactory {
/**
* 記錄熱載入類的載入資訊
*/
private static final Map<String, LoadInfo> loadTimeMap = new HashMap<String, LoadInfo>();
public static final String CLASS_PATH = "D:/";
public static final String MY_MANAGER = "com.lyz.hot.classloader.MyManager";
public static BaseManager getManager(String className) {
File loadFile = new File(CLASS_PATH + className.replaceAll("\\.", "/")
+ ".class");
long lastModified = loadFile.lastModified();
// 檢視是否被載入過 ,如果沒有被載入過則載入
if (loadTimeMap.get(className) == null) {
load(className, lastModified);
} else if (loadTimeMap.get(className).getLoadTime() != lastModified) {// 如果被載入過,檢視載入時間,如果該類已經被修改,則重新載入
load(className, lastModified);
}
return loadTimeMap.get(className).getManager();
}
private static void load(String className, long lastModified) {
MyClassLoader myLoader = new MyClassLoader(CLASS_PATH);
Class<?> loadClass = null;
try {
loadClass = myLoader.loadClass(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
BaseManager manager = newInstance(loadClass);
LoadInfo loadInfo2 = new LoadInfo(myLoader, lastModified);
loadInfo2.setManager(manager);
loadTimeMap.put(className, loadInfo2);
}
private static BaseManager newInstance(Class<?> cls) {
try {
return (BaseManager) cls.getConstructor(new Class[] {})
.newInstance(new Object[] {});
} catch (InstantiationException | IllegalAccessException
| IllegalArgumentException | InvocationTargetException
| NoSuchMethodException | SecurityException e) {
e.printStackTrace();
}
return null;
}
}
6、開啟執行緒不斷重新整理重新載入類MsgHandler
package com.lyz.hot.classloader;
/**
*
* 開啟執行緒不斷重新整理重新載入類
* @author liuyazhuang
*
*/
public class MsgHandler implements Runnable{
@Override
public void run() {
while(true){
BaseManager manager = ManagerFactory.getManager(ManagerFactory.MY_MANAGER);
manager.logic();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
new Thread(new MsgHandler()).start();
}
}
7、熱載入的類MyManager
package com.lyz.hot.classloader;
/**
* 動態載入的類MyManager
* @author liuyazhuang
*
*/
public class MyManager implements BaseManager {
@Override
public void logic() {
System.out.println("bbb");
System.out.println("logic");
}
}
執行後,修改MyManager類後輸出如下:
載入類===com.load.manager.MyManager
bbb
logic
bbb
logic
bbb
logic
bbb
logic
bbb
logic
載入類===com.load.manager.MyManager
aaaaaaaaaaaaaaaaaaaa
logic
aaaaaaaaaaaaaaaaaaaa
logic
aaaaaaaaaaaaaaaaaaaa
logic
aaaaaaaaaaaaaaaaaaaa
logic
可見,修改後重新載入了MyManager。到此熱載入成功。