實 現JAVA 的 動 態 類 載 入 機 制

作 爲 充 分 利 用Java 的 動 態 類 載 入 機 制 的 最 好 例 子, 帶 有Java 擴 展 的Web 瀏 覽 器 根 據 請 求 從 網 絡 或 本 地 文 件 系 統 中 動 態 加 載Java applet( 遵 循 一 定 規 則 的Java 小 應 用 程 序 類), 然 後 在 本 地 系 統 中 執 行 它, 大 大 增 強 了 主 頁 的 功 能。

---- 其 實,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 的 安 全 管 理 器 和 適 當 的 加 密 算 法 來 解 決, 已 超 出 本 文 的 討 論 範 圍。  

發佈了117 篇原創文章 · 獲贊 1 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章