1.前言 本篇通過自定義的一個ClassLoader初步實現基礎的HotSwap。2.HotSwapClassLoader源代碼,解釋詳見源代碼注釋package com.mavsplus.example.java.classloader;import java.io.File;import java.io.IOException;import java.io.InputStream;import java.net.URI;import java.nio.file.Files;import java.nio.file.Path;import java.nio.file.Paths;import java.util.Arrays;import java.util.HashSet;import java.util.Set;/**
* 一個用于Hot-Swap的classloader,繼承自{@link java.lang.ClassLoader}
*
* <pre>
* 1.Java 中,有四種類型的類加載器,分別為:BootStrapClassLoader、ExtClassLoader、AppClassLoader 以及用戶自定義的 ClassLoader。
* 這四種類加載器分別負責不同路徑的類的加載,并形成了一個類加載的層次結構。
* 2.BootStrapClassLoader 處于類加載器層次結構的最高層,負責 sun.boot.class.path 路徑下類的加載,默認為 jre/lib 目錄下的核心 API 或 -Xbootclasspath 選項指定的 jar 包。
* ExtClassLoader 的加載路徑為 java.ext.dirs,默認為 jre/lib/ext 目錄或者 -Djava.ext.dirs 指定目錄下的 jar 包加載
* AppClassLoader 的加載路徑為 java.class.path,默認為環境變量 CLASSPATH 中設定的值。也可以通過 -classpath 選型進行指定。
* 用戶自定義 ClassLoader 可以根據用戶的需要定制自己的類加載過程,在運行期進行指定類的動態實時加載。
* 3.一般來說,這四種類加載器會形成一種父子關系,高層為低層的父加載器。在進行類加載時,首先會自底向上挨個檢查是否已經加載了指定類,
* 如果已經加載則直接返回該類的引用。如果到最高層也沒有加載過指定類,那么會自頂向下挨個嘗試加載,直到用戶自定義類加載器,如果還不能成功,就會拋出異常
* </pre>
*
* <pre>
* 1.每個類加載器有自己的名字空間,對于同一個類加載器實例來說,名字相同的類只能存在一個,并且僅加載一次。不管該類有沒有變化,下次再需要加載時,它只是從自己的緩存中直接返回已經加載過的類引用。
* 2.實現 Java 類的熱替換,首先必須要實現系統中同名類的不同版本實例的共存。要想實現同一個類的不同版本的共存,我們必須要通過不同的類加載器來加載該類的不同版本。
* 即不能把這些類的加載工作委托給系統加載器來完成,因為它們只有一份。
* 3.不能采用系統默認的類加載器委托規則,也就是說我們定制的類加載器的父加載器必須設置為 null
//landon:其實也可以使用類加載器委托規則,但是前提是加載的class不能在classpath下,否則的話會優先被系統類加載器加載到
* </pre>
*
* <a href = "http://www.ibm.com/developerworks/cn/java/j-lo-hotswapcls/"></a>
*
* @author landon
* @since 1.8.0_25
*/public class HotSwapClassLoader extends ClassLoader { /** 加載的類文件根目錄目錄,存放加載的.class文件 */ private URI reloadBaseDir; /** 加載的類名集合,Full-Name,不在此集合的類委托給系統加載器來完成 */ private Set<String> reloadClazzs; public HotSwapClassLoader(URI loadDir) { // 指定父加載器為null
super(null); this.reloadBaseDir = loadDir; reloadClazzs = new HashSet<>(); } /**
* 指定加載的類
*
* <p>
* 通過根目錄+Full-Name找到.class
*
* @param clazzNames
*/ public void assignLoadedClazzs(String
clazzNames) { for (String clazzName : clazzNames) { defineClassFromPath(getLoadedClassPath(clazzName), clazzName); } // 添加至加載的集合
reloadClazzs.addAll(Arrays.asList(clazzNames)); } /**
* 根據類名獲取所在路徑
*
* @param clazzName
* @return
*/ private Path getLoadedClassPath(String clazzName) { String pathName = clazzName.replace('.', File.separatorChar); String classPathName = pathName + ".class"; return Paths.get(reloadBaseDir).resolve(classPathName); } /**
* 從指定的Path接收類字節碼->轉換為Class實例
*
* @param path
* @param clazzFullName
* @return
*/ private Class<?> defineClassFromPath(Path path, String clazzFullName) { File classFile = path.toFile(); int fileLength = (int) classFile.length(); byte[] rawBytes = new byte[fileLength]; try { InputStream in = Files.newInputStream(path); in.read(rawBytes); in.close(); } catch (IOException e) { e.printStackTrace(); } // Converts an array of bytes into an instance of class Class. Before
// the Class can be used it must be resolved.
return defineClass(clazzFullName, rawBytes, 0, fileLength); } // Loads the class
// 本類加載器只加載reloadClazzs中的類,其余的類委托給系統類加載器進行加載
// Foo的接口類IFoo加載也會調用此方法,不過其實用系統類加載器進行加載的
@Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { Class<?> clazz = null; // 每個類加載器都維護有自己的一份已加載類名字空間,其中不能出現兩個同名的類。凡是通過該類加載器加載的類,無論是直接的還是間接的,都保存在自己的名字空間.
// 該方法即在該名字空間中尋找指定的類是否存在,如果存在舊返回給類的引用;否則就返回null;這里的直接是指存在于該類加載器的加載路徑上并由該加載器完成加載,
// 間接是指由類加載器把類的加載工作委托給其他類加載器完成類的實際加載
// landon:1.因在loadClass之前,我們調用了{@link #defineClassFromPath}->通過調用{@link
// #defineClass}->已經實現了將reloadClazzs中的class的裝載工作->所以如果通過loadClass方法加載的class是reloadClazzs中的,
// 則通過findLoadedClass方法可直接獲取
// 2.當然還有一種做法是直接覆蓋findClass方法,在該方法中調用defineClass方法.而findClass的調用時機則是在沒有找到加載的類時調用的.
// 其默認實現是直接拋出一個ClassNotFoundException
clazz = findLoadedClass(name); if (!this.reloadClazzs.contains(name) && clazz == null) { clazz = getSystemClassLoader().loadClass(name); } if (clazz == null) { throw new ClassNotFoundException(name); } if (resolve) { // Links the specified class
resolveClass(clazz); } return clazz; }} 3.用于reload的測試類IFoo,Foopackage com.mavsplus.example.java.classloader;
/**
* IFoo接口
*
* @author landon
* @since 1.8.0_25
*/
public interface IFoo {
public void say();
}
package com.mavsplus.example.java.classloader;
/**
* 用于hotswap的類
*
* @author landon
* @since 1.8.0_25
*/
public class Foo implements IFoo {
@Override
public void say() {
// 打印了類所有的ClassLoader
System.out.println("Hello,HotSwap.[version 1][ClassLoader-" + getClass().getClassLoader().getClass() + "]");
}
}
4.入口調用類HotSwapExample,解釋詳見源代碼注釋package com.mavsplus.example.java.classloader;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
/**
* Hot-Swap例子
*
* @author landon
* @since 1.8.0_25
*/
public class HotSwapExample {
public static void main(String[] args) throws Exception {
HotSwapExample example = new HotSwapExample();
while (true) {
example.systemLoad();
example.hotswapLoad();
TimeUnit.SECONDS.sleep(3);
}
}
// 這里為了測試,每次調用方法的時候都會初始化一個HotSwapClassLoader對象
// 線上產品只需要在hotswap的時候初始化一個HotSwapClassLoader對象即可
public void hotswapLoad() throws Exception {
// 初始化ClassLoader,指定加載根目錄及加載的類,這里通過ClassLoader.getSystemResource獲取classpath所在的目錄(即target\classes目錄)
// 這樣我們直接就可以eclipse中直接進行修改Foo,編譯->下一次HotSwapClassLoader就直接會加載最新的Foo.class
HotSwapClassLoader classLoader = new HotSwapClassLoader(ClassLoader.getSystemResource("").toURI());
classLoader.assignLoadedClazzs("com.mavsplus.example.java.classloader.Foo");
// 調用loadClass方法加載類
Class<?> clazz = classLoader.loadClass("com.mavsplus.example.java.classloader.Foo");
// 實例化
Object foo = clazz.newInstance();
Method method = foo.getClass().getMethod("say");
method.invoke(foo);
// 這樣直接調用會拋出java.lang.ClassCastException,因為即使是同一個類文件,如果是由不同的類加載器實例加載的,那么他們的類型就是不同的
// clazz對象是由HotSwapClassLoader加載的,而foo2的類型生命和轉型的Foo類都是由方法所屬的類加載(默認是AppClassLoader)加載的,因此是
// 完全不同的類型,所以會拋出轉型異常
// Foo foo2 = (Foo)clazz.newInstance();
// foo2.say();
// 這里通過接口進行調用,并沒有拋出ClassCastException.因為HotSwapClassLoader只指定加載了Foo,IFoo接口的加載則會委托給系統類加載器加載
// 所以轉型可成功
IFoo foo3 = (IFoo) clazz.newInstance();
foo3.say();
}
public void systemLoad() throws Exception {
Foo foo = new Foo();
foo.say();
}
}
5.測試,控制臺輸出,紅色部分即是我們直接修改了Foo的實現后的結果Hello,HotSwap.[version 1][ClassLoader-class sun.misc.Launcher$AppClassLoader]
Hello,HotSwap.[version 1][ClassLoader-class com.mavsplus.example.java.classloader.HotSwapClassLoader]
Hello,HotSwap.[version 1][ClassLoader-class com.mavsplus.example.java.classloader.HotSwapClassLoader]
Hello,HotSwap.[version 1][ClassLoader-class sun.misc.Launcher$AppClassLoader]
Hello,HotSwap.[version 1][ClassLoader-class com.mavsplus.example.java.classloader.HotSwapClassLoader]
Hello,HotSwap.[version 1][ClassLoader-class com.mavsplus.example.java.classloader.HotSwapClassLoader]
Hello,HotSwap.[version 1][ClassLoader-class sun.misc.Launcher$AppClassLoader]
Hello,HotSwap.[version 1][ClassLoader-class com.mavsplus.example.java.classloader.HotSwapClassLoader]
Hello,HotSwap.[version 1][ClassLoader-class com.mavsplus.example.java.classloader.HotSwapClassLoader]
Hello,HotSwap.[version 1][ClassLoader-class sun.misc.Launcher$AppClassLoader]
Hello,HotSwap.[version 1][ClassLoader-class com.mavsplus.example.java.classloader.HotSwapClassLoader]
Hello,HotSwap.[version 1][ClassLoader-class com.mavsplus.example.java.classloader.HotSwapClassLoader]
Hello,HotSwap.[version 1][ClassLoader-class sun.misc.Launcher$AppClassLoader]
Hello,HotSwap.[version 2][ClassLoader-class com.mavsplus.example.java.classloader.HotSwapClassLoader]
Hello,HotSwap.[version 2][ClassLoader-class com.mavsplus.example.java.classloader.HotSwapClassLoader]
Hello,HotSwap.[version 1][ClassLoader-class sun.misc.Launcher$AppClassLoader]
Hello,HotSwap.[version 2][ClassLoader-class com.mavsplus.example.java.classloader.HotSwapClassLoader]
Hello,HotSwap.[version 2][ClassLoader-class com.mavsplus.example.java.classloader.HotSwapClassLoader]
Hello,HotSwap.[version 1][ClassLoader-class sun.misc.Launcher$AppClassLoader]
Hello,HotSwap.[version 2][ClassLoader-class com.mavsplus.example.java.classloader.HotSwapClassLoader]
Hello,HotSwap.[version 2][ClassLoader-class com.mavsplus.example.java.classloader.HotSwapClassLoader]
Hello,HotSwap.[version 1][ClassLoader-class sun.misc.Launcher$AppClassLoader]
Hello,HotSwap.[version 3][ClassLoader-class com.mavsplus.example.java.classloader.HotSwapClassLoader]
Hello,HotSwap.[version 3][ClassLoader-class com.mavsplus.example.java.classloader.HotSwapClassLoader]
6.總結: 本篇用一個例子講解了基礎的Java實現HotSwap的方式。后續會繼續深入講解.
posted on 2015-07-03 17:35
landon 閱讀(5425)
評論(0) 編輯 收藏 所屬分類:
JVM 、
HotSwap 、
ClassLoader