本文將從多個方面對Spring Boot本地類和Jar包類加載順序做詳細的闡述,並給出相應的代碼示例。
一、類加載機制概述
在介紹Spring Boot本地類和Jar包類加載順序之前,有必要簡要介紹一下Java的類加載機制。
Java虛擬機在程序運行期間動態加載類,並將類的字節碼轉換成運行時數據結構,形成可執行代碼。類的加載過程由ClassLoader類及其子類完成。
Java虛擬機規範將ClassLoader類設計成了一個抽象類,用以描述Java虛擬機中存在的類加載器,用以加載Class文件。ClassLoader類的任務是把class文件字節碼轉化為java.lang.Class的一個實例。而Class實例通常存放於運行時數據區的方法區內。
二、本地類和Jar包類加載順序詳解
2.1 Jar包優先於本地類
在Java類加載過程中,如果系統中存在多個相同的類文件,則優先加載JVM類路徑下的Jar包中的class文件,而忽略本地類路徑下的同名class文件。
為了說明這一點,我們可以自己手寫一個同名class文件,並同時存放在JVM類路徑下和本地類路徑下。接着,我們在代碼中進行加載並打印該class文件的內容。
// 在JVM類路徑下創建SameClass.java
package com.example.demo;
public class SameClass {
public static void printFile() {
System.out.println("This is a file in JVM classpath.");
}
}
// 在本地類路徑下創建SameClass.java,內容和上述相同
package com.example.demo;
public class SameClass {
public static void printFile() {
System.out.println("This is a file in local classpath.");
}
}
// 在代碼中進行加載並打印SameClass類文件的內容
package com.example.demo;
public class ClassLoadingOrderDemoApplication {
public static void main(String[] args) throws ClassNotFoundException {
ClassLoader classLoader = ClassLoadingOrderDemoApplication.class.getClassLoader();
Class clazz = classLoader.loadClass("com.example.demo.SameClass");
clazz.getMethod("printFile").invoke(null, null);
}
}
運行上面的代碼之後,我們發現輸出結果是”This is a file in JVM classpath.”。
這是因為JVM類路徑下的SameClass.class文件優先被加載,而本地類路徑下的同名文件被忽略了。
2.2 本地類優先於系統類
對於Jar包中不存在的類文件,Java會首先從本地文件系統中查找,然後才會查找系統類庫(如JDK中的rt.jar)。
同樣地,我們可以手寫一個只在本地類路徑下存在的class文件,並在代碼中進行加載和打印。
// 在本地類路徑下創建OnlyLocalClass.java
package com.example.demo;
public class OnlyLocalClass {
public static void printFile() {
System.out.println("This is a file in local classpath, but not in the system classpath.");
}
}
// 在代碼中進行加載並打印OnlyLocalClass類文件的內容
package com.example.demo;
public class ClassLoadingOrderDemoApplication {
public static void main(String[] args) throws ClassNotFoundException {
ClassLoader classLoader = ClassLoadingOrderDemoApplication.class.getClassLoader();
Class clazz = classLoader.loadClass("com.example.demo.OnlyLocalClass");
clazz.getMethod("printFile").invoke(null, null);
}
}
運行上述代碼之後,我們可以發現打印結果為”This is a file in local classpath, but not in the system classpath.”。
由此可見,本地類優先於系統類。
2.3 父ClassLoader優先於子ClassLoader
對於同一份class文件,父ClassLoader的加載順序優先於子ClassLoader。
我們可以通過自定義ClassLoader來模擬這種情況,並在代碼中進行加載和打印。
// 自定義ClassLoader
package com.example.demo;
import java.io.IOException;
import java.io.InputStream;
public class MyClassLoader extends ClassLoader {
@Override
protected Class findClass(String name) throws ClassNotFoundException {
try {
String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
InputStream is = this.getClass().getResourceAsStream(fileName);
if (is == null) {
return super.loadClass(name);
}
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
throw new ClassNotFoundException(name);
}
}
}
// 在本地類路徑下創建ParentClass.java
package com.example.demo;
public class ParentClass {
public static void printFile() {
System.out.println("This is a file in parent classloader.");
}
}
// 在代碼中進行加載並打印ParentClass類文件的內容
package com.example.demo;
public class ClassLoadingOrderDemoApplication {
public static void main(String[] args) throws ClassNotFoundException {
ClassLoader parentClassLoader = new MyClassLoader();
ClassLoader childClassLoader = new MyClassLoader();
parentClassLoader.loadClass("com.example.demo.ParentClass");
Class clazz = childClassLoader.loadClass("com.example.demo.ParentClass");
clazz.getMethod("printFile").invoke(null, null);
}
}
運行上述代碼之後,我們可以發現打印結果為”This is a file in parent classloader.”,即父ClassLoader先於子ClassLoader加載了該class文件。
三、小結
本文從類加載機制概述、Jar包與本地類加載順序、父ClassLoader與子ClassLoader的加載順序等多個方面,對Spring Boot本地類和Jar包類加載順序做了詳細剖析。
Java虛擬機能夠動態加載類,並將類的字節碼轉換成運行時數據結構形成可執行代碼,類的加載過程由ClassLoader類及其子類完成。
原創文章,作者:OISGV,如若轉載,請註明出處:https://www.506064.com/zh-hant/n/373731.html
微信掃一掃
支付寶掃一掃