1. 程式人生 > >Java之——類熱載入

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