您好,登錄后才能下訂單哦!
這篇文章運用簡單易懂的例子給大家介紹java中類加載器的使用方法,代碼非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。
概述
Java類加載器( 英語:Java Classloader)是Java運行時環境(Java Runtime Environment)的一部分,負責動態加載Java類到 Java虛擬機的內存空間中。類通常是按需加載,即第一次使用該類時才加載。由于有了類加載器,Java運行時系統不需要知道文件與文件系統。學習類加載器時,掌握Java的委派概念很重要。
獲得ClassLoader的途徑
1. 獲得當前類的ClassLoader
clazz.getClassLoader()
2. 獲得當前線程上下文的ClassLoader
Thread.currentThread().getContextClassLoader();
3. 獲得系統的ClassLoader
ClassLoader.getSystemClassLoader()
4. 獲得調用者的ClassLoader
DriverManager.getCallerClassLoader
ClassLoader源碼解析
代碼一:
public class Test12 { public static void main(String[] args) { String[] strings = new String[6]; System.out.println(strings.getClass().getClassLoader()); // 運行結果:null Test12[] test12s = new Test12[1]; System.out.println(test12s.getClass().getClassLoader()); // 運行結果:sun.misc.Launcher$AppClassLoader@18b4aac2 int[] ints = new int[2]; System.out.println(ints.getClass().getClassLoader()); // 運行結果:null } }
loadClass方法
loadClass的源碼如下, loadClass方法加載擁有指定的二進制名稱的Class,默認按照如下順序尋找類:
a)調用findLoadedClass(String)檢查這個類是否被加載
b)調用父類加載器的loadClass方法,如果父類加載器為null,就會調用啟動類加載器
c)調用findClass(String)方法尋找
使用上述步驟如果類被找到且resolve為true,就會去調用resolveClass(Class)方法
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }
findClass方法
findClass的源碼如下,findClass尋找擁有指定二進制名稱的類,JVM鼓勵我們重寫此方法,需要自定義加載器遵循雙親委托機制,該方法會在檢查完父類加載器之后被loadClass方法調用,默認返回ClassNotFoundException異常。
protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); }
defineClass方法
defineClass的源碼如下,defineClass方法將一個字節數組轉換為Class的實例。
protected final Class<?> defineClass(String name, byte[] b, int off, int len, ProtectionDomain protectionDomain) throws ClassFormatError { protectionDomain = preDefineClass(name, protectionDomain); String source = defineClassSourceLocation(protectionDomain); Class<?> c = defineClass1(name, b, off, len, protectionDomain, source); postDefineClass(c, protectionDomain); return c; }
自定義類加載器
/** * 繼承了ClassLoader,這是一個自定義的類加載器 * @author 夜的那種黑丶 */ public class ClassLoaderTest extends ClassLoader { public static void main(String[] args) throws Exception { ClassLoaderTest loader = new ClassLoaderTest("loader"); Class<?> clazz = loader.loadClass("classloader.Test01"); Object object = clazz.newInstance(); System.out.println(object); System.out.println(object.getClass().getClassLoader()); } //------------------------------以上為測試代碼--------------------------------- /** * 類加載器名稱,標識作用 */ private String classLoaderName; /** * 從磁盤讀物字節碼文件的擴展名 */ private String fileExtension = ".class"; /** * 創建一個類加載器對象,將系統類加載器當做該類加載器的父加載器 * @param classLoaderName 類加載器名稱 */ private ClassLoaderTest(String classLoaderName) { // 將系統類加載器當做該類加載器的父加載器 super(); this.classLoaderName = classLoaderName; } /** * 創建一個類加載器對象,顯示指定該類加載器的父加載器 * 前提是需要有一個類加載器作為父加載器 * @param parent 父加載器 * @param classLoaderName 類加載器名稱 */ private ClassLoaderTest(ClassLoader parent, String classLoaderName) { // 顯示指定該類加載器的父加載器 super(parent); this.classLoaderName = classLoaderName; } /** * 尋找擁有指定二進制名稱的類,重寫ClassLoader類的同名方法,需要自定義加載器遵循雙親委托機制 * 該方法會在檢查完父類加載器之后被loadClass方法調用 * 默認返回ClassNotFoundException異常 * @param className 類名 * @return Class的實例 * @throws ClassNotFoundException 如果類不能被找到,拋出此異常 */ @Override protected Class<?> findClass(String className) throws ClassNotFoundException { byte[] data = this.loadClassData(className); /* * 通過defineClass方法將字節數組轉換為Class * defineClass:將一個字節數組轉換為Class的實例,在使用這個Class之前必須要被解析 */ return this.defineClass(className, data, 0 , data.length); } /** * io操作,根據類名找到對應文件,返回class文件的二進制信息 * @param className 類名 * @return class文件的二進制信息 * @throws ClassNotFoundException 如果類不能被找到,拋出此異常 */ private byte[] loadClassData(String className) throws ClassNotFoundException { InputStream inputStream = null; byte[] data; ByteArrayOutputStream byteArrayOutputStream = null; try { this.classLoaderName = this.classLoaderName.replace(".", "/"); inputStream = new FileInputStream(new File(className + this.fileExtension)); byteArrayOutputStream = new ByteArrayOutputStream(); int ch; while (-1 != (ch = inputStream.read())) { byteArrayOutputStream.write(ch); } data = byteArrayOutputStream.toByteArray(); } catch (Exception e) { throw new ClassNotFoundException(); } finally { try { if (inputStream != null) { inputStream.close(); } if (byteArrayOutputStream != null) { byteArrayOutputStream.close(); } } catch (IOException e) { e.printStackTrace(); } } return data; } }
以上是一段自定義類加載器的代碼,我們執行這段代碼
classloader.Test01@7f31245a sun.misc.Launcher$AppClassLoader@18b4aac2
可以看見,這段代碼中進行類加載的類加載器還是系統類加載器(AppClassLoader)。這是因為jvm的雙親委托機制造成的,private ClassLoaderTest(String classLoaderName)
將系統類加載器當做我們自定義類加載器的父加載器,jvm的雙親委托機制使自定義類加載器委托系統類加載器完成加載。
改造以下代碼,添加一個path屬性用來指定類加載位置:
public class ClassLoaderTest extends ClassLoader { public static void main(String[] args) throws Exception { ClassLoaderTest loader = new ClassLoaderTest("loader"); loader.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/"); Class<?> clazz = loader.loadClass("classloader.Test01"); System.out.println("class:" + clazz); Object object = clazz.newInstance(); System.out.println(object); System.out.println(object.getClass().getClassLoader()); } //------------------------------以上為測試代碼--------------------------------- /** * 從指定路徑加載 */ private String path; ...... /** * io操作,根據類名找到對應文件,返回class文件的二進制信息 * @param className 類名 * @return class文件的二進制信息 * @throws ClassNotFoundException 如果類不能被找到,拋出此異常 */ private byte[] loadClassData(String className) throws ClassNotFoundException { InputStream inputStream = null; byte[] data; ByteArrayOutputStream byteArrayOutputStream = null; className = className.replace(".", "/"); try { this.classLoaderName = this.classLoaderName.replace(".", "/"); inputStream = new FileInputStream(new File(this.path + className + this.fileExtension)); byteArrayOutputStream = new ByteArrayOutputStream(); int ch; while (-1 != (ch = inputStream.read())) { byteArrayOutputStream.write(ch); } data = byteArrayOutputStream.toByteArray(); } catch (Exception e) { throw new ClassNotFoundException(); } finally { try { if (inputStream != null) { inputStream.close(); } if (byteArrayOutputStream != null) { byteArrayOutputStream.close(); } } catch (IOException e) { e.printStackTrace(); } } return data; } public void setPath(String path) { this.path = path; } }
運行一下
class:class classloader.Test01 classloader.Test01@7f31245a sun.misc.Launcher$AppClassLoader@18b4aac2
修改一下測試代碼,并刪除工程下的Test01.class文件
public static void main(String[] args) throws Exception { ClassLoaderTest loader = new ClassLoaderTest("loader"); loader.setPath("/home/fanxuan/桌面/"); Class<?> clazz = loader.loadClass("classloader.Test01"); System.out.println("class:" + clazz); Object object = clazz.newInstance(); System.out.println(object); System.out.println(object.getClass().getClassLoader()); }
運行一下
class:class classloader.Test01 classloader.Test01@135fbaa4 classloader.ClassLoaderTest@7f31245a
分析
改造后的兩塊代碼,第一塊代碼中加載類的是系統類加載器AppClassLoader,第二塊代碼中加載類的是自定義類加載器ClassLoaderTest。是因為ClassLoaderTest會委托他的父加載器AppClassLoader加載class,第一塊代碼的path直接是工程下,AppClassLoader可以加載到,而第二塊代碼的path在桌面目錄下,所以AppClassLoader無法加載到,然后ClassLoaderTest自身嘗試加載并成功加載到。如果第二塊代碼工程目錄下的Test01.class文件沒有被刪除,那么依然是AppClassLoader加載。
再來測試一塊代碼
public static void main(String[] args) throws Exception { ClassLoaderTest loader = new ClassLoaderTest("loader"); loader.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/"); Class<?> clazz = loader.loadClass("classloader.Test01"); System.out.println("class:" + clazz.hashCode()); Object object = clazz.newInstance(); System.out.println(object.getClass().getClassLoader()); ClassLoaderTest loader2 = new ClassLoaderTest("loader"); loader2.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/"); Class<?> clazz2 = loader2.loadClass("classloader.Test01"); System.out.println("class:" + clazz2.hashCode()); Object object2 = clazz2.newInstance(); System.out.println(object2.getClass().getClassLoader()); }
結果顯而易見,類由系統類加載器加載,并且clazz和clazz2是相同的。
class:2133927002 sun.misc.Launcher$AppClassLoader@18b4aac2 class:2133927002 sun.misc.Launcher$AppClassLoader@18b4aac2
再改造一下
public static void main(String[] args) throws Exception { ClassLoaderTest loader = new ClassLoaderTest("loader"); loader.setPath("/home/fanxuan/桌面/"); Class<?> clazz = loader.loadClass("classloader.Test01"); System.out.println("class:" + clazz.hashCode()); Object object = clazz.newInstance(); System.out.println(object.getClass().getClassLoader()); ClassLoaderTest loader2 = new ClassLoaderTest("loader2"); loader2.setPath("/home/fanxuan/桌面/"); Class<?> clazz2 = loader2.loadClass("classloader.Test01"); System.out.println("class:" + clazz2.hashCode()); Object object2 = clazz2.newInstance(); System.out.println(object2.getClass().getClassLoader()); }
運行結果
class:325040804 classloader.ClassLoaderTest@7f31245a class:621009875 classloader.ClassLoaderTest@45ee12a7
ClassLoaderTest是顯而易見,但是clazz和clazz2是不同的,這是因為類加載器的命名空間的原因。
我們可以通過設置父類加載器來讓loader和loader2處于同一命名空間
public static void main(String[] args) throws Exception { ClassLoaderTest loader = new ClassLoaderTest("loader"); loader.setPath("/home/fanxuan/桌面/"); Class<?> clazz = loader.loadClass("classloader.Test01"); System.out.println("class:" + clazz.hashCode()); Object object = clazz.newInstance(); System.out.println(object.getClass().getClassLoader()); ClassLoaderTest loader2 = new ClassLoaderTest(loader, "loader2"); loader2.setPath("/home/fanxuan/桌面/"); Class<?> clazz2 = loader2.loadClass("classloader.Test01"); System.out.println("class:" + clazz2.hashCode()); Object object2 = clazz2.newInstance(); System.out.println(object2.getClass().getClassLoader()); }
運行結果
class:325040804 classloader.ClassLoaderTest@7f31245a class:325040804 classloader.ClassLoaderTest@7f31245a
擴展:命名空間
1. 每個類加載器都有自己的命名空間,命名空間由該加載器及所有的父加載器所加載的類組成
2. 在同一命名空間中,不會出現類的完整名字(包括類的包名)相同的兩個類
3. 在不同的命名空間中,有可能會出現類的完整名字(包括類的包名)相同的兩個類
關于java中類加載器的使用方法就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。