---- 其 實,Java 本 身 就 是 一 種 極 具 動 態 性 的 語 言。 類 似Windows 的 動 態 鏈 接 庫(DLL),Java 應 用 程 序 總 是 被 編 譯 成 若 幹 個 單 獨 的class 文 件, 程 序 執 行 時 根 據 需 要 由Java 虛 擬 機 動 態 載 入 相 應 的 類。 這 種 機 制 使 編 寫 動 態 的 分 布 式 應 用 程 序 成 爲 可 能: 我 們 可 以 在 客 戶 端 編 寫 自 己 的 類 載 入 器, 而 真 正 執 行 的 程 序 卻 存 放 在 本 地、 局 域 網 或 世 界 另 一 端 的 主 機 上。 下 面 將 介 紹 如 何 在 自 己 的 應 用 程 序 中 實 現Java 的 動 態 類 載 入 機 制。
與 動 態 類 載 入 有 關 的 系 統 類
---- 爲 支 持 動 態 類 載 入 機 制, 在 系 統 類 組java.lang 中 提 供 了 兩 個 類:Class 類 和ClassLoader 類。
---- 1、 類java.lang.Class。 在Java 虛 擬 機 中, 每 一 個 類 或 接 口 都 是 由Class 類 來 操 縱 的, 它 不 能 被 顯 式 的 實 例 化, 必 須 用 其 他 方 法 來 獲 取Class 類 的 對 象。 動 態 類 載 入 機 制 的 關 鍵 一 步 在 於 如 何 獲 得 指 定 類 的Class 類 型 的 對 象。 相 關 方 法 主 要 有:
---- public static Class forName(String className)
---- 這 是 一 個 靜 態 方 法, 它 獲 取 指 定 名 字 的 類 的Class 類 型 對 象, 類 名 可 以 是 象“sun.applet.Applet” 這 樣 的 字 符 串, 但 不 能 帶 有 路 徑 或 網 絡 地 址 等 信 息。 這 是 從 本 地 系 統 中 動 態 載 入 類 的 最 方 便 的 辦 法。
---- public Object newInstance()
---- 這 是 最 重 要 的 一 個 方 法, 它 建 立 由Class 類 型 對 象 描 述 的 指 定 類 的 實 例。
---- 下 面 是 一 個 用forName() 和newInstance() 方 法 實 現 動 態 類 載 入 的 代 碼,share 類 包 含 一 個 接 口, 詳 細 內 容 將 在 第 三 部 分 中 解 釋。
try{ //根據類名建立Class類型的對象。 Class cc =Class.forName("類名")); //建立被載入類類的實例並強制類型轉換, 值賦給share類型的變量。 share oo=((share)cc).newInstance(); //調用該類的方法進行工作。 } catch (Exception ex){ //如果發生例外,則進行相應處理。 };
---- 2、 類java.lang.ClassLoader。 這 是 一 個 抽 象 類, 如 果 打 算 運 用 它, 必 須 繼 承 它 並 重 寫 它 的loadClass() 方 法。 其 主 要 方 法 有:
---- protected ClassLoader();
---- 這 是 一 個 建 構 元, 可 以 用 它 建 立 一 個ClassLoader 類 的 實 例。 注 意 繼 承 這 個 類 的 類 必 須 重 寫 這 個 方 法, 而 不 能 使 用 缺 省 的 建 構 元。
---- protected abstract Class loadClass(String name, boolean resolve)
---- 載 入 指 定 的 類 數 據, 建 立Class 類 型 的 對 象 並 根 據 需 要 解 析 它。 這 是 一 個 抽 象 方 法, 大 家 必 須 在 自 己 的 子 類 中 重 寫 這 個 方 法, 重 寫 的 規 則 可 以 參 考 第 三 部 分 的 例 子。
---- protected final Class defineClass(byte data[], int offset, int length)
---- 將 字 節 數 組 中 的 數 據 定 義 爲Class 類 型 的 對 象, 字 節 數 組 的 格 式 由 虛 擬 機 規 定。
---- protected final Class findSystemClass(String name)
---- 根 據 指 定 的 類 名 載 入 類, 它 會 自 動 在 當 前 目 錄 和 環 境 變 量“CLASSPATH” 指 定 的 路 徑 中 尋 找, 如 果 找 不 到, 則 會 拋 出ClassNotFoundException 例 外。
---- protected final void resolveClass(Class c)
---- 通 過 載 入 與 指 定 的 類 相 關 的 所 有 類 來 解 析 這 個 類, 這 必 須 在 類 被 使 用 之 前 完 成。
擴 充ClasslLader 類 以 實 現 動 態 類 載 入
---- 理 解 動 態 類 載 入 機 制 的 最 好 辦 法 是 通 過 例 子, 下 面 這 個 完 整 的 例 子 由 四 個 類 組 成, 分 別 解 釋 如 下:
---- 1、MyClassLoader 類 是ClassLoader 類 的 子 類, 它 重 寫 了loadClass 方 法, 實 現 了 將 網 絡 上 用URL 地 址 指 定 的 類 動 態 載 入, 取 得 它 的Class 類 型 對 象 的 功 能。 讀 者 可 根 據 自 己 載 入 類 的 具 體 方 式 改 寫 下 面 的 代 碼。
import java.io.*; import java.util.*; import java.net.*; public class MyClassLoader extends ClassLoader { //定義哈希表(Hashtable)類型的變量, 用於保存被載入的類數據。 Hashtable loadedClasses; public MyClassLoader() { loadedClasses = new Hashtable(); } public synchronized Class loadClass(String className, boolean resolve) throws ClassNotFoundException { Class newClass; byte[] classData; //檢查要載入的類數據是否已經被保存在哈希表中。 newClass = (Class) loadedClasses.get(className); //如果類數據已經存在且resolve值爲true,則解析它。 if (newClass != null){ if (resolve) resolveClass(newClass); return newClass; }
---- /* 首 先 試 圖 從 本 地 系 統 類 組 中 載 入 指 定 類。 這 是 必 須 的, 因 爲 虛 擬 機 將 這 個 類 載 入 後, 在 解 析 和 執 行 它 時 所 用 到 的 任 何 其 他 類, 如java.lang.System 類 等, 均 不 再 使 用 虛 擬 機 的 類 載 入 器, 而 是 調 用 我 們 自 制 的 類 載 入 器 來 加 載。*/
try { newClass = findSystemClass(className); return newClass; } catch (ClassNotFoundException e) { System.out.println(className+" is not a system class!"); } //如果不是系統類, 則試圖從網絡中指定的URL地址載入類。 try { //用自定義方法載入類數據, 存放於字節數組classData中。 classData = getClassData(className); //由字節數組所包含的數據建立一個class類型的對象。 newClass = defineClass(classData, 0, classData.length); if (newClass == null) throw new ClassNotFoundException(className); } catch (Exception e) { throw new ClassNotFoundException(className); } //如果類被正確載入, 則將類數據保存在哈希表中,以備再次使用。 loadedClasses.put(className, newClass); //如果resolve值爲true,則解析類數據。 if (resolve){ resolveClass(newClass); } return newClass; } //這個方法從網絡中載入類數據。 protected byte[] getClassData(String className) throws IOException { byte[] data; int length; try { //從網絡中採用URL類的方法 載入指定URL地址的類的數據。 URL url = new URL(className.endsWith(".class") ? className : className + ".class"); URLConnection connection = url.openConnection(); InputStream inputStream = connection.getInputStream(); length = connection.getContentLength(); data = new byte[length]; inputStream.read(data); inputStream.close(); return data; } catch(Exception e) { throw new IOException(className); } } }
---- 2、 由 於Java 是 強 類 型 檢 查 語 言, 通 過 網 絡 載 入 後 的 類 被 實 例 化 後 只 是 一 個Object 類 型 的 對 象, 虛 擬 機 並 不 知 道 它 包 含 那 些 方 法, 應 從 哪 個 方 法 開 始 執 行。 因 此, 可 以 被 動 態 載 入 的 類 必 須 繼 承 某 一 個 抽 象 類 或 實 現 某 一 個 接 口, 因 爲 父 類 只 能 有 一 個, 所 以 通 常 用 實 現 特 定 接 口 的 辦 法。 下 面 的 代 碼 定 義 了 一 個 接 口 類share 和 它 的 方 法start()。
public interface share { public void start(String[] option); }
---- 3、TestClassLoader 類 通 過 使 用MyClassLoader 類 的loadClass() 方 法, 將 指 定URL 地 址 的 類 載 入 並 在 本 地 系 統 執 行 它, 實 現 了 類 的 動 態 載 入。 注 意 在 執 行 被 載 入 類 的 方 法 前 一 定 要 將 它 進 行 強 制 數 據 類 型 轉 換。
public class TestClassLoader { public static void main(String[] args){ MyClassLoader ll = new MyClassLoader(); Class cc; Object oo; String ss = "http://kyzser.ydxx/classLoader/Tested.class"; if (args.length != 0) ss = args[0]; try { System.out.println("Loading class "+ss+"..."); //使用重寫的方法loadClass()載入類數據。 cc = ll.loadClass(ss); System.out.println("Creat instance..."); //創建Object類型的類實例。 oo = cc.newInstance(); System.out.println("Call start() method..."); //強制類型轉換並執行被載入類中的方法。 ((share) oo).start(args); }catch (Exception e) { System.out.println("Caught exception : "+e); } } }
---- 4、Tested 類 很 簡 單, 可 以 將 它 放 在 任 何WEB 服 務 器 上, 但 應 注 意 能 動 態 載 入 且 被 執 行 的 類, 一 定 要 實 現 預 先 定 義 的 接 口 中 的 方 法。 下 面 的 例 子 實 現 了 接 口share 的start 方 法。
public class Tested implements share{ public void start(String[] option){ //填寫程序代碼。 } }
動 態 類 載 入 機 制 的 幾 點 應 用
---- 1、 開 發 分 布 式 應 用。 這 對 開 發 遠 程 的 客 戶 端 應 用 程 序 最 有 用, 客 戶 端 僅 需 要 安 裝 一 些 基 本 的 系 統 和 一 個 能 實 現 動 態 類 載 入 機 制 的 類, 需 要 本 地 系 統 不 存 在 的 功 能 時, 僅 需 要 從 網 絡 動 態 載 入 並 執 行 相 應 類 即 可 獲 得 特 定 功 能。 因 爲 客 戶 端 所 使 用 的 總 是 軟 件 的 最 新 版 本, 所 以 不 再 存 在 軟 件 的 升 級 和 維 護 問 題, 即 實 現 了 所 謂 的“ 零 管 理” 模 式。
---- 2、 對.class 文 件 加 密。 由 於Java 的 字 節 碼(bytecode) 容 易 被 反 編 譯, 大 部 分 開 發Java 應 用 程 序 的 公 司 均 擔 心 自 己 的 成 果 被 別 人 不 勞 而 獲。 其 實 可 以 將 類 文 件 進 行 適 當 的 加 密 處 理, 執 行 時 使 用 自 己 的 類 載 入 器 進 行 相 應 的 解 密, 就 可 以 解 決 這 個 問 題。
---- 3、 使 第 三 方 開 發 者 易 於 擴 展 你 的 應 用。 從 前 面 可 知, 所 有 可 以 被 你 的 類 載 入 器 動 態 載 入 並 被 執 行 的 類, 必 須 繼 承 你 定 義 的 類 或 實 現 你 定 義 的 接 口, 這 樣, 你 可 以 制 訂 一 些 規 則, 使 其 他 開 發 者 不 必 了 解 你 的 應 用 程 序 也 可 以 擴 充 功 能。
---- 當 然, 有 利 必 有 弊, 在 網 絡 中 使 用 動 態 類 載 入 的 主 要 缺 陷 在 於 安 全 性, 很 難 保 證 不 載 入 不 懷 好 意 的 代 碼, 這 個 問 題 要 靠Java 的 安 全 管 理 器 和 適 當 的 加 密 算 法 來 解 決, 已 超 出 本 文 的 討 論 範 圍。