本文將從多個方面對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-tw/n/373731.html