Java基于自定義類加載器實(shí)現(xiàn)熱部署過程解析
熱部署:
熱部署就是在不重啟應(yīng)用的情況下,當(dāng)類的定義即字節(jié)碼文件修改后,能夠替換該Class創(chuàng)建的對(duì)象。一般情況下,類的加載都是由系統(tǒng)自帶的類加載器完成,且對(duì)于同一個(gè)全限定名的java類,只能被加載一次,而且無法被卸載。可以使用自定義的 ClassLoader 替換系統(tǒng)的加載器,創(chuàng)建一個(gè)新的 ClassLoader,再用它加載 Class,得到的 Class 對(duì)象就是新的(因?yàn)椴皇峭粋€(gè)類加載器),再用該 Class 對(duì)象創(chuàng)建一個(gè)實(shí)例,從而實(shí)現(xiàn)動(dòng)態(tài)更新。如:修改 JSP 文件即生效,就是利用自定義的 ClassLoader 實(shí)現(xiàn)的。
還需要?jiǎng)?chuàng)建一個(gè)守護(hù)線程,不斷地檢查class文件是否被修改過,通過判斷文件的上次修改時(shí)間實(shí)現(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文件修改時(shí)間使用,substring(6)去掉開頭的file:/ } // 獲取一個(gè)任務(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)建一個(gè)任務(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); // 守護(hù)線程 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(); }}
遇到的坑:
剛開始自定義類加載器時(shí),重寫的是 loadClass(String name) 方法,但不斷地報(bào)錯(cuò),后來明白了,因?yàn)?Task 類實(shí)現(xiàn)了 Java.lang.Runnable 接口,且重寫 loadClass 方法破壞了雙親委派機(jī)制,導(dǎo)致了自定義的類加載器去加載 java.lang.Runnable,但被Java安全機(jī)制禁止了所以會(huì)報(bào)錯(cuò)。defineClass調(diào)用preDefineClass,preDefineClass 會(huì)檢查包名,如果以java開頭,就會(huì)拋出異常,因?yàn)樽層脩糇远x的類加載器來加載Java自帶的類庫會(huì)引起混亂。
于是又重寫findClass 方法,但還是不行,findClass方法總是得不到執(zhí)行,因?yàn)榫幾g好的類是在 classpath 下的,而自定義的 ClassLoader 的父加載器是 AppClassLoader,由于雙親委派機(jī)制,類就會(huì)被 Application ClassLoader來加載了。因此自定義的 findClass 方法就不會(huì)被執(zhí)行。解決方法是,向構(gòu)造器 ClassLoader(ClassLoader parent) 傳入null,或傳入 getSystemClassLoader().getParent()。
還有就是路徑問題:
path不以 / 開頭時(shí),默認(rèn)是從此類所在的包下取資源;path 以 / 開頭時(shí),則是從ClassPath根下獲取; URL getClass.getResource(String path) InputStream getClass().getResourceAsStream(String path) getResource('') 返回當(dāng)前類所在的包的路徑 getResource('/') 返回當(dāng)前的 classpath 根據(jù)路徑 path 不能以 / 開始,path 是從 classpath 根開始算的, 因?yàn)閏lassloader 不是用戶自定義的類,所以沒有相對(duì)路徑的配置文件可以獲取,所以默認(rèn)都是從哪個(gè)classpath 路徑下讀取,自然就沒有必要以 / 開頭了 。 URL Class.getClassLoader().getResource(String path) InputStream Class.getClassLoader().getResourceAsStream(String path)以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持好吧啦網(wǎng)。
相關(guān)文章:
1. Ajax實(shí)現(xiàn)表格中信息不刷新頁面進(jìn)行更新數(shù)據(jù)2. 詳解CSS偽元素的妙用單標(biāo)簽之美3. pip已經(jīng)安裝好第三方庫但pycharm中import時(shí)還是標(biāo)紅的解決方案4. 使用Python FastAPI構(gòu)建Web服務(wù)的實(shí)現(xiàn)5. UDDI FAQs6. msxml3.dll 錯(cuò)誤 800c0019 系統(tǒng)錯(cuò)誤:-2146697191解決方法7. HTML <!DOCTYPE> 標(biāo)簽8. Python collections模塊的使用方法9. CSS自定義滾動(dòng)條樣式案例詳解10. 將properties文件的配置設(shè)置為整個(gè)Web應(yīng)用的全局變量實(shí)現(xiàn)方法
