Java 动 态 代 理 机 制 分 析 及 扩 展, 第 1 部 分 引 言 Java 动 态 代 理 机 制 的 出 现, 使 得 Java 开 发 人 员 不 用 手 工 编 写 代 理 类, 只 要 简 单 地 指 定 一 组 接 口 及 委 托 类 对 象, 便 能 动 态 地 获 得 代 理 类 代 理 类 会 负 责 将 所 有 的 方 法 调 用 分 派 到 委 托 对 象 上 反 射 执 行, 在 分 派 执 行 的 过 程 中, 开 发 人 员 还 可 以 按 需 调 整 委 托 类 对 象 及 其 功 能, 这 是 一 套 非 常 灵 活 有 弹 性 的 代 理 框 架 通 过 阅 读 本 文, 读 者 将 会 对 Java 动 态 代 理 机 制 有 更 加 深 入 的 理 解 本 文 首 先 从 Java 动 态 代 理 的 运 行 机 制 和 特 点 出 发, 对 其 代 码 进 行 了 分 析, 推 演 了 动 态 生 成 类 的 内 部 实 现 回 页 首 代 理 : 设 计 模 式 代 理 是 一 种 常 用 的 设 计 模 式, 其 目 的 就 是 为 其 他 对 象 提 供 一 个 代 理 以 控 制 对 某 个 对 象 的 访 问 代 理 类 负 责 为 委 托 类 预 处 理 消 息, 过 滤 消 息 并 转 发 消 息, 以 及 进 行 消 息 被 委 托 类 执 行 后 的 后 续 处 理 图 1. 代 理 模 式 为 了 保 持 行 为 的 一 致 性, 代 理 类 和 委 托 类 通 常 会 实 现 相 同 的 接 口, 所 以 在 访 问 者 看 来 两 者 没 有 丝 毫 的 区 别 通 过 代 理 类 这 中 间 一 层, 能 有 效 控 制 对 委 托 类 对 象 的 直 接 访 问, 也 可 以 很 好 地 隐 藏 和 保 护 委 托 类 对 象, 同 时 也 为 实 施 不 同 控 制 策 略 预 留 了 空 间, 从 而 在 设 计 上 获 得 了 更 大 的 灵 活 性 Java 动 态 代 理 机 制 以 巧 妙 的 方 式 近 乎 完 美 地 实 践 了 代 理 模 式 的 设 计 理 念
回 页 首 相 关 的 类 和 接 口 要 了 解 Java 动 态 代 理 的 机 制, 首 先 需 要 了 解 以 下 相 关 的 类 或 接 口 : java.lang.reflect.proxy: 这 是 Java 动 态 代 理 机 制 的 主 类, 它 提 供 了 一 组 静 态 方 法 来 为 一 组 接 口 动 态 地 生 成 代 理 类 及 其 对 象 清 单 1. Proxy 的 静 态 方 法 // 方 法 1: 该 方 法 用 于 获 取 指 定 代 理 对 象 所 关 联 的 调 用 处 理 器 static InvocationHandler getinvocationhandler(object proxy) // 方 法 2: 该 方 法 用 于 获 取 关 联 于 指 定 类 装 载 器 和 一 组 接 口 的 动 态 代 理 类 的 类 对 象 static Class getproxyclass(classloader loader, Class[] interfaces) // 方 法 3: 该 方 法 用 于 判 断 指 定 类 对 象 是 否 是 一 个 动 态 代 理 类 static boolean isproxyclass(class cl) // 方 法 4: 该 方 法 用 于 为 指 定 类 装 载 器 一 组 接 口 及 调 用 处 理 器 生 成 动 态 代 理 类 实 例 static Object newproxyinstance(classloader loader, Class[] interfaces, InvocationHandler h) java.lang.reflect.invocationhandler: 这 是 调 用 处 理 器 接 口, 它 自 定 义 了 一 个 invoke 方 法, 用 于 集 中 处 理 在 动 态 代 理 类 对 象 上 的 方 法 调 用, 通 常 在 该 方 法 中 实 现 对 委 托 类 的 代 理 访 问 清 单 2. InvocationHandler 的 核 心 方 法 // 该 方 法 负 责 集 中 处 理 动 态 代 理 类 上 的 所 有 方 法 调 用 第 一 个 参 数 既 是 代 理 类 实 例, 第 二 个 参 数 是 被 调 用 的 方 法 对 象 // 第 三 个 方 法 是 调 用 参 数 调 用 处 理 器 根 据 这 三 个 参 数 进 行 预 处 理 或 分 派 到 委 托 类 实 例 上 发 射 执 行 Object invoke(object proxy, Method method, Object[] args)
每 次 生 成 动 态 代 理 类 对 象 时 都 需 要 指 定 一 个 实 现 了 该 接 口 的 调 用 处 理 器 对 象 ( 参 见 Proxy 静 态 方 法 4 的 第 三 个 参 数 ) java.lang.classloader: 这 是 类 装 载 器 类, 负 责 将 类 的 字 节 码 装 载 到 Java 虚 拟 机 (JVM) 中 并 为 其 定 义 类 对 象, 然 后 该 类 才 能 被 使 用 Proxy 静 态 方 法 生 成 动 态 代 理 类 同 样 需 要 通 过 类 装 载 器 来 进 行 装 载 才 能 使 用, 它 与 普 通 类 的 唯 一 区 别 就 是 其 字 节 码 是 由 JVM 在 运 行 时 动 态 生 成 的 而 非 预 存 在 于 任 何 一 个.class 文 件 中 每 次 生 成 动 态 代 理 类 对 象 时 都 需 要 指 定 一 个 类 装 载 器 对 象 ( 参 见 Proxy 静 态 方 法 4 的 第 一 个 参 数 ) 回 页 首 代 理 机 制 及 其 特 点 首 先 让 我 们 来 了 解 一 下 如 何 使 用 Java 动 态 代 理 具 体 有 如 下 四 步 骤 : 1. 通 过 实 现 InvocationHandler 接 口 创 建 自 己 的 调 用 处 理 器 ; 2. 通 过 为 Proxy 类 指 定 ClassLoader 对 象 和 一 组 interface 来 创 建 动 态 代 理 类 ; 3. 通 过 反 射 机 制 获 得 动 态 代 理 类 的 构 造 函 数, 其 唯 一 参 数 类 型 是 调 用 处 理 器 接 口 类 型 ; 4. 通 过 构 造 函 数 创 建 动 态 代 理 类 实 例, 构 造 时 调 用 处 理 器 对 象 作 为 参 数 被 传 入 清 单 3. 动 态 代 理 对 象 创 建 过 程 // InvocationHandlerImpl 实 现 了 InvocationHandler 接 口, 并 能 实 现 方 法 调 用 从 代 理 类 到 委 托 类 的 分 派 转 发 // 其 内 部 通 常 包 含 指 向 委 托 类 实 例 的 引 用, 用 于 真 正 执 行 分 派 转 发 过 来 的 方 法 调 用 InvocationHandler handler = new InvocationHandlerImpl(..); // 通 过 Proxy 为 包 括 Interface 接 口 在 内 的 一 组 接 口 动 态 创 建 代 理 类 的 类 对 象 Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class,... ); // 通 过 反 射 从 生 成 的 类 对 象 获 得 构 造 函 数 对 象 Constructor constructor = clazz.getconstructor(new Class[] { InvocationHandler.class );
// 通 过 构 造 函 数 对 象 创 建 动 态 代 理 类 实 例 Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler ); 实 际 使 用 过 程 更 加 简 单, 因 为 Proxy 的 静 态 方 法 newproxyinstance 已 经 为 我 们 封 装 了 步 骤 2 到 步 骤 4 的 过 程, 所 以 简 化 后 的 过 程 如 下 清 单 4. 简 化 的 动 态 代 理 对 象 创 建 过 程 // InvocationHandlerImpl 实 现 了 InvocationHandler 接 口, 并 能 实 现 方 法 调 用 从 代 理 类 到 委 托 类 的 分 派 转 发 InvocationHandler handler = new InvocationHandlerImpl(..); // 通 过 Proxy 直 接 创 建 动 态 代 理 类 实 例 Interface proxy = (Interface)Proxy.newProxyInstance( classloader, new Class[] { Interface.class, handler ); 接 下 来 让 我 们 来 了 解 一 下 Java 动 态 代 理 机 制 的 一 些 特 点 首 先 是 动 态 生 成 的 代 理 类 本 身 的 一 些 特 点 1) 包 : 如 果 所 代 理 的 接 口 都 是 public 的, 那 么 它 将 被 定 义 在 顶 层 包 ( 即 包 路 径 为 空 ), 如 果 所 代 理 的 接 口 中 有 非 public 的 接 口 ( 因 为 接 口 不 能 被 定 义 为 protect 或 private, 所 以 除 public 之 外 就 是 默 认 的 package 访 问 级 别 ), 那 么 它 将 被 定 义 在 该 接 口 所 在 包 ( 假 设 代 理 了 com.ibm.developerworks 包 中 的 某 非 public 接 口 A, 那 么 新 生 成 的 代 理 类 所 在 的 包 就 是 com.ibm.developerworks), 这 样 设 计 的 目 的 是 为 了 最 大 程 度 的 保 证 动 态 代 理 类 不 会 因 为 包 管 理 的 问 题 而 无 法 被 成 功 定 义 并 访 问 ;2) 类 修 饰 符 : 该 代 理 类 具 有 final 和 public 修 饰 符, 意 味 着 它 可 以 被 所 有 的 类 访 问, 但 是 不 能 被 再 度 继 承 ;3) 类 名 : 格 式 是 $ProxyN, 其 中 N 是 一 个 逐 一 递 增 的 阿 拉 伯 数 字, 代 表 Proxy 类 第 N 次 生 成 的 动 态 代 理 类, 值 得 注 意 的 一 点 是, 并 不 是 每 次 调 用 Proxy 的 静 态 方 法 创 建 动 态 代 理 类 都 会 使 得 N 值 增 加, 原 因 是 如 果 对 同 一 组 接 口 ( 包 括 接 口 排 列 的 顺 序 相 同 ) 试 图 重 复 创 建 动 态 代 理 类, 它 会 很 聪 明 地 返 回 先 前 已 经 创 建 好 的 代 理 类 的 类 对 象, 而 不 会 再 尝 试 去 创 建 一 个 全 新 的 代 理 类, 这 样 可 以 节 省 不 必 要 的 代 码 重 复 生 成, 提 高 了 代 理 类 的 创 建 效 率 4) 类 继 承 关 系 : 该 类 的 继 承 关 系 如 图 :
图 2. 动 态 代 理 类 的 继 承 图 由 图 可 见,Proxy 类 是 它 的 父 类, 这 个 规 则 适 用 于 所 有 由 Proxy 创 建 的 动 态 代 理 类 而 且 该 类 还 实 现 了 其 所 代 理 的 一 组 接 口, 这 就 是 为 什 么 它 能 够 被 安 全 地 类 型 转 换 到 其 所 代 理 的 某 接 口 的 根 本 原 因 接 下 来 让 我 们 了 解 一 下 代 理 类 实 例 的 一 些 特 点 每 个 实 例 都 会 关 联 一 个 调 用 处 理 器 对 象, 可 以 通 过 Proxy 提 供 的 静 态 方 法 getinvocationhandler 去 获 得 代 理 类 实 例 的 调 用 处 理 器 对 象 在 代 理 类 实 例 上 调 用 其 代 理 的 接 口 中 所 声 明 的 方 法 时, 这 些 方 法 最 终 都 会 由 调 用 处 理 器 的 invoke 方 法 执 行, 此 外, 值 得 注 意 的 是, 代 理 类 的 根 类 java.lang.object 中 有 三 个 方 法 也 同 样 会 被 分 派 到 调 用 处 理 器 的 invoke 方 法 执 行, 它 们 是 hashcode,equals 和 tostring, 可 能 的 原 因 有 : 一 是 因 为 这 些 方 法 为 public 且 非 final 类 型, 能 够 被 代 理 类 覆 盖 ; 二 是 因 为 这 些 方 法 往 往 呈 现 出 一 个 类 的 某 种 特 征 属 性, 具 有 一 定 的 区 分 度, 所 以 为 了 保 证 代 理 类 与 委 托 类 对 外 的 一 致 性, 这 三 个 方 法 也 应 该 被 分 派 到 委 托 类 执 行 当 代 理 的 一 组 接 口 有 重 复 声 明 的 方 法 且 该 方 法 被 调 用 时, 代 理 类 总 是 从 排 在 最 前 面 的 接 口 中 获 取 方 法 对 象 并 分 派 给 调 用 处 理 器, 而 无 论 代 理 类 实 例 是 否 正 在 以 该 接 口 ( 或 继 承 于 该 接 口 的 某 子 接 口 ) 的 形 式 被 外 部 引 用, 因 为 在 代 理 类 内 部 无 法 区 分 其 当 前 的 被 引 用 类 型 接 着 来 了 解 一 下 被 代 理 的 一 组 接 口 有 哪 些 特 点 首 先, 要 注 意 不 能 有 重 复 的 接 口, 以 避 免 动 态 代 理 类 代 码 生 成 时 的 编 译 错 误 其 次, 这 些 接 口 对 于 类 装 载 器 必 须 可 见, 否 则 类 装 载 器 将 无 法 链 接 它 们, 将 会 导 致 类 定 义 失 败 再 次, 需 被 代 理 的 所 有 非 public 的 接 口 必 须 在 同 一 个 包 中, 否 则 代 理 类 生 成 也 会 失 败 最 后, 接 口 的 数 目 不 能 超 过 65535, 这 是 JVM 设 定 的 限 制 最 后 再 来 了 解 一 下 异 常 处 理 方 面 的 特 点 从 调 用 处 理 器 接 口 声 明 的 方 法 中 可 以 看 到 理 论 上 它 能 够 抛 出 任 何 类 型 的 异 常, 因 为 所 有 的 异 常 都 继 承 于 Throwable 接 口, 但 事 实 是 否 如 此 呢? 答 案 是 否 定 的, 原 因 是 我 们 必 须 遵 守 一 个 继 承 原 则 : 即 子 类 覆 盖 父 类 或 实 现 父 接 口 的 方 法 时, 抛 出 的 异 常 必 须 在 原 方 法 支 持 的 异 常 列 表 之 内 所 以 虽 然 调 用 处 理 器 理 论 上 讲 能 够, 但 实 际 上 往 往 受 限 制, 除 非 父 接 口 中 的 方 法 支 持 抛 Throwable 异 常 那 么 如 果 在 invoke 方 法 中 的 确 产 生 了 接 口 方 法 声 明 中 不 支 持 的 异 常, 那 将 如 何 呢? 放 心,Java 动 态 代 理 类 已 经 为 我 们 设 计 好 了 解 决 方 法 : 它 将 会 抛 出 UndeclaredThrowableException 异 常 这 个 异 常 是 一 个 RuntimeException 类 型, 所 以 不 会 引 起 编 译 错 误 通 过 该 异 常 的 getcause 方 法, 还 可 以 获 得 原 来 那 个 不 受 支 持 的 异 常 对 象, 以 便 于 错 误 诊 断
回 页 首 代 码 是 最 好 的 老 师 机 制 和 特 点 都 介 绍 过 了, 接 下 来 让 我 们 通 过 源 代 码 来 了 解 一 下 Proxy 到 底 是 如 何 实 现 的 首 先 记 住 Proxy 的 几 个 重 要 的 静 态 变 量 : 清 单 5. Proxy 的 重 要 静 态 变 量 // 映 射 表 : 用 于 维 护 类 装 载 器 对 象 到 其 对 应 的 代 理 类 缓 存 private static Map loadertocache = new WeakHashMap(); // 标 记 : 用 于 标 记 一 个 动 态 代 理 类 正 在 被 创 建 中 private static Object pendinggenerationmarker = new Object(); // 同 步 表 : 记 录 已 经 被 创 建 的 动 态 代 理 类 类 型, 主 要 被 方 法 isproxyclass 进 行 相 关 的 判 断 private static Map proxyclasses = Collections.synchronizedMap(new WeakHashMap()); // 关 联 的 调 用 处 理 器 引 用 protected InvocationHandler h; 然 后, 来 看 一 下 Proxy 的 构 造 方 法 : 清 单 6. Proxy 构 造 方 法 // 由 于 Proxy 内 部 从 不 直 接 调 用 构 造 函 数, 所 以 private 类 型 意 味 着 禁 止 任 何 调 用 private Proxy() { // 由 于 Proxy 内 部 从 不 直 接 调 用 构 造 函 数, 所 以 protected 意 味 着 只 有 子 类 可 以 调 用 protected Proxy(InvocationHandler h) {this.h = h; 接 着, 可 以 快 速 浏 览 一 下 newproxyinstance 方 法, 因 为 其 相 当 简 单 :
清 单 7. Proxy 静 态 方 法 newproxyinstance public static Object newproxyinstance(classloader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { // 检 查 h 不 为 空, 否 则 抛 异 常 if (h == null) { throw new NullPointerException(); // 获 得 与 制 定 类 装 载 器 和 一 组 接 口 相 关 的 代 理 类 类 型 对 象 Class cl = getproxyclass(loader, interfaces); // 通 过 反 射 获 取 构 造 函 数 对 象 并 生 成 代 理 类 实 例 try { Constructor cons = cl.getconstructor(constructorparams); return (Object) cons.newinstance(new Object[] { h ); catch (NoSuchMethodException e) { throw new InternalError(e.toString()); catch (IllegalAccessException e) { throw new InternalError(e.toString()); catch (InstantiationException e) { throw new InternalError(e.toString()); catch (InvocationTargetException e) { throw new InternalError(e.toString()); 由 此 可 见, 动 态 代 理 真 正 的 关 键 是 在 getproxyclass 方 法, 该 方 法 负 责 为 一 组 接 口 动 态 地 生 成 代 理 类 类 型 对 象 在 该 方 法 内 部, 您 将 能 看 到 Proxy 内 的 各 路 英 雄 ( 静 态 变 量 ) 悉 数 登 场 有 点 迫 不 及 待 了 么? 那 就 让 我 们 一 起 走 进 Proxy 最 最 神 秘 的 殿 堂 去 欣 赏 一 番 吧 该 方 法 总 共 可 以 分 为 四 个 步 骤 : 1. 对 这 组 接 口 进 行 一 定 程 度 的 安 全 检 查, 包 括 检 查 接 口 类 对 象 是 否 对 类 装 载 器 可 见 并 且 与 类 装 载 器 所 能 识 别 的 接 口 类 对 象 是 完 全 相 同 的, 还 会 检 查 确 保 是 interface 类 型 而 不 是 class 类 型 这 个 步 骤 通 过 一 个 循 环 来 完 成, 检 查 通 过 后 将 会 得 到 一 个 包 含 所 有 接 口 名 称 的 字 符 串 数 组, 记 为 String[] interfacenames 总 体 上 这 部 分 实 现 比 较 直 观, 所 以 略 去 大 部 分 代 码, 仅 保 留 留 如 何 判 断 某 类 或 接 口 是 否 对 特 定 类 装 载 器 可 见 的 相 关 代 码
清 单 8. 通 过 Class.forName 方 法 判 接 口 的 可 见 性 try { // 指 定 接 口 名 字 类 装 载 器 对 象, 同 时 制 定 initializeboolean 为 false 表 示 无 须 初 始 化 类 // 如 果 方 法 返 回 正 常 这 表 示 可 见, 否 则 会 抛 出 ClassNotFoundException 异 常 表 示 不 可 见 interfaceclass = Class.forName(interfaceName, false, loader); catch (ClassNotFoundException e) { 2. 从 loadertocache 映 射 表 中 获 取 以 类 装 载 器 对 象 为 关 键 字 所 对 应 的 缓 存 表, 如 果 不 存 在 就 创 建 一 个 新 的 缓 存 表 并 更 新 到 loadertocache 缓 存 表 是 一 个 HashMap 实 例, 正 常 情 况 下 它 将 存 放 键 值 对 ( 接 口 名 字 列 表, 动 态 生 成 的 代 理 类 的 类 对 象 引 用 ) 当 代 理 类 正 在 被 创 建 时 它 会 临 时 保 存 ( 接 口 名 字 列 表,pendingGenerationMarker) 标 记 pendinggenerationmarke 的 作 用 是 通 知 后 续 的 同 类 请 求 ( 接 口 数 组 相 同 且 组 内 接 口 排 列 顺 序 也 相 同 ) 代 理 类 正 在 被 创 建, 请 保 持 等 待 直 至 创 建 完 成 清 单 9. 缓 存 表 的 使 用 do { // 以 接 口 名 字 列 表 作 为 关 键 字 获 得 对 应 cache 值 Object value = cache.get(key); if (value instanceof Reference) { proxyclass = (Class) ((Reference) value).get(); if (proxyclass!= null) { // 如 果 已 经 创 建, 直 接 返 回 return proxyclass; else if (value == pendinggenerationmarker) { // 代 理 类 正 在 被 创 建, 保 持 等 待 try { cache.wait(); catch (InterruptedException e) { // 等 待 被 唤 醒, 继 续 循 环 并 通 过 二 次 检 查 以 确 保 创 建 完 成, 否 则 重 新 等 待 continue;
else { // 标 记 代 理 类 正 在 被 创 建 cache.put(key, pendinggenerationmarker); // break 跳 出 循 环 已 进 入 创 建 过 程 break; while (true); 3. 动 态 创 建 代 理 类 的 类 对 象 首 先 是 确 定 代 理 类 所 在 的 包, 其 原 则 如 前 所 述, 如 果 都 为 public 接 口, 则 包 名 为 空 字 符 串 表 示 顶 层 包 ; 如 果 所 有 非 public 接 口 都 在 同 一 个 包, 则 包 名 与 这 些 接 口 的 包 名 相 同 ; 如 果 有 多 个 非 public 接 口 且 不 同 包, 则 抛 异 常 终 止 代 理 类 的 生 成 确 定 了 包 后, 就 开 始 生 成 代 理 类 的 类 名, 同 样 如 前 所 述 按 格 式 $ProxyN 生 成 类 名 也 确 定 了, 接 下 来 就 是 见 证 奇 迹 的 发 生 动 态 生 成 代 理 类 : 清 单 10. 动 态 生 成 代 理 类 // 动 态 地 生 成 代 理 类 的 字 节 码 数 组 byte[] proxyclassfile = ProxyGenerator.generateProxyClass( proxyname, interfaces); try { // 动 态 地 定 义 新 生 成 的 代 理 类 proxyclass = defineclass0(loader, proxyname, proxyclassfile, 0, proxyclassfile.length); catch (ClassFormatError e) { throw new IllegalArgumentException(e.toString()); // 把 生 成 的 代 理 类 的 类 对 象 记 录 进 proxyclasses 表 proxyclasses.put(proxyclass, null); 由 此 可 见, 所 有 的 代 码 生 成 的 工 作 都 由 神 秘 的 ProxyGenerator 所 完 成 了, 当 你 尝 试 去 探 索 这 个 类 时, 你 所 能 获 得 的 信 息 仅 仅 是 它 位 于 并 未 公 开 的 sun.misc 包, 有 若 干 常 量 变 量 和 方 法 以 完 成 这 个 神 奇 的 代 码 生 成 的 过 程, 但 是 sun 并 没 有 提 供 源 代 码 以 供 研 读 至 于 动 态 类 的 定 义, 则 由 Proxy 的 native 静 态 方 法 defineclass0 执 行 4. 代 码 生 成 过 程 进 入 结 尾 部 分, 根 据 结 果 更 新 缓 存 表, 如 果 成 功 则 将 代 理 类 的 类 对 象 引 用 更 新 进 缓 存 表, 否 则 清 楚 缓 存 表 中 对 应 关 键 值, 最 后 唤 醒 所 有 可 能 的 正 在 等 待 的 线 程
走 完 了 以 上 四 个 步 骤 后, 至 此, 所 有 的 代 理 类 生 成 细 节 都 已 介 绍 完 毕, 剩 下 的 静 态 方 法 如 getinvocationhandler 和 isproxyclass 就 显 得 如 此 的 直 观, 只 需 通 过 查 询 相 关 变 量 就 可 以 完 成, 所 以 对 其 的 代 码 分 析 就 省 略 了 回 页 首 代 理 类 实 现 推 演 分 析 了 Proxy 类 的 源 代 码, 相 信 在 读 者 的 脑 海 中 会 对 Java 动 态 代 理 机 制 形 成 一 个 更 加 清 晰 的 理 解, 但 是, 当 探 索 之 旅 在 sun.misc.proxygenerator 类 处 嘎 然 而 止, 所 有 的 神 秘 都 汇 聚 于 此 时, 相 信 不 少 读 者 也 会 对 这 个 ProxyGenerator 类 产 生 有 类 似 的 疑 惑 : 它 到 底 做 了 什 么 呢? 它 是 如 何 生 成 动 态 代 理 类 的 代 码 的 呢? 诚 然, 这 里 也 无 法 给 出 确 切 的 答 案 还 是 让 我 们 带 着 这 些 疑 惑, 一 起 开 始 探 索 之 旅 吧 事 物 往 往 不 像 其 看 起 来 的 复 杂, 需 要 的 是 我 们 能 够 化 繁 为 简, 这 样 也 许 就 能 有 更 多 拨 云 见 日 的 机 会 抛 开 所 有 想 象 中 的 未 知 而 复 杂 的 神 秘 因 素, 如 果 让 我 们 用 最 简 单 的 方 法 去 实 现 一 个 代 理 类, 唯 一 的 要 求 是 同 样 结 合 调 用 处 理 器 实 施 方 法 的 分 派 转 发, 您 的 第 一 反 应 将 是 什 么 呢? 听 起 来 似 乎 并 不 是 很 复 杂 的 确, 掐 指 算 算 所 涉 及 的 工 作 无 非 包 括 几 个 反 射 调 用, 以 及 对 原 始 类 型 数 据 的 装 箱 或 拆 箱 过 程, 其 他 的 似 乎 都 已 经 水 到 渠 成 非 常 地 好, 让 我 们 整 理 一 下 思 绪, 一 起 来 完 成 一 次 完 整 的 推 演 过 程 吧 清 单 11. 代 理 类 中 方 法 调 用 的 分 派 转 发 推 演 实 现 // 假 设 需 代 理 接 口 Simulator public interface Simulator { short simulate(int arg1, long arg2, String arg3) throws ExceptionA, ExceptionB; // 假 设 代 理 类 为 SimulatorProxy, 其 类 声 明 将 如 下 final public class SimulatorProxy implements Simulator { // 调 用 处 理 器 对 象 的 引 用 protected InvocationHandler handler; // 以 调 用 处 理 器 为 参 数 的 构 造 函 数 public SimulatorProxy(InvocationHandler handler){ this.handler = handler;
// 实 现 接 口 方 法 simulate public short simulate(int arg1, long arg2, String arg3) throws ExceptionA, ExceptionB { // 第 一 步 是 获 取 simulate 方 法 的 Method 对 象 java.lang.reflect.method method = null; try{ method = Simulator.class.getMethod( "simulate", new Class[] {int.class, long.class, String.class ); catch(exception e) { // 异 常 处 理 1( 略 ) // 第 二 步 是 调 用 handler 的 invoke 方 法 分 派 转 发 方 法 调 用 Object r = null; try { r = handler.invoke(this, method, // 对 于 原 始 类 型 参 数 需 要 进 行 装 箱 操 作 new Object[] {new Integer(arg1), new Long(arg2), arg3); catch(throwable e) { // 异 常 处 理 2( 略 ) // 第 三 步 是 返 回 结 果 ( 返 回 类 型 是 原 始 类 型 则 需 要 进 行 拆 箱 操 作 ) return ((Short)r).shortValue(); 模 拟 推 演 为 了 突 出 通 用 逻 辑 所 以 更 多 地 关 注 正 常 流 程, 而 淡 化 了 错 误 处 理, 但 在 实 际 中 错 误 处 理 同 样 非 常 重 要 从 以 上 的 推 演 中 我 们 可 以 得 出 一 个 非 常 通 用 的 结 构 化 流 程 : 第 一 步 从 代 理 接 口 获 取 被 调 用 的 方 法 对 象, 第 二 步 分 派 方 法 到 调 用 处 理 器 执 行, 第 三 步 返 回 结 果 在 这 之 中, 所 有 的 信 息 都 是 可 以 已 知 的, 比 如 接 口 名 方 法 名 参 数 类 型 返 回 类 型 以 及 所 需 的 装 箱 和 拆 箱 操 作, 那 么 既 然 我 们 手 工 编 写 是 如 此, 那 又 有 什 么 理 由 不 相 信 ProxyGenerator 不 会 做 类 似 的 实 现 呢? 至 少 这 是 一 种 比 较 可 能 的 实 现 接 下 来 让 我 们 把 注 意 力 重 新 回 到 先 前 被 淡 化 的 错 误 处 理 上 来 在 异 常 处 理 1 处, 由 于 我 们 有 理 由 确 保 所 有 的 信 息 如 接 口 名 方 法 名 和 参 数 类 型 都 准 确 无 误, 所 以 这 部 分 异 常 发 生 的 概 率 基 本 为 零, 所 以 基 本 可 以 忽 略 而 异 常 处 理 2 处, 我 们 需 要 思 考 得 更 多 一 些 回 想 一 下, 接 口 方 法 可 能 声 明 支 持 一 个 异 常 列 表, 而 调 用 处 理 器 invoke 方 法 又 可 能 抛 出 与 接 口 方 法 不 支 持 的 异 常, 再 回 想 一 下 先 前 提 及 的 Java 动 态 代 理 的 关 于 异 常 处 理 的 特 点, 对 于 不 支 持 的 异 常, 必 须 抛
UndeclaredThrowableException 运 行 时 异 常 所 以 通 过 再 次 推 演, 我 们 可 以 得 出 一 个 更 加 清 晰 的 异 常 处 理 2 的 情 况 : 清 单 12. 细 化 的 异 常 处 理 2 Object r = null; try { r = handler.invoke(this, method, new Object[] {new Integer(arg1), new Long(arg2), arg3); catch( ExceptionA e) { // 接 口 方 法 支 持 ExceptionA, 可 以 抛 出 throw e; catch( ExceptionB e ) { // 接 口 方 法 支 持 ExceptionB, 可 以 抛 出 throw e; catch(throwable e) { // 其 他 不 支 持 的 异 常, 一 律 抛 UndeclaredThrowableException throw new UndeclaredThrowableException(e); 这 样 我 们 就 完 成 了 对 动 态 代 理 类 的 推 演 实 现 推 演 实 现 遵 循 了 一 个 相 对 固 定 的 模 式, 可 以 适 用 于 任 意 定 义 的 任 何 接 口, 而 且 代 码 生 成 所 需 的 信 息 都 是 可 知 的, 那 么 有 理 由 相 信 即 使 是 机 器 自 动 编 写 的 代 码 也 有 可 能 延 续 这 样 的 风 格, 至 少 可 以 保 证 这 是 可 行 的 回 页 首 美 中 不 足 诚 然,Proxy 已 经 设 计 得 非 常 优 美, 但 是 还 是 有 一 点 点 小 小 的 遗 憾 之 处, 那 就 是 它 始 终 无 法 摆 脱 仅 支 持 interface 代 理 的 桎 梏, 因 为 它 的 设 计 注 定 了 这 个 遗 憾 回 想 一 下 那 些 动 态 生 成 的 代 理 类 的 继 承 关 系 图, 它 们 已 经 注 定 有 一 个 共 同 的 父 类 叫 Proxy Java 的 继 承 机 制 注 定 了 这 些 动 态 代 理 类 们 无 法 实 现 对 class 的 动 态 代 理, 原 因 是 多 继 承 在 Java 中 本 质 上 就 行 不 通
有 很 多 条 理 由, 人 们 可 以 否 定 对 class 代 理 的 必 要 性, 但 是 同 样 有 一 些 理 由, 相 信 支 持 class 动 态 代 理 会 更 美 好 接 口 和 类 的 划 分, 本 就 不 是 很 明 显, 只 是 到 了 Java 中 才 变 得 如 此 的 细 化 如 果 只 从 方 法 的 声 明 及 是 否 被 定 义 来 考 量, 有 一 种 两 者 的 混 合 体, 它 的 名 字 叫 抽 象 类 实 现 对 抽 象 类 的 动 态 代 理, 相 信 也 有 其 内 在 的 价 值 此 外, 还 有 一 些 历 史 遗 留 的 类, 它 们 将 因 为 没 有 实 现 任 何 接 口 而 从 此 与 动 态 代 理 永 世 无 缘 如 此 种 种, 不 得 不 说 是 一 个 小 小 的 遗 憾 但 是, 不 完 美 并 不 等 于 不 伟 大, 伟 大 是 一 种 本 质,Java 动 态 代 理 就 是 佐 例 参 考 资 料 Dynamic Proxy Classes : 查 看 Java 动 态 代 理 的 相 关 文 档 Introduction to Java Exception Handling : 介 绍 了 如 何 处 理 Java 异 常 Java 理 论 与 实 践 : 用 动 态 代 理 进 行 修 饰 (developerworks,2005 年 9 月 ): 动 态 代 理 工 具 是 java.lang.reflect 包 的 一 部 分, 在 JDK 1.3 版 本 中 添 加 到 JDK, 它 允 许 程 序 创 建 代 理 对 象 本 文 中, 作 者 介 绍 了 几 个 用 于 动 态 代 理 的 应 用 程 序 利 用 动 态 代 理 的 Java 验 证 (developerworks,2004 年 9 月 ): 本 文 向 您 展 示 动 态 代 理 如 何 让 核 心 应 用 程 序 代 码 独 立 于 验 证 例 程, 而 只 关 注 业 务 逻 辑 developerworks Java 技 术 专 区 : 数 百 篇 关 于 Java 编 程 各 个 方 面 的 文 章