為什么要自定義類加載器
在某些框架內進行中間件與應用的模塊隔離,把類加載到不同的環境,如Tomcat這類Web應用服務器,內部自定義了好幾種類加載器,用于隔離同一個Web應用服務器上的不同應用程序
類的加載模型并非強制,除Bootstrap外,其他的加載并非一定要引入,根據實際情況在某個時間點進行按需進行動態加載
Java代碼容易被編譯和篡改,可以進行編譯加密,那么類加載也需要自定義,還原加密的字節碼
常見的場景
實現類似進程內隔離,類加載器實際上用作不同的命名空間,以提供類似容器、模塊化的效果,如兩個模塊依賴于某個類庫的不同版本,如果分別被不同的容器加載,就可以互不干擾,這個方面集大成者是JavaEE和OSGI,JPMS等框架 應用需要從不同的數據源獲取類定義信息,如網絡數據源,而不是本地文件系統,或者是需要自己操縱字節碼,動態修改或生成類型
注意
在一般情況下,使用不同的類加載器去加載不同的功能模塊,會提高應用程序的安全性,但是如果涉及Java類型轉換,則加載器類反而容易產生不美好的事情,在做Java類型轉換時,只有兩個類型都是由同一個加載器所加載,才能進行類型轉換,否則轉換時會發生異常
類加載器
實現方式
Java提供了抽象類java.lang.ClassLoader,所有用戶自定義的類加載器都應該繼承ClassLoader類 在自定義ClassLoader的子類時,我們常見有兩種做法
重寫loadClass()方法 重寫findClass()方法–>推薦
對比
不建議直接修改loadClass(),而是在findClass里重寫自定義類的加載方法,根據參數指定類的名字,返回對應的Class對象引用
loadClass()這個方法是實現雙親委派模型的地方,擅自修改這個方法會導致模型被破壞,容易造成問題,因此我們最好是在雙親委派模型框架下進行小范圍的改動,不破壞原有的穩定結構,同時,也避免了自己重寫loadClass方法的過程中必須寫雙親委托的重復代碼,從代碼的復用性來看,不直接修改這個方法始終是比較好的選擇 當編寫好自定義類加載器后,便可以在程序中調用loadClass方法實現類加載
說明
其父類加載器是系統類加載器 JVM中所有類加載都會使用java.lang.ClassLoader.loadClass(String)接口(自定義類加載器并重寫java.lang.ClassLoader.loadClass(String)接口除外),連JDK的核心類庫也不能例外
package com. chapter11 ; import java. io. BufferedInputStream ;
import java. io. ByteArrayOutputStream ;
import java. io. FileInputStream ;
import java. io. IOException ;
public class MyClassLoader extends ClassLoader { private String byteCodePath; public MyClassLoader ( String byteCodePath) { this . byteCodePath = byteCodePath; } public MyClassLoader ( ClassLoader parent, String byteCodePath) { super ( parent) ; this . byteCodePath = byteCodePath; } @Override protected Class < ? > findClass ( String className) throws ClassNotFoundException { BufferedInputStream bis = null ; ByteArrayOutputStream baos = null ; try { String fileName = byteCodePath + className + ".class" ; bis = new BufferedInputStream ( new FileInputStream ( fileName) ) ; baos = new ByteArrayOutputStream ( ) ; int len; byte [ ] data = new byte [ 1024 ] ; while ( ( len = bis. read ( data) ) != - 1 ) { baos. write ( data, 0 , len) ; } byte [ ] byteCodes = baos. toByteArray ( ) ; Class < ? > clazz = defineClass ( null , byteCodes, 0 , byteCodes. length) ; return clazz; } catch ( IOException e) { e. printStackTrace ( ) ; } finally { try { if ( baos != null ) { baos. close ( ) ; } if ( bis != null ) { bis. close ( ) ; } } catch ( IOException e) { e. printStackTrace ( ) ; } } return null ; }
}
public class MyClassLoaderTest { public static void main ( String [ ] args) { MyClassLoader loader = new MyClassLoader ( "d:/" ) ; try { Class < ? > clazz = loader. loadClass ( "JavapTest" ) ; System . out. println ( "加載此類的加載器為:" + clazz. getClassLoader ( ) . getClass ( ) . getName ( ) ) ; System . out. println ( "加載當前JavapTest類的加載器的父類加載器為:" + clazz. getClassLoader ( ) . getParent ( ) . getClass ( ) . getName ( ) ) ; } catch ( ClassNotFoundException e) { e. printStackTrace ( ) ; } }
}
加載此類的加載器為:com. chapter11. MyClassLoader
加載當前JavapTest 類的加載器的父類加載器為:sun. misc. Launcher $AppClassLoader