Java基于自定義類加載器實現(xiàn)熱部署過程解析
熱部署:
熱部署就是在不重啟應(yīng)用的情況下,當類的定義即字節(jié)碼文件修改后,能夠替換該Class創(chuàng)建的對象。一般情況下,類的加載都是由系統(tǒng)自帶的類加載器完成,且對于同一個全限定名的java類,只能被加載一次,而且無法被卸載??梢允褂米远x的 ClassLoader 替換系統(tǒng)的加載器,創(chuàng)建一個新的 ClassLoader,再用它加載 Class,得到的 Class 對象就是新的(因為不是同一個類加載器),再用該 Class 對象創(chuàng)建一個實例,從而實現(xiàn)動態(tài)更新。如:修改 JSP 文件即生效,就是利用自定義的 ClassLoader 實現(xiàn)的。
還需要創(chuàng)建一個守護線程,不斷地檢查class文件是否被修改過,通過判斷文件的上次修改時間實現(xiàn)。
演示:
原來的程序:

修改后重新編譯:

代碼:
package Dynamic;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.TimeUnit;
public class ClassLoadStudy {
public static void main(String[] args) throws Exception {
HotDeploy hot = new HotDeploy("Dynamic.Task");
hot.monitor();
while (true) {
TimeUnit.SECONDS.sleep(2);
hot.getTask().run();
}
}
}
// 熱部署
class HotDeploy {
private static volatile Runnable instance;
private final String FILE_NAME;
private final String CLASS_NAME;
public HotDeploy(String name) {
CLASS_NAME = name; // 類的完全限定名
name = name.replaceAll("\\.", "/") + ".class";
FILE_NAME = (getClass().getResource("/") + name).substring(6); // 判斷class文件修改時間使用,substring(6)去掉開頭的file:/
}
// 獲取一個任務(wù)
public Runnable getTask() {
if (instance == null) { // 雙重檢查鎖,單例,線程安全
synchronized (HotDeploy.class) {
if (instance == null) {
try {
instance = createTask();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
return instance;
}
// 創(chuàng)建一個任務(wù),重新加載 class 文件
private Runnable createTask() {
try {
Class clazz = MyClassLoader.getLoader().loadClass(CLASS_NAME);
if (clazz != null)
return (Runnable)clazz.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
// 監(jiān)視器,監(jiān)視class文件是否被修改過,如果是的話,則重新加載
public void monitor() throws IOException {
Thread t = new Thread(()->{
try {
long lastModified = Files.getLastModifiedTime(Path.of(FILE_NAME)).toMillis();
while(true) {
Thread.sleep(500);
long now = Files.getLastModifiedTime(Path.of(FILE_NAME)).toMillis();
if(now != lastModified) { // 如果class文件被修改過了
lastModified = now;
instance = createTask(); // 重新加載
}
}
} catch (InterruptedException | IOException e) {
e.printStackTrace();
}
});
t.setDaemon(true); // 守護線程
t.start();
}
}
// 自定義的類加載器
class MyClassLoader extends ClassLoader {
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
try {
String fileName = "/" + name.replaceAll("\\.", "/") + ".class";
InputStream is = getClass().getResourceAsStream(fileName);
byte[] b = is.readAllBytes();
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
throw new ClassNotFoundException(name);
}
}
public static MyClassLoader getLoader() {
return new MyClassLoader();
}
}
遇到的坑:
剛開始自定義類加載器時,重寫的是 loadClass(String name) 方法,但不斷地報錯,后來明白了,因為 Task 類實現(xiàn)了 Java.lang.Runnable 接口,且重寫 loadClass 方法破壞了雙親委派機制,導致了自定義的類加載器去加載 java.lang.Runnable,但被Java安全機制禁止了所以會報錯。defineClass調(diào)用preDefineClass,preDefineClass 會檢查包名,如果以java開頭,就會拋出異常,因為讓用戶自定義的類加載器來加載Java自帶的類庫會引起混亂。
于是又重寫findClass 方法,但還是不行,findClass方法總是得不到執(zhí)行,因為編譯好的類是在 classpath 下的,而自定義的 ClassLoader 的父加載器是 AppClassLoader,由于雙親委派機制,類就會被 Application ClassLoader來加載了。因此自定義的 findClass 方法就不會被執(zhí)行。解決方法是,向構(gòu)造器 ClassLoader(ClassLoader parent) 傳入null,或傳入 getSystemClassLoader().getParent()。
還有就是路徑問題:
- path不以 / 開頭時,默認是從此類所在的包下取資源;path 以 / 開頭時,則是從ClassPath根下獲??;
- URL getClass.getResource(String path)
- InputStream getClass().getResourceAsStream(String path)
- getResource("") 返回當前類所在的包的路徑
- getResource("/") 返回當前的 classpath 根據(jù)路徑
- path 不能以 / 開始,path 是從 classpath 根開始算的, 因為classloader 不是用戶自定義的類,所以沒有相對路徑的配置文件可以獲取,所以默認都是從哪個classpath 路徑下讀取,自然就沒有必要以 / 開頭了 。
- URL Class.getClassLoader().getResource(String path)
- InputStream Class.getClassLoader().getResourceAsStream(String path)
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
JAVA 根據(jù)設(shè)置的概率生成隨機數(shù)的方法
本篇文章主要介紹了JAVA 根據(jù)設(shè)置的概率生成隨機數(shù)的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-08-08
Springboot配置管理Externalized?Configuration深入探究
這篇文章主要介紹了Springboot配置管Externalized?Configuration深入探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2024-01-01
測試springboot項目出現(xiàn)Test Ignored的解決
這篇文章主要介紹了測試springboot項目出現(xiàn)Test Ignored的解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11
關(guān)于springboot 中使用httpclient或RestTemplate做MultipartFile文件跨服務(wù)傳輸
這篇文章主要介紹了關(guān)于springboot 中使用httpclient或RestTemplate做MultipartFile文件跨服務(wù)傳輸?shù)膯栴},本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-01-01
SpringBoot配置application.yml時遇到的錯誤及解決
這篇文章主要介紹了SpringBoot配置application.yml時遇到的錯誤及解決,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-07-07

