溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Java基于自定義類加載器如何實現(xiàn)熱部署

發(fā)布時間:2021-05-17 13:34:29 來源:億速云 閱讀:174 作者:小新 欄目:編程語言

這篇文章主要介紹Java基于自定義類加載器如何實現(xiàn)熱部署,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!

熱部署:

熱部署就是在不重啟應(yīng)用的情況下,當類的定義即字節(jié)碼文件修改后,能夠替換該Class創(chuàng)建的對象。一般情況下,類的加載都是由系統(tǒng)自帶的類加載器完成,且對于同一個全限定名的java類,只能被加載一次,而且無法被卸載。可以使用自定義的 ClassLoader 替換系統(tǒng)的加載器,創(chuàng)建一個新的 ClassLoader,再用它加載 Class,得到的 Class 對象就是新的(因為不是同一個類加載器),再用該 Class 對象創(chuàng)建一個實例,從而實現(xiàn)動態(tài)更新。如:修改 JSP 文件即生效,就是利用自定義的 ClassLoader 實現(xiàn)的。

還需要創(chuàng)建一個守護線程,不斷地檢查class文件是否被修改過,通過判斷文件的上次修改時間實現(xiàn)。

演示:

原來的程序:

Java基于自定義類加載器如何實現(xiàn)熱部署

修改后重新編譯:

Java基于自定義類加載器如何實現(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 方法破壞了雙親委派機制,導(dǎo)致了自定義的類加載器去加載 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根下獲?。?/p>

    • 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)

Java是什么

Java是一門面向?qū)ο缶幊陶Z言,可以編寫桌面應(yīng)用程序、Web應(yīng)用程序、分布式系統(tǒng)和嵌入式系統(tǒng)應(yīng)用程序。

以上是“Java基于自定義類加載器如何實現(xiàn)熱部署”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對大家有幫助,更多相關(guān)知識,歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI