午夜剧场伦理_日本一道高清_国产又黄又硬_91黄色网战_女同久久另类69精品国产_妹妹的朋友在线

您的位置:首頁(yè)技術(shù)文章
文章詳情頁(yè)

淺談JAVA 類加載器

瀏覽:92日期:2022-08-30 15:34:48

類加載機(jī)制

類加載器負(fù)責(zé)加載所有的類,系統(tǒng)為所有被載入內(nèi)存中的類生成一個(gè) java.lang.Class 實(shí)例。一旦一個(gè)類被載入 JVM 中,同個(gè)類就不會(huì)被再次載入了。現(xiàn)在的問題是,怎么樣才算“同一個(gè)類”?

正如一個(gè)對(duì)象有一個(gè)唯一的標(biāo)識(shí)一樣,一個(gè)載入 JVM 中的類也有一個(gè)唯一的標(biāo)識(shí)。在 Java 中,一個(gè)類用其全限定類名(包括包名和類名)作為標(biāo)識(shí):但在 JVM 中,一個(gè)類用其全限定類名和其類加載器作為唯一標(biāo)識(shí)。例如,如果在 pg 的包中有一個(gè)名為 Person 的類,被類加載器 ClassLoader 的實(shí)例 k1 負(fù)責(zé)加載,則該 Person 類對(duì)應(yīng)的 Class 對(duì)象在 JVM 中表示為(Person、pg、k1)。這意味著兩個(gè)類加載器加載的同名類:(Person、pg、k1)和(Person、pg、k12)是不同的,它們所加載的類也是完全不同、互不兼容的。

當(dāng) JVM 啟動(dòng)時(shí),會(huì)形成由三個(gè)類加載器組成的初始類加載器層次結(jié)構(gòu)。

Bootstrap ClassLoader:根類加載器。 Extension ClassLoader:擴(kuò)展類加載器。 System ClassLoader:系統(tǒng)類加載器。

Bootstrap ClassLoader 被稱為引導(dǎo)(也稱為原始或根)類加載器,它負(fù)責(zé)加載 Java 的核心類。在Sun 的 JVM 中,當(dāng)執(zhí)行 java.exe 命令時(shí),使用 -Xbootclasspath 或 -D 選項(xiàng)指定 sun.boot.class.path 系統(tǒng)屬性值可以指定加載附加的類。

JVM的類加載機(jī)制主要有如下三種。

全盤負(fù)責(zé)。所謂全盤負(fù)責(zé),就是當(dāng)一個(gè)類加載器負(fù)責(zé)加載某個(gè) Class 時(shí),該 Class 所依賴的和引用的其他 Class 也將由該類加載器負(fù)責(zé)載入,除非顯式使用另外一個(gè)類加載器來(lái)載入。 父類委托。所謂父類委托,則是先讓 parent(父)類加載器試圖加載該 Class,只有在父類加載器無(wú)法加載該類時(shí)才嘗試從自己的類路徑中加載該類。 緩存機(jī)制。緩存機(jī)制將會(huì)保證所有加載過的 Class 都會(huì)被緩存,當(dāng)程序中需要使用某個(gè) Class 時(shí),類加載器先從緩存區(qū)中搜尋該 Class,只有當(dāng)緩存區(qū)中不存在該 Class 對(duì)象時(shí),系統(tǒng)才會(huì)讀取該類對(duì)應(yīng)的二進(jìn)制數(shù)據(jù),并將其轉(zhuǎn)換成 Class 對(duì)象,存入緩存區(qū)中。這就是為什么修改了 Class 后,必須重新啟動(dòng) JVM,程序所做的修改才會(huì)生效的原因。

除了可以使用 Java 提供的類加載器之外,開發(fā)者也可以實(shí)現(xiàn)自己的類加載器,自定義的類加載器通過繼承 ClassLoader 來(lái)實(shí)現(xiàn)。JVM 中這4種類加載器的層次結(jié)構(gòu)如下圖所示。

淺談JAVA 類加載器

注意:類加載器之間的父子關(guān)系并不是類繼承上的父子關(guān)系,這里的父子關(guān)系是類加載器實(shí)例之間的關(guān)系

下面程序示范了訪問 JVM 的類加載器。

public class ClassLoaderPropTest { public static void main(String[] args) throws IOException { // 獲取系統(tǒng)類加載器 ClassLoader systemLoader = ClassLoader.getSystemClassLoader(); System.out.println('系統(tǒng)類加載器:' + systemLoader); /* * 獲取系統(tǒng)類加載器的加載路徑——通常由CLASSPATH環(huán)境變量指定 如果操作系統(tǒng)沒有指定CLASSPATH環(huán)境變量,默認(rèn)以當(dāng)前路徑作為 * 系統(tǒng)類加載器的加載路徑 */ Enumeration<URL> em1 = systemLoader.getResources(''); while (em1.hasMoreElements()) { System.out.println(em1.nextElement()); } // 獲取系統(tǒng)類加載器的父類加載器:得到擴(kuò)展類加載器 ClassLoader extensionLader = systemLoader.getParent(); System.out.println('擴(kuò)展類加載器:' + extensionLader); System.out.println('擴(kuò)展類加載器的加載路徑:' + System.getProperty('java.ext.dirs')); System.out.println('擴(kuò)展類加載器的parent: ' + extensionLader.getParent()); }}

運(yùn)行上面的程序,會(huì)看到如下運(yùn)行結(jié)果

系統(tǒng)類加載器:sun.misc.Launcher$AppClassLoader@73d16e93file:/F:/EclipseProjects/demo/bin/擴(kuò)展類加載器:sun.misc.Launcher$ExtClassLoader@15db9742擴(kuò)展類加載器的加載路徑:C:Program FilesJavajre1.8.0_181libext;C:WindowsSunJavalibext擴(kuò)展類加載器的parent: null

從上面運(yùn)行結(jié)果可以看出,系統(tǒng)類加載器的加載路徑是程序運(yùn)行的當(dāng)前路徑,擴(kuò)展類加載器的加載路徑是null(與 Java8 有區(qū)別),但此處看到擴(kuò)展類加載器的父加載器是null,并不是根類加載器。這是因?yàn)楦惣虞d器并沒有繼承 ClassLoader 抽象類,所以擴(kuò)展類加載器的 getParent() 方法返回null。但實(shí)際上,擴(kuò)展類加載器的父類加載器是根類加載器,只是根類加載器并不是 Java 實(shí)現(xiàn)的。

從運(yùn)行結(jié)果可以看出,系統(tǒng)類加載器是 AppClassLoader 的實(shí)例,擴(kuò)展類加載器 ExtClassLoader 的實(shí)例。實(shí)際上,這兩個(gè)類都是 URLClassLoader 類的實(shí)例。

注意:JVM 的根類加載器并不是 Java 實(shí)現(xiàn)的,而且由于程序通常無(wú)須訪問根類加載器,因此訪問擴(kuò)展類加載器的父類加載器時(shí)返回null。

類加載器加載 Class 大致要經(jīng)過如下8個(gè)步驟。

檢測(cè)此 Class 是否載入過(即在緩存區(qū)中是否有此Class),如果有則直接進(jìn)入第8步,否則接著執(zhí)行第2步。 如果父類加載器不存在(如果沒有父類加載器,則要么 parent 一定是根類加載器,要么本身就是根類加載器),則跳到第4步執(zhí)行;如果父類加載器存在,則接著執(zhí)行第3步。 請(qǐng)求使用父類加載器去載入目標(biāo)類,如果成功載入則跳到第8步,否則接著執(zhí)行第5步。 請(qǐng)求使用根類加載器來(lái)載入目標(biāo)類,如果成功載入則跳到第8步,否則跳到第7步。 當(dāng)前類加載器嘗試尋找 Class 文件(從與此 ClassLoader 相關(guān)的類路徑中尋找),如果找到則執(zhí)行第6步,如果找不到則跳到第7步。 從文件中載入 Class,成功載入后跳到第8步。 拋出 ClassNotFoundExcepuon 異常。 返回對(duì)應(yīng)的 java.lang.Class 對(duì)象。

其中,第5、6步允許重寫 ClassLoader的 findClass() 方法來(lái)實(shí)現(xiàn)自己的載入策略,甚至重寫 loadClass() 方法來(lái)實(shí)現(xiàn)自己的載入過程。

創(chuàng)建并使用自定義的類加載器

JVM 中除根類加載器之外的所有類加載器都是 ClassLoader 子類的實(shí)例,開發(fā)者可以通過擴(kuò)展 ClassLoader 的子類,并重寫該 ClassLoader 所包含的方法來(lái)實(shí)現(xiàn)自定義的類加載器。查閱API文檔中關(guān)于 ClassLoader 的方法不難發(fā)現(xiàn),ClassLoader 中包含了大量的 protected 方法——這些方法都可被子類重寫。

ClassLoader 類有如下兩個(gè)關(guān)鍵方法。

loadClass(String name, boolean resolve):該方法為 ClassLoader 的入口點(diǎn),根據(jù)指定名稱來(lái)加載類,系統(tǒng)就是調(diào)用 ClassLoader 的該方法來(lái)獲取指定類對(duì)應(yīng)的 Class 對(duì)象。 findClass(String name):根據(jù)指定名稱來(lái)查找類。

如果需要實(shí)現(xiàn)自定義的 ClassLoader,則可以通過重寫以上兩個(gè)方法來(lái)實(shí)現(xiàn),通常推薦重寫 findClass() 方法,而不是重寫 loadClass() 方法。loadClass() 方法的執(zhí)行步驟如下。

用 findLoadedClass(String) 來(lái)檢查是否已經(jīng)加載類,如果已經(jīng)加載則直接返回。 在父類加載器上調(diào)用 loadClass() 方法。如果父類加載器為null,則使用根類加載器來(lái)加載。 調(diào)用 findClass(String) 方法查找類。

從上面步驟中可以看出,重寫 findClass()方法可以避免覆蓋默認(rèn)類加載器的父類委托、緩沖機(jī)制兩種策略:如果重寫 loadClass() 方法,則實(shí)現(xiàn)邏輯更為復(fù)雜。

在 ClassLoader 里還有一個(gè)核心方法:Class defineClass(String name, byte[] b, int off,int len) 該方法負(fù)責(zé)將指定類的字節(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)有效性等。不過不用擔(dān)心,程序員無(wú)須重寫該方法。實(shí)際上該方法是 final 的,即使想重寫也沒有機(jī)會(huì)。

除此之外,ClassLoader 里還包含如下一些普通方法。

findSystemClass(String name):從本地文件系統(tǒng)裝入文件。它在本地文件系統(tǒng)中尋找類文件,如果存在,就使用 defineClass() 方法將原始字節(jié)轉(zhuǎn)換成 Class 對(duì)象,以將該文件轉(zhuǎn)換成類。 static getSystemClassLoader():這是一個(gè)靜態(tài)方法,用于返回系統(tǒng)類加載器。 getParent():獲取該類加載器的父類加載器。 resolveClass(Class<?> c):鏈接指定的類。類加載器可以使用此方法來(lái)鏈接類c。讀者無(wú)須理會(huì)關(guān)于此方法的太多細(xì)節(jié)。 findLoadedClass(String name):如果此 Java 虛擬機(jī)已加載了名為 name 的類,則直接返回該類對(duì)應(yīng)的 Class 實(shí)例,否則返回null,該方法是 Java 類加載緩存機(jī)制的體現(xiàn)。

下面程序開發(fā)了一個(gè)自定義的 ClassLoader,該 ClassLoader 通過重寫 findClass() 方法來(lái)實(shí)現(xiàn)自定義的類加載機(jī)制。這個(gè) ClassLoader 可以在加載類之前先編譯該類的文件,從而實(shí)現(xiàn)運(yùn)行 Java 之前先編譯該程序的目標(biāo),這樣即可通過該 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 { // 其他線程都等待這個(gè)線程完成 p.waitFor(); } catch (InterruptedException ie) { System.out.println(ie); } // 獲取javac線程的退出值 int ret = p.exitValue(); // 返回編譯是否成功 return ret == 0; } // 重寫ClassLoader的findClass方法 protected Class<?> findClass(String name) throws ClassNotFoundException { Class clazz = null; // 將包路徑中的點(diǎ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í)沒有參數(shù),即沒有目標(biāo)類 if (args.length < 1) { System.out.println('缺少目標(biāo)類,請(qǐng)按如下格式運(yùn)行Java源文件:'); System.out.println('java CompileClassLoader ClassName'); } // 第一個(gè)參數(shù)是需要運(yùn)行的類 String progClass = args[0]; // 剩下的參數(shù)將作為運(yùn)行目標(biāo)類時(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)行的類 Class<?> clazz = ccl.loadClass(progClass); // 獲取需要運(yùn)行的類的主方法 Method main = clazz.getMethod('main', (new String[0]).getClass()); Object[] argsArray = { progArgs }; main.invoke(null, argsArray); }}

上面程序中的粗體字代碼重寫了 findClass() 方法,通過重寫該方法就可以實(shí)現(xiàn)自定義的類加載機(jī)制。在本類的 findClass() 方法中先檢查需要加載類的 Class 文件是否存在,如果不存在則先編譯源文件,再調(diào)用 ClassLoader 的 defineClass() 方法來(lái)加載這個(gè) Class 文件,并生成相應(yīng)的 Class 對(duì)象。

接下來(lái)可以隨意提供一個(gè)簡(jiǎn)單的主類,該主類無(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); } }}

淺談JAVA 類加載器

本示例程序提供的類加載器功能比較簡(jiǎn)單,僅僅提供了在運(yùn)行之前先編譯 Java 源文件的功能。實(shí)際上,使用自定義的類加載器,可以實(shí)現(xiàn)如下常見功能。

執(zhí)行代碼前自動(dòng)驗(yàn)證數(shù)字簽名。 根據(jù)用戶提供的密碼解密代碼,從而可以實(shí)現(xiàn)代碼混淆器來(lái)避免反編譯 *.class 文件。 根據(jù)用戶需求來(lái)動(dòng)態(tài)地加載類。 根據(jù)應(yīng)用需求把其他數(shù)據(jù)以字節(jié)碼的形式加載到應(yīng)用中。

URLClassLoader 類

Java 為 ClassLoader 提供了一個(gè) URLClassLoader 實(shí)現(xiàn)類,該類也是系統(tǒng)類加載器和擴(kuò)展類加載器的父類(此處的父類,就是指類與類之間的繼承關(guān)系)。URLClassLoader 功能比較強(qiáng)大,它既可以從本地文件系統(tǒng)獲取二進(jìn)制文件來(lái)加載類,也可以從遠(yuǎn)程主機(jī)獲取二進(jìn)制文件來(lái)加載類。

在應(yīng)用程序中可以直接使用 URLClassLoader 加載類,URLClassLoader 類提供了如下兩個(gè)構(gòu)造器。

URLClassLoader(URL[] urls):使用默認(rèn)的父類加載器創(chuàng)建一個(gè) ClassLoader 對(duì)象,該對(duì)象將從 urls 所指定的系列路徑來(lái)查詢并加載類。 URLClassLoader(URL[] urls, ClassLoader parent):使用指定的父類加載器創(chuàng)建一個(gè) ClassLoader 對(duì)象,其他功能與前一個(gè)構(gòu)造器相同。

一旦得到了 URLClassLoader 對(duì)象之后,就可以調(diào)用該對(duì)象的 loadClass() 方法來(lái)加載指定類。下面程序示范了如何直接從文件系統(tǒng)中加載 MySQL 驅(qū)動(dòng),并使用該驅(qū)動(dòng)來(lái)獲取數(shù)據(jù)庫(kù)連接。通過這種方式來(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)的父類加載器,該類加載器的類加載路徑是當(dāng)前路徑下的 mysql-connector-java-5.1.30-bin.jar 文件,將 MySQL 驅(qū)動(dòng)復(fù)制到該路徑下,這樣保證該 ClassLoader 可以正常加載到 com.mysql.jdbc.Driver 類。

程序的第三行粗體字代碼使用 ClassLoader 的 loadClass() 加載指定類,并調(diào)用 Class 對(duì)象的 newInstance() 方法創(chuàng)建了一個(gè)該類的默認(rèn)實(shí)例——也就是得到 com.mysql.jdbc.Driver 類的對(duì)象,當(dāng)然該對(duì)象的實(shí)現(xiàn)類實(shí)現(xiàn)了 java.sql.Driver 接口,所以程序?qū)⑵鋸?qiáng)制類型轉(zhuǎn)換為 Driver,程序的最后一行粗體字代碼通過 Driver 而不是 DriverManager 來(lái)獲取數(shù)據(jù)庫(kù)連接,關(guān)于 Driver 接口的用法讀者可以自行查閱API文檔。

正如前面所看到的,創(chuàng)建 URLClassLoader 時(shí)傳入了一個(gè) URL 數(shù)組參數(shù),該 ClassLoader 就可以從這系列 URL 指定的資源中加載指定類,這里的 URL 可以以 file: 為前綴,表明從本地文件系統(tǒng)加載;可以以 http: 為前綴,表明從互聯(lián)網(wǎng)通過 HTTP 訪問來(lái)加載;也可以以 ftp: 為前綴,表明從互聯(lián)網(wǎng)通過 FTP訪問來(lái)加載......功能非常強(qiáng)大。

以上就是淺談JAVA 類加載器的詳細(xì)內(nèi)容,更多關(guān)于JAVA 類加載器的資料請(qǐng)關(guān)注好吧啦網(wǎng)其它相關(guān)文章!

標(biāo)簽: Java
相關(guān)文章:
主站蜘蛛池模板: 日韩激情在线播放 | 超碰人人人人人人人 | 四虎影院色 | 三级网站在线播放 | 亚洲国产精品女人久久久 | 天天艹天天 | 麻豆精品在线视频 | 久久精品一级片 | 精品一区二区三区视频 | 成人久久免费视频 | 国产美女激情视频 | 一级免费看片 | 亚洲国产精品视频一区 | 国产又粗又长又大 | 日韩三级久久久 | 亚洲a v网站 | 新呦u视频一区二区 | 岛国成人在线 | 黄视频在线播放 | a级在线 | 国产成人精品视频免费 | 欧美自拍偷拍第一页 | 日韩不卡中文字幕 | 日韩久久久久 | 日韩在线免费观看视频 | 日本黄网站| 久久亚洲区 | 成年免费视频黄网站在线观看 | 在线免费观看亚洲 | 亚洲性视频 | 2级毛片| 国产精品美女久久 | 草草影院欧美 | 国产精品综合 | 色播一区 | 国产精品一区二区三区四区五区 | 99久精品 | 色av中文字幕 | 欧美日韩一级二级 | 狠狠的色 | 一区二区三区四区视频在线 |