天天看點

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。到此熱加載成功。