原來類加載器這么簡單,手把手教你用純java代碼實現(xiàn)熱部署

      網(wǎng)友投稿 1196 2022-05-28

      1、什么是熱部署

      相信大家在寫代碼的時候都有這個困擾,就是每次我改完代碼之后都需要重啟項目才能看到結果,小項目還好,啟動不會占用太多時間,如果是一個大項目,每次重啟都要耗費很多時間的情況下,這無疑對開發(fā)效率都會大幅下降,那么有沒有這樣一種技術? 我修改后無需重啟服務器,就可以馬上看到效果?我可以很肯定地回答你:“有”,就是熱部署技術,在你修改完代碼之后,這項技術會自動幫你重新加載被修改后class文件,真正實現(xiàn)實時查看改動的結果;

      2、準備

      要知道熱部署,就得先了解class加載機制,在我們啟動項目的時候,首先編輯器會將后綴為.java的文件編譯成.class文件,之后jvm虛擬機會把class文件轉成二進制的字節(jié)碼加載到內存里面,中間經過了一系列的鏈接過程,最后完成初始化,加載過程不是本文的重點,這里簡單說明略過;

      3、類加載器的分類

      類加載器基類叫做 ClassLoader,這是一個抽象類,既然是抽象就代表著它是可擴展的,所以衍生出了不同種類的類加器

      BootstrapClassLoader :主要負責加載核心的類庫(java.lang.*等),構造ExtClassLoader和APPClassLoader。

      ExtClassLoader?:主要負責加載?%JAVA_HOME%/jre/lib/ext??目錄下的一些擴展的jar。

      AppClassLoader:主要負責加載應用程序的主函數(shù)類, 加載classPath目錄

      UserClassLoader:?用戶自定義類加載器,名字隨便起,只需要繼承ClassLoader類即可;

      3.1、BootstrapClassLoader

      BootStrapClassLoader 是一個純的C++實現(xiàn),沒有對應的Java類。所以在Java中是取不到的。如果我們在 idea 編輯器中搜索BootStrapClassLoader,是找不到這個類的

      如果一個類的classLoader是null。已經足可以證明他就是由BootStrapClassLoader 加載的,現(xiàn)在我們用一組代碼測試下

      public static void main(String[] args) throws Exception {

      String str = new String("123");

      System.out.println("String類的類加載器為:"+str.getClass().getClassLoader());

      }

      java.lang.String 這個類是系統(tǒng)的類,當我們獲取這個類時,可以看到它的類加載器是null值,所以就可以斷定,String 使用的是BootStrapClassLoader加載的;

      3.2、ExtClassLoader

      ExtClassLoader主要加載%JAVA_HOME%/jre/lib/ext,此路徑下的所有classes目錄以及java.ext.dirs系統(tǒng)變量指定的路徑中類庫?,那么都有哪些類呢?看下圖就知道啦,主要加載下圖jar包中的類!此類繼承ClassLoader加載器;

      需要注意的是,ExtClassLoader這個類是在 Launcher? 類里面的一個內部類

      3.3、AppClassLoader

      App就是應用程序的意思,所以這個類加載器就是用來加載我們用戶自己寫的java類;此類繼承ClassLoader加載器,需要注意的是,這個類也是在 Launcher? 類里面的一個內部類

      接下來我們自己定義一個類 User.java ,然后在控制臺打印一下這個類的類加載器;

      package com;

      public class User {

      public static void main(String[] args) {

      System.out.println("User類的類加載器為:"+User.class.getClassLoader());

      }

      }

      通過控制臺打印的結果可以得知,自定義的 User類 使用的是AppClassLoader 類加載器;

      3.5、關于類加載,還有2點需要注意:

      1、同一個classLoader只會生成一個相同的class對象

      2、不同的ClassLoader 加載相同的Class對象不能互相強轉

      為什么不能強轉呢?我們來寫一組代碼測試下就知道啦

      先定一個 自動義類加載器? ? MyClassLoader.java

      package com;

      import java.io.InputStream;

      public class MyClassLoader extends ClassLoader{

      /**

      * 加載class文件

      * 重寫此方法的目的是為了能讓此方法被外部調用,父類的 findClass 是 protected 修飾的,只能被子類調用

      * @param name 類的全類名 示例: com.xd.User

      * @return

      * @throws ClassNotFoundException

      */

      @Override

      public Class findClass(String name) throws ClassNotFoundException {

      try {

      String fileName = name.replaceAll("\.","/") + ".class";

      fileName = "/"+ fileName;

      // 獲取文件輸入流

      InputStream is = this.getClass().getResourceAsStream(fileName);

      // 讀取字節(jié)

      byte[] b = new byte[is.available()];

      is.read(b);

      // 將byte字節(jié)流解析成jvm能夠識別的Class對象

      return defineClass(name, b, 0, b.length);

      } catch (Exception e) {

      throw new ClassNotFoundException();

      }

      }

      }

      定義一個 HotUserModel.java 類,用來實例化用的

      package com.hot.deploy.model;

      public class HotUserModel {

      }

      主程序入口測試

      public static void main(String[] args) throws Exception {

      // 默認加載器加載的 HotUserModel

      HotUserModel hotUserModel = null;

      // 使用自定義加載器加載 HotUserModel

      MyClassLoader myClassLoader = new MyClassLoader();

      Class aClass =myClassLoader.findClass("com.hot.deploy.model.HotUserModel");

      Object o = aClass.newInstance();

      System.out.println("普通 HotUserModel 使用的類加載器為:" + HotUserModel.class.getClassLoader());

      System.out.println();

      System.out.println("使用自動義類加載器的 HotUserModel 使用的加載器為:" + o.getClass().getClassLoader());

      System.out.println();

      // 以下語句賦值會報錯

      hotUserModel = (HotUserModel) o;

      }

      運行后打印結果如下

      通過打印的結果可以看到,相同的類無法強轉,?因為使用了不同的類加載器;

      4、雙親委派加載機制

      當類加載器在加載一個類的時候,它首先不會馬上去加載這個類,而是先把這個請求委派給父類去完成,而父類接受到加載請求的時候也不會馬上去加載這個類,而是在交給自己的父類去加載,每一個層次加載器都是如此,直到父類加載器無法加載的時候,子類才會自己去嘗試完成加載,我們的要實現(xiàn)的熱部署原理就是要打破雙親委派加載機制,直接由子類來加載;而不去請求父加載器;

      加載流程如下

      將自定義加載器掛載到應用程序加載器

      應用程序加載器將類加載請求委托給擴展類加載器

      擴展類加載器將類加載請求委托給啟動類加載器

      啟動類加載器在加載路徑下查找并加載Class文件,如果未找到目標Class文件,則交由擴展類加載器加載;

      擴展類加載器在加載路徑下未找到目標Class文件,交由應用程序類加載器加載

      應用程序類加載器在加載路徑下未找到目標Class文件,交由自定義加載器加載

      自定義加載器在加載路徑下未找到目標Class文件,拋出ClassNotFound異常

      4.1、雙親委派的作用

      1、通過以上的委托流程,雙親委派機制保障了類的唯一性,

      2、保證核心.class不被篡改,即使被篡改也不會被加載,即使被加載也不會是同一個class對象,因為不同的加載器加載同一個.class也不是同一個Class對象。這樣則保證了Class的執(zhí)行安全。

      5、全盤負責委托機制

      當一個A加載器加載一個類的時候,除非顯式地使用另一個加載器B,否則該類的所有依賴和引用都使用這個A加載器; 也就是說,如果一個類 為User類,這個User類的加載器是AppClassLoader加載器加載的,那么這個User類下面的所有引用類的加載器都是AppClassLoader;

      接下來測試一把,先創(chuàng)建一個自定義類加載器?MyClassLoader.java

      package com;

      import java.io.InputStream;

      import java.net.URL;

      public class MyClassLoader extends ClassLoader{

      /**

      * 加載class文件

      * 重寫此方法的目的是為了能讓此方法被外部調用,父類的 findClass 是 protected 修飾的,只能被子類調用

      * @param name 類的全類名 示例: com.xd.User

      * @return

      * @throws ClassNotFoundException

      */

      @Override

      public Class findClass(String name) throws ClassNotFoundException {

      try {

      // 獲取class文件名稱 去掉包路徑

      String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";

      // 獲取文件輸入流

      InputStream is = this.getClass().getResourceAsStream(fileName);

      // 讀取字節(jié)

      byte[] b = new byte[is.available()];

      is.read(b);

      // 將byte字節(jié)流解析成jvm能夠識別的Class對象

      return defineClass(name, b, 0, b.length);

      } catch (Exception e) {

      throw new ClassNotFoundException();

      }

      }

      }

      用來創(chuàng)建對象的類 User.java

      注意:在User的show方法里面創(chuàng)建了一個新的User對象,我們就是要測試這個新的User對象是否使用了全盤負責委托機制

      package com;

      public class User {

      public void show(){

      System.out.println("當前User使用的類加載器為:"+this.getClass().getClassLoader());

      User user = new User();

      System.out.println("新的User對象使用的類加載器為:"+user.getClass().getClassLoader());

      }

      }

      創(chuàng)建main方法測試

      public static void main(String[] args) throws Exception {

      MyClassLoader myClassLoader = new MyClassLoader();

      Class aClass = myClassLoader.findClass("com.User");

      // 實例化User對象

      Object o = aClass.newInstance();

      Method show = aClass.getDeclaredMethod("show");

      // 執(zhí)行show方法

      show.invoke(o);

      }

      運行后打印結果如下,很明顯,全盤負責委托機制生效了,用的都是自定義加載類加載的class對象;

      當前User使用的類加載器為:com.MyClassLoader@1d44bcfa

      新的User對象使用的類加載器為:com.MyClassLoader@1d44bcfa

      Process finished with exit code 0

      6、熱部署代碼

      實現(xiàn)熱部署需要做2件事,一是打破雙親委派機制,二是使用全盤委托機制實現(xiàn)熱部署(廢話,默認是使用的);廢話不多說,上代碼,需要注意的是,熱部署的代碼和上面5步的代碼沒有任何關系,請不要將其混淆

      HotUserModel.java

      package com.hot.deploy.model;

      public class HotUserModel {

      // 用戶名

      private String userName="yexindong";

      // 年齡

      private Integer age = 18;

      public void show(){

      System.out.println("大家好,我叫:"+userName+",我的年齡是:"+age);

      System.out.println("當前使用的類加載器:"+this.getClass().getClassLoader());

      }

      }

      自定義類加載器? HotClassLoader.java

      package com.hot.deploy;

      import java.io.File;

      import java.io.FileInputStream;

      import java.io.IOException;

      import java.io.InputStream;

      import java.util.ArrayList;

      import java.util.List;

      public class HotClassLoader extends ClassLoader{

      // 項目目錄

      private String projectPath;

      // class的包路徑列表 格式: [com.xxx.App.class]

      private List classList;

      /**

      *

      * @param projectPath 項目絕對路徑

      * @param classPaths class文件的完整路徑列表

      * @throws IOException

      原來類加載器這么簡單,手把手教你用純java代碼實現(xiàn)熱部署

      */

      public HotClassLoader(String projectPath,String... classPaths) throws IOException {

      // 空格解碼

      this.projectPath = projectPath.replaceAll("%20"," ");

      classList = new ArrayList();

      // 掃描包路徑,并加載類,打破雙親委派機制

      for (String classPath : classPaths) {

      // 轉為路徑

      classPath = classPath.replaceAll("\\.", "/");

      // 空格解碼

      classPath.replaceAll("%20"," ");

      File file = new File(classPath);

      readFile(file);

      }

      }

      public void readFile(File file) throws IOException {

      if (file.isDirectory()) {

      for (File child : file.listFiles()) {

      readFile(child);

      }

      } else {

      // 加載類

      String fileName = file.getName();

      String suffix = fileName.substring(fileName.lastIndexOf(".")+1);

      if(!"class".equals(suffix)){

      return;

      }

      //將class文件轉為字節(jié)碼

      InputStream inputStream = new FileInputStream(file);

      byte[] classByte = new byte[(int)file.length()];

      inputStream.read(classByte);

      // 獲取全類名 com.xx.xx.class

      String className = this.getClassName(file);

      // 記錄已被加載過的class文件

      classList.add(className);

      // 加載class到jvm虛擬機

      defineClass(className,classByte, 0,classByte.length);

      }

      }

      /**

      * 獲取全類名 com.xx.xx.class

      * @param file

      * @return

      */

      public String getClassName(File file){

      String className = file.getPath().replace(projectPath, "");

      // 去掉.class

      className = className.substring(1,className.indexOf("."));

      // 將斜杠轉為.

      className = className.replaceAll("/",".");

      return className;

      }

      // 這個方法可改可不改

      @Override

      public Class loadClass(String name) throws ClassNotFoundException {

      Class loadedClass = findLoadedClass(name);

      if(loadedClass == null ){

      if(classList.contains(name)){

      throw new ClassNotFoundException("找不到類");

      }

      // 調用系統(tǒng)類加載器

      loadedClass = getSystemClassLoader().loadClass(name);

      }

      return loadedClass;

      // return super.loadClass(name);

      }

      }

      Application.java

      package com.hot.deploy;

      import com.hot.deploy.model.HotUserModel;

      import java.io.File;

      import java.lang.reflect.Method;

      public class Application {

      public void showApplication(){

      // 創(chuàng)建新對象,這個創(chuàng)建對象時使用的類加載器也是自定義的加載器

      new HotUserModel().show();

      }

      /**

      * 開始加載

      * @param classLoader

      * @throws Exception

      */

      public static void start0(HotClassLoader classLoader) throws Exception {

      // 加載當前類Application

      Class aClass = classLoader.loadClass("com.hot.deploy.Application");

      // 調用showApplication方法

      Method show = aClass.getDeclaredMethod("showApplication");

      Object o = aClass.newInstance();

      Object invoke = show.invoke(o);

      // 延時一秒

      Thread.sleep(1000);

      }

      /**

      * 使用死循環(huán)來重新加載class,每次循環(huán)加載的class都是new出來的新對象

      * @throws Exception

      */

      public static void run() throws Exception {

      for(;;){

      // 獲取項目的目錄

      String projectPath = HotClassLoader.class.getResource("/").getPath().replaceAll("%20"," ");

      projectPath = new File(projectPath).getPath();

      // 使用自定義加載器加載所有的對象

      HotClassLoader hotClassLoader = new HotClassLoader(projectPath, projectPath + "/com");

      // 開始加載

      start0(hotClassLoader);

      }

      }

      }

      創(chuàng)建一個啟動類來運行? MainApp.java

      package com.hot.deploy;

      public class MainApp {

      /**

      * 運行main 方法后,修改 HotUserModel 類的System.out.println()打印的內容,然后按一下右上角的綠色錘子(Build Project)就可以看到效果了

      *

      * 熱部署實現(xiàn)原理: 是因為打破了雙親委派機制 和 全盤委托 機制;

      * @param args

      * @throws Exception

      */

      public static void main(String[] args) throws Exception {

      Application.run();

      }

      }

      先運行main方法,在運行的同時,先修改HotUserModel類的userName屬性

      然后點擊idea 右上角的綠色的錘子進行手動編譯

      編譯完成后,馬上就可以在控制臺看到修改的內容了

      熱部署這塊確實有些晦澀難懂,但是只要認真學習,多去網(wǎng)上找找資料學習,總會茅塞頓開的,俗話說,難的不會,會的不難,說的就是這個道理,當你不知道的時候,你會覺得好復雜,當你學會之后會覺得也就那樣!也希望童鞋們不要停下學習的腳步,向著詩和遠方,我們一起出發(fā)!!

      Java JVM

      版權聲明:本文內容由網(wǎng)絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內刪除侵權內容。

      上一篇:Hadoop 各種壓縮的應用場景與使用
      下一篇:selenium實戰(zhàn)一:播放音悅臺高清Mv
      相關文章
      亚洲色偷偷综合亚洲AV伊人蜜桃 | 亚洲国产精品一区二区三区久久| 亚洲欧洲日本在线| 亚洲a∨无码一区二区| 色偷偷女男人的天堂亚洲网| 精品亚洲国产成AV人片传媒| 亚洲avav天堂av在线不卡| 亚洲AV无码1区2区久久| 国产亚洲美女精品久久久2020| 亚洲熟伦熟女新五十路熟妇| 亚洲人成色77777在线观看大| 亚洲精品无码av片| 亚洲av纯肉无码精品动漫| 亚洲av无码专区国产不乱码| 国产精品成人亚洲| 亚洲成a人无码av波多野按摩| 亚洲国产婷婷综合在线精品| 国产亚洲美日韩AV中文字幕无码成人 | 亚洲精品人成网线在线播放va| 亚洲精品无码你懂的| 亚洲色大成网站www久久九| 亚洲一卡2卡3卡4卡5卡6卡| 亚洲国产AV无码一区二区三区| 亚洲A∨精品一区二区三区下载| 国产天堂亚洲国产碰碰| 亚洲五月午夜免费在线视频| 亚洲综合另类小说色区| 久久亚洲精品中文字幕三区| 亚洲另类激情综合偷自拍| 亚洲精品中文字幕无码AV| 亚洲av无码不卡久久| 亚洲欧美一区二区三区日产| 国产成人亚洲精品无码AV大片| 全亚洲最新黄色特级网站 | 国产啪亚洲国产精品无码| 亚洲精品夜夜夜妓女网 | 亚洲日韩国产AV无码无码精品| 国产亚洲精品国产福利在线观看| 久久精品国产精品亚洲下载| 久久精品国产亚洲AV麻豆~| 亚洲明星合成图综合区在线|