淺談JAVA 類(lèi)加載器
類(lèi)加載機(jī)制
類(lèi)加載器負(fù)責(zé)加載所有的類(lèi),系統(tǒng)為所有被載入內(nèi)存中的類(lèi)生成一個(gè) java.lang.Class 實(shí)例。一旦一個(gè)類(lèi)被載入 JVM 中,同個(gè)類(lèi)就不會(huì)被再次載入了?,F(xiàn)在的問(wèn)題是,怎么樣才算“同一個(gè)類(lèi)”?
正如一個(gè)對(duì)象有一個(gè)唯一的標(biāo)識(shí)一樣,一個(gè)載入 JVM 中的類(lèi)也有一個(gè)唯一的標(biāo)識(shí)。在 Java 中,一個(gè)類(lèi)用其全限定類(lèi)名(包括包名和類(lèi)名)作為標(biāo)識(shí):但在 JVM 中,一個(gè)類(lèi)用其全限定類(lèi)名和其類(lèi)加載器作為唯一標(biāo)識(shí)。例如,如果在 pg 的包中有一個(gè)名為 Person 的類(lèi),被類(lèi)加載器 ClassLoader 的實(shí)例 k1 負(fù)責(zé)加載,則該 Person 類(lèi)對(duì)應(yīng)的 Class 對(duì)象在 JVM 中表示為(Person、pg、k1)。這意味著兩個(gè)類(lèi)加載器加載的同名類(lèi):(Person、pg、k1)和(Person、pg、k12)是不同的,它們所加載的類(lèi)也是完全不同、互不兼容的。
當(dāng) JVM 啟動(dòng)時(shí),會(huì)形成由三個(gè)類(lèi)加載器組成的初始類(lèi)加載器層次結(jié)構(gòu)。
- Bootstrap ClassLoader:根類(lèi)加載器。
- Extension ClassLoader:擴(kuò)展類(lèi)加載器。
- System ClassLoader:系統(tǒng)類(lèi)加載器。
Bootstrap ClassLoader 被稱(chēng)為引導(dǎo)(也稱(chēng)為原始或根)類(lèi)加載器,它負(fù)責(zé)加載 Java 的核心類(lèi)。在Sun 的 JVM 中,當(dāng)執(zhí)行 java.exe 命令時(shí),使用 -Xbootclasspath 或 -D 選項(xiàng)指定 sun.boot.class.path 系統(tǒng)屬性值可以指定加載附加的類(lèi)。
JVM的類(lèi)加載機(jī)制主要有如下三種。
- 全盤(pán)負(fù)責(zé)。所謂全盤(pán)負(fù)責(zé),就是當(dāng)一個(gè)類(lèi)加載器負(fù)責(zé)加載某個(gè) Class 時(shí),該 Class 所依賴(lài)的和引用的其他 Class 也將由該類(lèi)加載器負(fù)責(zé)載入,除非顯式使用另外一個(gè)類(lèi)加載器來(lái)載入。
- 父類(lèi)委托。所謂父類(lèi)委托,則是先讓 parent(父)類(lèi)加載器試圖加載該 Class,只有在父類(lèi)加載器無(wú)法加載該類(lèi)時(shí)才嘗試從自己的類(lèi)路徑中加載該類(lèi)。
- 緩存機(jī)制。緩存機(jī)制將會(huì)保證所有加載過(guò)的 Class 都會(huì)被緩存,當(dāng)程序中需要使用某個(gè) Class 時(shí),類(lèi)加載器先從緩存區(qū)中搜尋該 Class,只有當(dāng)緩存區(qū)中不存在該 Class 對(duì)象時(shí),系統(tǒng)才會(huì)讀取該類(lèi)對(duì)應(yīng)的二進(jìn)制數(shù)據(jù),并將其轉(zhuǎn)換成 Class 對(duì)象,存入緩存區(qū)中。這就是為什么修改了 Class 后,必須重新啟動(dòng) JVM,程序所做的修改才會(huì)生效的原因。
除了可以使用 Java 提供的類(lèi)加載器之外,開(kāi)發(fā)者也可以實(shí)現(xiàn)自己的類(lèi)加載器,自定義的類(lèi)加載器通過(guò)繼承 ClassLoader 來(lái)實(shí)現(xiàn)。JVM 中這4種類(lèi)加載器的層次結(jié)構(gòu)如下圖所示。

注意:類(lèi)加載器之間的父子關(guān)系并不是類(lèi)繼承上的父子關(guān)系,這里的父子關(guān)系是類(lèi)加載器實(shí)例之間的關(guān)系
下面程序示范了訪(fǎng)問(wèn) JVM 的類(lèi)加載器。
public class ClassLoaderPropTest {
public static void main(String[] args) throws IOException {
// 獲取系統(tǒng)類(lèi)加載器
ClassLoader systemLoader = ClassLoader.getSystemClassLoader();
System.out.println("系統(tǒng)類(lèi)加載器:" + systemLoader);
/*
* 獲取系統(tǒng)類(lèi)加載器的加載路徑——通常由CLASSPATH環(huán)境變量指定 如果操作系統(tǒng)沒(méi)有指定CLASSPATH環(huán)境變量,默認(rèn)以當(dāng)前路徑作為
* 系統(tǒng)類(lèi)加載器的加載路徑
*/
Enumeration<URL> em1 = systemLoader.getResources("");
while (em1.hasMoreElements()) {
System.out.println(em1.nextElement());
}
// 獲取系統(tǒng)類(lèi)加載器的父類(lèi)加載器:得到擴(kuò)展類(lèi)加載器
ClassLoader extensionLader = systemLoader.getParent();
System.out.println("擴(kuò)展類(lèi)加載器:" + extensionLader);
System.out.println("擴(kuò)展類(lèi)加載器的加載路徑:" + System.getProperty("java.ext.dirs"));
System.out.println("擴(kuò)展類(lèi)加載器的parent: " + extensionLader.getParent());
}
}
運(yùn)行上面的程序,會(huì)看到如下運(yùn)行結(jié)果
系統(tǒng)類(lèi)加載器:sun.misc.Launcher$AppClassLoader@73d16e93
file:/F:/EclipseProjects/demo/bin/
擴(kuò)展類(lèi)加載器:sun.misc.Launcher$ExtClassLoader@15db9742
擴(kuò)展類(lèi)加載器的加載路徑:C:\Program Files\Java\jre1.8.0_181\lib\ext;C:\Windows\Sun\Java\lib\ext
擴(kuò)展類(lèi)加載器的parent: null
從上面運(yùn)行結(jié)果可以看出,系統(tǒng)類(lèi)加載器的加載路徑是程序運(yùn)行的當(dāng)前路徑,擴(kuò)展類(lèi)加載器的加載路徑是null(與 Java8 有區(qū)別),但此處看到擴(kuò)展類(lèi)加載器的父加載器是null,并不是根類(lèi)加載器。這是因?yàn)楦?lèi)加載器并沒(méi)有繼承 ClassLoader 抽象類(lèi),所以擴(kuò)展類(lèi)加載器的 getParent() 方法返回null。但實(shí)際上,擴(kuò)展類(lèi)加載器的父類(lèi)加載器是根類(lèi)加載器,只是根類(lèi)加載器并不是 Java 實(shí)現(xiàn)的。
從運(yùn)行結(jié)果可以看出,系統(tǒng)類(lèi)加載器是 AppClassLoader 的實(shí)例,擴(kuò)展類(lèi)加載器 ExtClassLoader 的實(shí)例。實(shí)際上,這兩個(gè)類(lèi)都是 URLClassLoader 類(lèi)的實(shí)例。
注意:JVM 的根類(lèi)加載器并不是 Java 實(shí)現(xiàn)的,而且由于程序通常無(wú)須訪(fǎng)問(wèn)根類(lèi)加載器,因此訪(fǎng)問(wèn)擴(kuò)展類(lèi)加載器的父類(lèi)加載器時(shí)返回null。
類(lèi)加載器加載 Class 大致要經(jīng)過(guò)如下8個(gè)步驟。
- 檢測(cè)此 Class 是否載入過(guò)(即在緩存區(qū)中是否有此Class),如果有則直接進(jìn)入第8步,否則接著執(zhí)行第2步。
- 如果父類(lèi)加載器不存在(如果沒(méi)有父類(lèi)加載器,則要么 parent 一定是根類(lèi)加載器,要么本身就是根類(lèi)加載器),則跳到第4步執(zhí)行;如果父類(lèi)加載器存在,則接著執(zhí)行第3步。
- 請(qǐng)求使用父類(lèi)加載器去載入目標(biāo)類(lèi),如果成功載入則跳到第8步,否則接著執(zhí)行第5步。
- 請(qǐng)求使用根類(lèi)加載器來(lái)載入目標(biāo)類(lèi),如果成功載入則跳到第8步,否則跳到第7步。
- 當(dāng)前類(lèi)加載器嘗試尋找 Class 文件(從與此 ClassLoader 相關(guān)的類(lèi)路徑中尋找),如果找到則執(zhí)行第6步,如果找不到則跳到第7步。
- 從文件中載入 Class,成功載入后跳到第8步。
- 拋出 ClassNotFoundExcepuon 異常。
- 返回對(duì)應(yīng)的 java.lang.Class 對(duì)象。
其中,第5、6步允許重寫(xiě) ClassLoader的 findClass() 方法來(lái)實(shí)現(xiàn)自己的載入策略,甚至重寫(xiě) loadClass() 方法來(lái)實(shí)現(xiàn)自己的載入過(guò)程。
創(chuàng)建并使用自定義的類(lèi)加載器
JVM 中除根類(lèi)加載器之外的所有類(lèi)加載器都是 ClassLoader 子類(lèi)的實(shí)例,開(kāi)發(fā)者可以通過(guò)擴(kuò)展 ClassLoader 的子類(lèi),并重寫(xiě)該 ClassLoader 所包含的方法來(lái)實(shí)現(xiàn)自定義的類(lèi)加載器。查閱API文檔中關(guān)于 ClassLoader 的方法不難發(fā)現(xiàn),ClassLoader 中包含了大量的 protected 方法——這些方法都可被子類(lèi)重寫(xiě)。
ClassLoader 類(lèi)有如下兩個(gè)關(guān)鍵方法。
- loadClass(String name, boolean resolve):該方法為 ClassLoader 的入口點(diǎn),根據(jù)指定名稱(chēng)來(lái)加載類(lèi),系統(tǒng)就是調(diào)用 ClassLoader 的該方法來(lái)獲取指定類(lèi)對(duì)應(yīng)的 Class 對(duì)象。
- findClass(String name):根據(jù)指定名稱(chēng)來(lái)查找類(lèi)。
如果需要實(shí)現(xiàn)自定義的 ClassLoader,則可以通過(guò)重寫(xiě)以上兩個(gè)方法來(lái)實(shí)現(xiàn),通常推薦重寫(xiě) findClass() 方法,而不是重寫(xiě) loadClass() 方法。loadClass() 方法的執(zhí)行步驟如下。
- 用 findLoadedClass(String) 來(lái)檢查是否已經(jīng)加載類(lèi),如果已經(jīng)加載則直接返回。
- 在父類(lèi)加載器上調(diào)用 loadClass() 方法。如果父類(lèi)加載器為null,則使用根類(lèi)加載器來(lái)加載。
- 調(diào)用 findClass(String) 方法查找類(lèi)。
從上面步驟中可以看出,重寫(xiě) findClass()方法可以避免覆蓋默認(rèn)類(lèi)加載器的父類(lèi)委托、緩沖機(jī)制兩種策略:如果重寫(xiě) loadClass() 方法,則實(shí)現(xiàn)邏輯更為復(fù)雜。
在 ClassLoader 里還有一個(gè)核心方法:Class defineClass(String name, byte[] b, int off,int len) 該方法負(fù)責(zé)將指定類(lèi)的字節(jié)碼文件(即 Class 文件,如 Hello.class)讀入字節(jié)數(shù)組 byte[] b 內(nèi),并把它轉(zhuǎn)換為 Class對(duì)象,該字節(jié)碼文件可以來(lái)源于文件、網(wǎng)絡(luò)等。
defineClass() 方法管理 JVM 的許多復(fù)雜的實(shí)現(xiàn),它負(fù)責(zé)將字節(jié)碼分析成運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu),并校驗(yàn)有效性等。不過(guò)不用擔(dān)心,程序員無(wú)須重寫(xiě)該方法。實(shí)際上該方法是 final 的,即使想重寫(xiě)也沒(méi)有機(jī)會(huì)。
除此之外,ClassLoader 里還包含如下一些普通方法。
- findSystemClass(String name):從本地文件系統(tǒng)裝入文件。它在本地文件系統(tǒng)中尋找類(lèi)文件,如果存在,就使用 defineClass() 方法將原始字節(jié)轉(zhuǎn)換成 Class 對(duì)象,以將該文件轉(zhuǎn)換成類(lèi)。
- static getSystemClassLoader():這是一個(gè)靜態(tài)方法,用于返回系統(tǒng)類(lèi)加載器。
- getParent():獲取該類(lèi)加載器的父類(lèi)加載器。
- resolveClass(Class<?> c):鏈接指定的類(lèi)。類(lèi)加載器可以使用此方法來(lái)鏈接類(lèi)c。讀者無(wú)須理會(huì)關(guān)于此方法的太多細(xì)節(jié)。
- findLoadedClass(String name):如果此 Java 虛擬機(jī)已加載了名為 name 的類(lèi),則直接返回該類(lèi)對(duì)應(yīng)的 Class 實(shí)例,否則返回null,該方法是 Java 類(lèi)加載緩存機(jī)制的體現(xiàn)。
下面程序開(kāi)發(fā)了一個(gè)自定義的 ClassLoader,該 ClassLoader 通過(guò)重寫(xiě) findClass() 方法來(lái)實(shí)現(xiàn)自定義的類(lèi)加載機(jī)制。這個(gè) ClassLoader 可以在加載類(lèi)之前先編譯該類(lèi)的文件,從而實(shí)現(xiàn)運(yùn)行 Java 之前先編譯該程序的目標(biāo),這樣即可通過(guò)該 ClassLoader 直接運(yùn)行 Java 源文件。
public class CompileClassLoader extends ClassLoader {
// 讀取一個(gè)文件的內(nèi)容
private byte[] getBytes(String filename) throws IOException {
File file = new File(filename);
long len = file.length();
byte[] raw = new byte[(int) len];
try (FileInputStream fin = new FileInputStream(file)) {
// 一次讀取class文件的全部二進(jìn)制數(shù)據(jù)
int r = fin.read(raw);
if (r != len)
throw new IOException("無(wú)法讀取全部文件:" + r + " != " + len);
return raw;
}
}
// 定義編譯指定Java文件的方法
private boolean compile(String javaFile) throws IOException {
System.out.println("CompileClassLoader:正在編譯 " + javaFile + "...");
// 調(diào)用系統(tǒng)的javac命令
Process p = Runtime.getRuntime().exec("javac " + javaFile);
try {
// 其他線(xiàn)程都等待這個(gè)線(xiàn)程完成
p.waitFor();
} catch (InterruptedException ie) {
System.out.println(ie);
}
// 獲取javac線(xiàn)程的退出值
int ret = p.exitValue();
// 返回編譯是否成功
return ret == 0;
}
// 重寫(xiě)ClassLoader的findClass方法
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class clazz = null;
// 將包路徑中的點(diǎn)(.)替換成斜線(xiàn)(/)。
String fileStub = name.replace(".", "/");
String javaFilename = fileStub + ".java";
String classFilename = fileStub + ".class";
File javaFile = new File(javaFilename);
File classFile = new File(classFilename);
// 當(dāng)指定Java源文件存在,且class文件不存在、或者Java源文件
// 的修改時(shí)間比class文件修改時(shí)間更晚,重新編譯
if (javaFile.exists() && (!classFile.exists() || javaFile.lastModified() > classFile.lastModified())) {
try {
// 如果編譯失敗,或者該Class文件不存在
if (!compile(javaFilename) || !classFile.exists()) {
throw new ClassNotFoundException("ClassNotFoundExcetpion:" + javaFilename);
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
// 如果class文件存在,系統(tǒng)負(fù)責(zé)將該文件轉(zhuǎn)換成Class對(duì)象
if (classFile.exists()) {
try {
// 將class文件的二進(jìn)制數(shù)據(jù)讀入數(shù)組
byte[] raw = getBytes(classFilename);
// 調(diào)用ClassLoader的defineClass方法將二進(jìn)制數(shù)據(jù)轉(zhuǎn)換成Class對(duì)象
clazz = defineClass(name, raw, 0, raw.length);
} catch (IOException ie) {
ie.printStackTrace();
}
}
// 如果clazz為null,表明加載失敗,則拋出異常
if (clazz == null) {
throw new ClassNotFoundException(name);
}
return clazz;
}
// 定義一個(gè)主方法
public static void main(String[] args) throws Exception {
// 如果運(yùn)行該程序時(shí)沒(méi)有參數(shù),即沒(méi)有目標(biāo)類(lèi)
if (args.length < 1) {
System.out.println("缺少目標(biāo)類(lèi),請(qǐng)按如下格式運(yùn)行Java源文件:");
System.out.println("java CompileClassLoader ClassName");
}
// 第一個(gè)參數(shù)是需要運(yùn)行的類(lèi)
String progClass = args[0];
// 剩下的參數(shù)將作為運(yùn)行目標(biāo)類(lèi)時(shí)的參數(shù),
// 將這些參數(shù)復(fù)制到一個(gè)新數(shù)組中
String[] progArgs = new String[args.length - 1];
System.arraycopy(args, 1, progArgs, 0, progArgs.length);
CompileClassLoader ccl = new CompileClassLoader();
// 加載需要運(yùn)行的類(lèi)
Class<?> clazz = ccl.loadClass(progClass);
// 獲取需要運(yùn)行的類(lèi)的主方法
Method main = clazz.getMethod("main", (new String[0]).getClass());
Object[] argsArray = { progArgs };
main.invoke(null, argsArray);
}
}
上面程序中的粗體字代碼重寫(xiě)了 findClass() 方法,通過(guò)重寫(xiě)該方法就可以實(shí)現(xiàn)自定義的類(lèi)加載機(jī)制。在本類(lèi)的 findClass() 方法中先檢查需要加載類(lèi)的 Class 文件是否存在,如果不存在則先編譯源文件,再調(diào)用 ClassLoader 的 defineClass() 方法來(lái)加載這個(gè) Class 文件,并生成相應(yīng)的 Class 對(duì)象。
接下來(lái)可以隨意提供一個(gè)簡(jiǎn)單的主類(lèi),該主類(lèi)無(wú)須編譯就可以使用上面的 CompileClassLoader 來(lái)運(yùn)行它。
public class Hello {
public static void main(String[] args) {
for (String arg : args) {
System.out.println("運(yùn)行Hello的參數(shù):" + arg);
}
}
}

本示例程序提供的類(lèi)加載器功能比較簡(jiǎn)單,僅僅提供了在運(yùn)行之前先編譯 Java 源文件的功能。實(shí)際上,使用自定義的類(lèi)加載器,可以實(shí)現(xiàn)如下常見(jiàn)功能。
- 執(zhí)行代碼前自動(dòng)驗(yàn)證數(shù)字簽名。
- 根據(jù)用戶(hù)提供的密碼解密代碼,從而可以實(shí)現(xiàn)代碼混淆器來(lái)避免反編譯 *.class 文件。
- 根據(jù)用戶(hù)需求來(lái)動(dòng)態(tài)地加載類(lèi)。
- 根據(jù)應(yīng)用需求把其他數(shù)據(jù)以字節(jié)碼的形式加載到應(yīng)用中。
URLClassLoader 類(lèi)
Java 為 ClassLoader 提供了一個(gè) URLClassLoader 實(shí)現(xiàn)類(lèi),該類(lèi)也是系統(tǒng)類(lèi)加載器和擴(kuò)展類(lèi)加載器的父類(lèi)(此處的父類(lèi),就是指類(lèi)與類(lèi)之間的繼承關(guān)系)。URLClassLoader 功能比較強(qiáng)大,它既可以從本地文件系統(tǒng)獲取二進(jìn)制文件來(lái)加載類(lèi),也可以從遠(yuǎn)程主機(jī)獲取二進(jìn)制文件來(lái)加載類(lèi)。
在應(yīng)用程序中可以直接使用 URLClassLoader 加載類(lèi),URLClassLoader 類(lèi)提供了如下兩個(gè)構(gòu)造器。
- URLClassLoader(URL[] urls):使用默認(rèn)的父類(lèi)加載器創(chuàng)建一個(gè) ClassLoader 對(duì)象,該對(duì)象將從 urls 所指定的系列路徑來(lái)查詢(xún)并加載類(lèi)。
- URLClassLoader(URL[] urls, ClassLoader parent):使用指定的父類(lèi)加載器創(chuàng)建一個(gè) ClassLoader 對(duì)象,其他功能與前一個(gè)構(gòu)造器相同。
一旦得到了 URLClassLoader 對(duì)象之后,就可以調(diào)用該對(duì)象的 loadClass() 方法來(lái)加載指定類(lèi)。下面程序示范了如何直接從文件系統(tǒng)中加載 MySQL 驅(qū)動(dòng),并使用該驅(qū)動(dòng)來(lái)獲取數(shù)據(jù)庫(kù)連接。通過(guò)這種方式來(lái)獲取數(shù)據(jù)厙連接,可以無(wú)須將 MySQL 驅(qū)動(dòng)添加到 CLASSPATH 環(huán)境變量中。
public class URLClassLoaderTest {
private static Connection conn;
// 定義一個(gè)獲取數(shù)據(jù)庫(kù)連接方法
public static Connection getConn(String url, String user, String pass) throws Exception {
if (conn == null) {
// 創(chuàng)建一個(gè)URL數(shù)組
URL[] urls = { new URL("file:mysql-connector-java-5.1.30-bin.jar") };
// 以默認(rèn)的ClassLoader作為父ClassLoader,創(chuàng)建URLClassLoader
URLClassLoader myClassLoader = new URLClassLoader(urls);
// 加載MySQL的JDBC驅(qū)動(dòng),并創(chuàng)建默認(rèn)實(shí)例
Driver driver = (Driver) myClassLoader.loadClass("com.mysql.jdbc.Driver").getConstructor().newInstance();
// 創(chuàng)建一個(gè)設(shè)置JDBC連接屬性的Properties對(duì)象
Properties props = new Properties();
// 至少需要為該對(duì)象傳入user和password兩個(gè)屬性
props.setProperty("user", user);
props.setProperty("password", pass);
// 調(diào)用Driver對(duì)象的connect方法來(lái)取得數(shù)據(jù)庫(kù)連接
conn = driver.connect(url, props);
}
return conn;
}
public static void main(String[] args) throws Exception {
System.out.println(getConn("jdbc:mysql://localhost:3306/mysql", "root", "32147"));
}
}
上面程序中的前兩行粗體字代碼創(chuàng)建了一個(gè) URLClassLoader 對(duì)象,該對(duì)象使用默認(rèn)的父類(lèi)加載器,該類(lèi)加載器的類(lèi)加載路徑是當(dāng)前路徑下的 mysql-connector-java-5.1.30-bin.jar 文件,將 MySQL 驅(qū)動(dòng)復(fù)制到該路徑下,這樣保證該 ClassLoader 可以正常加載到 com.mysql.jdbc.Driver 類(lèi)。
程序的第三行粗體字代碼使用 ClassLoader 的 loadClass() 加載指定類(lèi),并調(diào)用 Class 對(duì)象的 newInstance() 方法創(chuàng)建了一個(gè)該類(lèi)的默認(rèn)實(shí)例——也就是得到 com.mysql.jdbc.Driver 類(lèi)的對(duì)象,當(dāng)然該對(duì)象的實(shí)現(xiàn)類(lèi)實(shí)現(xiàn)了 java.sql.Driver 接口,所以程序?qū)⑵鋸?qiáng)制類(lèi)型轉(zhuǎn)換為 Driver,程序的最后一行粗體字代碼通過(guò) Driver 而不是 DriverManager 來(lái)獲取數(shù)據(jù)庫(kù)連接,關(guān)于 Driver 接口的用法讀者可以自行查閱API文檔。
正如前面所看到的,創(chuàng)建 URLClassLoader 時(shí)傳入了一個(gè) URL 數(shù)組參數(shù),該 ClassLoader 就可以從這系列 URL 指定的資源中加載指定類(lèi),這里的 URL 可以以 file: 為前綴,表明從本地文件系統(tǒng)加載;可以以 http: 為前綴,表明從互聯(lián)網(wǎng)通過(guò) HTTP 訪(fǎng)問(wèn)來(lái)加載;也可以以 ftp: 為前綴,表明從互聯(lián)網(wǎng)通過(guò) FTP訪(fǎng)問(wèn)來(lái)加載......功能非常強(qiáng)大。
以上就是淺談JAVA 類(lèi)加載器的詳細(xì)內(nèi)容,更多關(guān)于JAVA 類(lèi)加載器的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- 詳解Java的類(lèi)加載機(jī)制及熱部署的原理
- java類(lèi)加載相關(guān)知識(shí)總結(jié)
- Java基礎(chǔ)之自定義類(lèi)加載器
- Java虛擬機(jī)之類(lèi)加載
- jvm之java類(lèi)加載機(jī)制和類(lèi)加載器(ClassLoader)的用法
- Java類(lèi)加載機(jī)制實(shí)現(xiàn)流程及原理詳解
- 詳解JAVA類(lèi)加載機(jī)制
- Java類(lèi)加載機(jī)制實(shí)現(xiàn)步驟解析
- Java基于自定義類(lèi)加載器實(shí)現(xiàn)熱部署過(guò)程解析
- 分析Java中的類(lèi)加載問(wèn)題
相關(guān)文章
JAVA線(xiàn)上常見(jiàn)問(wèn)題排查手段匯總
這篇文章主要介紹了JAVA線(xiàn)上常見(jiàn)問(wèn)題排查手段匯總,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08
快速搭建一個(gè)SpringBoot項(xiàng)目(純小白搭建教程)
本文主要介紹了快速搭建一個(gè)SpringBoot項(xiàng)目,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11
Spring Cloud 部署時(shí)使用 Kubernetes 作為注冊(cè)中心和配置中
Spring Cloud Kubernetes提供了使用Kubernete本地服務(wù)的Spring Cloud通用接口實(shí)現(xiàn),這篇文章主要介紹了Spring Cloud 部署時(shí)如何使用 Kubernetes 作為注冊(cè)中心和配置中心,需要的朋友可以參考下2024-05-05
Java中基于Nacos實(shí)現(xiàn)Sentinel規(guī)則持久化詳解
這篇文章主要介紹了Java中基于Nacos實(shí)現(xiàn)Sentinel規(guī)則持久化詳解,Sentinel Dashboard中添加的規(guī)則數(shù)據(jù)存儲(chǔ)在內(nèi)存,微服務(wù)停掉規(guī)則數(shù)據(jù)就消失,在?產(chǎn)環(huán)境下不合適,我們可以將Sentinel規(guī)則數(shù)據(jù)持久化到Nacos配置中?,讓微服務(wù)從Nacos獲取規(guī)則數(shù)據(jù),需要的朋友可以參考下2023-09-09
SpringBoot整合Ip2region獲取IP地址和定位的詳細(xì)過(guò)程
ip2region v2.0 - 是一個(gè)離線(xiàn)IP地址定位庫(kù)和IP定位數(shù)據(jù)管理框架,10微秒級(jí)別的查詢(xún)效率,提供了眾多主流編程語(yǔ)言的 xdb 數(shù)據(jù)生成和查詢(xún)客戶(hù)端實(shí)現(xiàn) ,這篇文章主要介紹了SpringBoot整合Ip2region獲取IP地址和定位,需要的朋友可以參考下2023-06-06
Java?Controller實(shí)現(xiàn)參數(shù)驗(yàn)證與統(tǒng)一異常處理流程詳細(xì)講解
Controller是Spring接受并處理網(wǎng)頁(yè)請(qǐng)求的組件,是整個(gè)應(yīng)用的入口,因此學(xué)會(huì)Controller的常用注解對(duì)理解一個(gè)應(yīng)用是重中之重。SpringBoot的Controller中經(jīng)常會(huì)用到注解@Controller、@RestController、@RequestMapping、@RequestBody等2023-01-01
Java8中字符串處理庫(kù)strman-java的使用示例
除了Java本身的字符串處理方式外,我們還可以使用Apache Common Langs里的StringUtils來(lái)簡(jiǎn)化String的操作。但以上兩種方式對(duì)于我們?nèi)粘>幊讨凶钊菀着龅降淖址幚韥?lái)說(shuō),仍然顯得有些不足。所以這篇文章給大家介紹Java8中字符串處理庫(kù)strman-java的使用。2016-09-09

