深入浅出 动态代理

核心思想比喻:找个"万能秘书"帮你干活

想象一下,你是老板(调用者 ),有很多事情(方法调用)要处理,比如签合同、开会、回邮件。但你不想亲自处理所有细节,特别是那些重复性的工作(比如记录每个操作日志、检查权限、处理错误等)。

于是你雇了一个万能秘书(动态代理对象) 。这个秘书的特点是:

  1. 她/他必须遵守你的"工作手册"(接口) :秘书只会处理工作手册里写明的事情(接口中定义的方法)。你告诉秘书:"以后所有找我签合同、开会、回邮件的事情,都先找你"。
  2. 她/他有个"万能处理本"(InvocationHandler) :当有人真的来找秘书办事(调用代理对象的方法)时,秘书不会自己决定怎么做。她/他会立刻翻开她的"万能处理本",把这次办事的详细信息(哪个方法?什么参数? )记录在本子上,然后严格按照本子上写的步骤(你写的代码) 去处理这件事。
  3. 秘书是"临时工",按需生成 :这个秘书不是你提前招聘好的固定员工。而是在你确定了工作手册(接口)和处理本(InvocationHandler)的内容后,瞬间"变"出来的一个人。这就是"动态"的含义。

映射到技术概念:

  • 老板(调用者) :你的代码中需要调用某个对象方法的代码。
  • 工作手册(接口) :定义了一组方法的 Java 接口 (Interface)。
  • 原始员工(目标对象/被代理对象) :真正实现接口方法、完成核心业务逻辑的对象实例(RealObject)。老板本来应该直接找这位员工。
  • 万能秘书(动态代理对象) :在运行时动态生成的类 ($Proxy0, $Proxy1, ...) 的实例。它实现了你指定的接口
  • 万能处理本(InvocationHandler) :一个实现了 java.lang.reflect.InvocationHandler 接口的对象。它只有一个关键方法 invoke。代理对象所有方法的调用都会被"转发"到这个 invoke 方法里
  • 办事请求(方法调用) :对代理对象方法的调用。
  • "变"出来(动态生成) :Java 运行时系统(在 Android 中是 ART/Dalvik)利用 java.lang.reflect.Proxy 类在内存中创建代理类的字节码并加载它。

详解动态代理

1. 如何使用 (How to Use)

在 Android 中使用动态代理的步骤非常清晰:

  1. 定义接口 (Interface):

    arduino 复制代码
    // 你的"工作手册"
    public interface IService {
        void doSomething(String task);
        int calculate(int a, int b);
    }
  2. 实现目标对象 (Real Object):

    java 复制代码
    // 真正的"核心员工",干实活的
    public class RealService implements IService {
        @Override
        public void doSomething(String task) {
            Log.d("RealService", "Doing real work: " + task);
        }
    
        @Override
        public int calculate(int a, int b) {
            return a + b; // 简单示例,实际可能是复杂计算
        }
    }
  3. 实现 InvocationHandler:

    typescript 复制代码
    // 秘书的"万能处理本"
    public class MyInvocationHandler implements InvocationHandler {
    
        private Object realObject; // 保存对真实核心员工的引用
    
        public MyInvocationHandler(Object realObject) {
            this.realObject = realObject;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 秘书接到办事请求,翻开处理本执行以下步骤:
            // 1. 前置处理 (Before Advice)
            Log.d("Proxy", "秘书: 有人要调用方法 '" + method.getName() + "'");
            if (args != null) {
                Log.d("Proxy", "秘书: 参数是: " + Arrays.toString(args));
            }
    
            // 2. 核心:把活儿交给真正的员工干 (也可以选择不交!)
            Object result = method.invoke(realObject, args); // 关键!调用真实对象的方法
    
            // 3. 后置处理 (After Advice)
            Log.d("Proxy", "秘书: 方法调用完成,结果是: " + result);
    
            // 4. 把结果报告给老板 (调用者)
            return result;
        }
    }
  4. 创建并使用动态代理对象:

    java 复制代码
    // 创建真正的核心员工
    RealService realService = new RealService();
    
    // 创建秘书的"万能处理本",并告诉秘书她需要代理谁(核心员工)
    MyInvocationHandler handler = new MyInvocationHandler(realService);
    
    // 使用 Proxy 类"变"出万能秘书(动态代理对象)!
    // 参数1:ClassLoader - 通常用目标接口的ClassLoader
    // 参数2:Class[] - 代理对象要实现的接口列表 (工作手册列表)
    // 参数3:InvocationHandler - 秘书的处理本
    IService proxyService = (IService) Proxy.newProxyInstance(
            IService.class.getClassLoader(),
            new Class[]{IService.class}, // 告诉Proxy,秘书要实现的接口
            handler);
    
    // 老板(调用者)开始通过秘书办事
    proxyService.doSomething("重要项目"); // 调用会被handler.invoke()拦截处理
    int sum = proxyService.calculate(5, 3); // 调用也会被handler.invoke()拦截处理
    Log.d("Main", "计算结果: " + sum);

输出示例:

javascript 复制代码
D/Proxy: 秘书: 有人要调用方法 'doSomething'
D/Proxy: 秘书: 参数是: [重要项目]
D/RealService: Doing real work: 重要项目
D/Proxy: 秘书: 方法调用完成,结果是: null (因为doSomething返回void)
D/Proxy: 秘书: 有人要调用方法 'calculate'
D/Proxy: 秘书: 参数是: [5, 3]
D/Proxy: 秘书: 方法调用完成,结果是: 8
D/Main: 计算结果: 8

关键点:

  • 调用 proxyService 的任何方法 (doSomething, calculate),实际上都是在调用 handler.invoke() 方法
  • invoke 方法内部通过 method.invoke(realObject, args) 将调用委托给真正的目标对象 realService
  • 在委托调用前后 ,你可以插入任何自定义逻辑(日志、权限检查、性能监控、缓存、事务管理、错误处理、RPC调用封装等)。这就是 AOP(面向切面编程) 思想的体现。

2. 原理 (Principle)

动态代理的核心魔法发生在 Proxy.newProxyInstance() 方法内部。它的工作原理可以概括为:

  1. 接口检查: 确保传入的 Class[] 都是接口,且对当前 ClassLoader 可见。

  2. 查找或生成代理类:

    • 系统(Proxy类)首先会尝试查找是否已经为指定的接口列表(按顺序)生成过代理类。它内部维护了一个缓存(WeakCache)。

    • 如果缓存中没有: 系统会动态生成 一个新的代理类(如 $Proxy0, $Proxy1)的字节码。这个生成的类:

      • extends java.lang.reflect.Proxy (所以所有动态代理对象都是 Proxy 的子类)。

      • implements 你传入的所有接口(如 IService)。

      • 这个类中会为每一个接口方法生成对应的实现。这些实现都长得差不多:

        java 复制代码
        public final void doSomething(String task) {
            try {
                // 关键!调用父类Proxy中保存的InvocationHandler的invoke方法
                super.h.invoke(this, m3, new Object[]{task}); // m3是Method对象doSomething的标识
            } catch (...) { ... }
        }
        public final int calculate(int a, int b) {
            try {
                return ((Integer) super.h.invoke(this, m1, new Object[]{a, b})).intValue(); // m1是Method对象calculate的标识
            } catch (...) { ... }
        }
      • 这些方法的核心逻辑就是调用父类 Proxy 持有的 InvocationHandler 实例 (h) 的 invoke 方法,并把当前代理对象 (this)、代表该方法的 Method 对象(通过常量池或预加载获得)、以及参数数组传递进去。

    • 加载代理类: 将生成的字节码通过 ClassLoader 加载到 JVM (ART/Dalvik) 中,得到 Class 对象。

  3. 实例化代理对象: 使用反射,通过代理类的构造方法(它需要一个 InvocationHandler 参数)实例化代理对象。构造方法内部会调用 super(new InvocationHandler h),将你提供的 MyInvocationHandler 实例保存到父类 Proxyprotected InvocationHandler h; 字段中。

  4. 返回代理对象: 将实例化好的代理对象强制转换为你指定的接口类型(如 IService)返回给你。

核心奥秘:

  • 动态代理对象 ($Proxy0) 只是一个空壳转发器 。它实现了接口,但自身没有任何业务逻辑
  • 所有接口方法的实现逻辑都委托 给了关联的 InvocationHandlerinvoke 方法。
  • InvocationHandler 持有对真实目标对象 的引用,并在 invoke 方法中决定何时、如何调用真实对象的方法(甚至完全不调用,或者调用其他对象的方法)。

3. 源码调用链路 (Source Call Chain)

当你调用 proxyService.doSomething("重要项目") 时,发生了什么?让我们追踪一下(简化版,聚焦核心):

  1. proxyService.doSomething("重要项目")

    • proxyService$Proxy0 的实例。
  2. $Proxy0.doSomething(String task) (动态生成的类中的方法)

    typescript 复制代码
    public final void doSomething(String task) {
        try {
            // h 是父类Proxy中的字段,指向你的MyInvocationHandler实例
            // m3 是在类初始化时获取的Method对象 (代表IService.doSomething)
            super.h.invoke(this, m3, new Object[]{task}); // 关键调用!
        } catch (RuntimeException | Error e) { ... }
        catch (Throwable e) { ... }
    }
  3. MyInvocationHandler.invoke(Object proxy, Method method, Object[] args)

    • proxy 参数传入的是 proxyService (即 $Proxy0 实例本身)。

    • method 参数传入的是 m3 (代表 IService.doSomething)。

    • args 参数传入的是 new Object[]{"重要项目"}

    • 现在执行你写在 invoke 方法里的代码:

      • 记录日志:"秘书: 有人要调用方法 'doSomething'"

      • 记录参数:"秘书: 参数是: [重要项目]"

      • 核心委托: Object result = method.invoke(realObject, args);

        • methodIService.doSomething 的反射对象。
        • realObject 是你的 RealService 实例。
        • args["重要项目"]
        • 这行代码最终调用了 RealService.doSomething("重要项目")
      • 真实对象执行:Log.d("RealService", "Doing real work: 重要项目")

      • 记录结果:"秘书: 方法调用完成,结果是: null" (因为返回void)

      • 返回 null

  4. 控制流回到 $Proxy0.doSomething:它不做其他事,直接结束。

  5. 控制流回到你的调用代码 :继续执行下一行 (int sum = proxyService.calculate(5, 3);),过程类似。

关键路径:

scss 复制代码
调用者代码 -> $Proxy0.接口方法() -> Proxy.h.invoke() (即MyInvocationHandler.invoke()) -> Method.invoke(realObject, args) -> RealObject.接口方法()

这个路径中,InvocationHandler.invoke 是绝对的核心枢纽

4. 相关的关键概念 (Key Concepts)

  1. 接口 (Interface): 动态代理的基石。代理对象必须实现一个或多个接口。它定义了代理对象能"代理"哪些方法。没有接口,就无法使用JDK动态代理 (这是它与 CGLIB 等基于继承的代理的主要区别)。

  2. java.lang.reflect.Proxy: 核心工厂类。提供静态方法 newProxyInstance(...) 来创建动态代理对象。它负责在运行时生成代理类字节码、加载类并实例化对象。

  3. java.lang.reflect.InvocationHandler: 核心处理器接口。只有一个方法 Object invoke(Object proxy, Method method, Object[] args)。所有对代理对象方法的调用都会被路由到这里。你在这里实现代理的逻辑(增强、委托等)。

  4. 代理对象 ($Proxy0, $Proxy1, ...): 在运行时动态生成的类。它:

    • 继承自 java.lang.reflect.Proxy
    • 实现你指定的接口。
    • 为每个接口方法生成实现,该实现只是简单地调用关联的 InvocationHandlerinvoke 方法。
  5. 目标对象 / 被代理对象 (Real Object / Target Object): 真正执行业务逻辑的对象实例。InvocationHandler 持有它的引用,并在 invoke 方法内部决定是否及如何调用它的方法。

  6. 反射 (Reflection): Method 对象及其 invoke 方法的使用是动态代理实现委托的核心技术。它允许在运行时动态调用方法。

  7. AOP (Aspect-Oriented Programming - 面向切面编程): 动态代理是实现 AOP 的一种重要技术。通过在 InvocationHandler.invoke 中添加代码(称为"通知" - Advice),你可以将横切关注点 (如日志、事务、安全、性能监控)与核心业务逻辑解耦,模块化地应用到多个目标方法上。

  8. 委托 (Delegation): 动态代理模式的本质是委托模式 。代理对象将方法的调用委托给 InvocationHandler,而 InvocationHandler 通常又委托给目标对象,并在委托前后添加额外逻辑。

Android 中的注意事项

  1. 性能: 动态代理涉及反射调用 (method.invoke()) 和动态类生成,相比直接调用会带来一定的性能开销。在性能极度敏感的路径(如高频循环)中需谨慎使用。Android 7.0 (API 24) 之前 ,动态生成代理类使用的是 sun.misc.ProxyGenerator (或类似私有API),性能相对较差。Android 7.0 及以后 ,ART 使用了更优化的 java.lang.reflect.ProxyBuilder (内部 Native 实现),性能有显著提升。
  2. 接口限制: 只能代理接口方法,不能代理类本身的非接口方法。
  3. 混淆 (ProGuard/R8): 如果你的接口、InvocationHandler 或目标对象会被混淆,需要确保它们在混淆规则中保留(使用 -keep 规则),否则运行时动态代理会因找不到类或方法而崩溃。
  4. InvocationHandler 中的耗时操作:invoke 方法中执行的逻辑,尤其是委托调用 (method.invoke()),会阻塞调用者线程。如果这里有网络请求等耗时操作,必须在后台线程执行,否则会阻塞 UI 线程导致 ANR。
  5. 调试: 生成的代理类名字如 $Proxy0,在调试时看起来可能不太直观。

扩展应用场景

  1. 权限检查代理

    kotlin 复制代码
    class PermissionInvocationHandler(
        private val target: Any,
        private val permission: String
    ) : InvocationHandler {
        override fun invoke(proxy: Any, method: Method, args: Array<out Any>?): Any? {
            if (hasPermission(permission)) {
                return method.invoke(target, *(args ?: emptyArray()))
            } else {
                requestPermission(permission)
                return null
            }
        }
    }
  2. 日志记录代理

    kotlin 复制代码
    class LoggingInvocationHandler(
        private val target: Any
    ) : InvocationHandler {
        override fun invoke(proxy: Any, method: Method, args: Array<out Any>?): Any? {
            Log.d("EventProxy", "Calling ${method.name} with ${args?.contentToString()}")
            val result = method.invoke(target, *(args ?: emptyArray()))
            Log.d("EventProxy", "Method ${method.name} completed")
            return result
        }
    }
  3. 性能监控代理

    kotlin 复制代码
    class PerformanceInvocationHandler(
        private val target: Any
    ) : InvocationHandler {
        override fun invoke(proxy: Any, method: Method, args: Array<out Any>?): Any? {
            val start = System.currentTimeMillis()
            val result = method.invoke(target, *(args ?: emptyArray()))
            val duration = System.currentTimeMillis() - start
            if (duration > 16) Log.w("Perf", "${method.name} took ${duration}ms")
            return result
        }
    }

总结

Android 中的动态代理是一种强大的运行时技术,它允许你创建一个实现了指定接口的对象,但实际的方法调用会被转发到一个 InvocationHandler 。这个 Handler 就像是一个集中处理器,它可以在调用真实对象方法前后插入自定义逻辑 (日志、权限、缓存、监控等),是实现 AOP、解耦、增强功能的利器。

核心流程:

  1. 定义接口 (IService)。
  2. 实现目标对象 (RealService)。
  3. 实现 InvocationHandler (MyInvocationHandler),在其中持有目标对象引用,并在 invoke 方法中处理调用(包括委托给目标对象)。
  4. Proxy.newProxyInstance() 创建代理对象 ,传入接口列表和 InvocationHandler
  5. 通过代理对象调用方法 ,实际工作由 InvocationHandler.invoke() 完成。
相关推荐
子林super6 分钟前
主从数据全量迁移到分片集群测试
前端
Spider_Man8 分钟前
🚀 TypeScript从入门到React实战:前端工程师的类型安全之旅
前端·typescript
TheRedAce11 分钟前
Select对于onChange的prop进行警告
前端·javascript
Mishi12 分钟前
Cursor前端初体验-不懂MCP?那就做一个MCP
前端
归于尽14 分钟前
从按钮 "跳帧" 到 3D 翻书:CSS 动画进阶的 "三级跳"
前端·css
Cache技术分享17 分钟前
131. Java 泛型 - 目标类型与泛型推断
前端·后端
浅墨momo25 分钟前
介绍一下Shopify App 的 Theme App Extensions
前端·创业
1_2_3_26 分钟前
利用高德地图怎么单独显示某个省份的轮廓
前端
gyeolhada27 分钟前
Web: 基础知识、HTML、CSS、JavaScript(英文版--知识点学习/复习)
前端·javascript·css3·html5·web
荣达33 分钟前
「CoT」巧思还是骗局?
前端·aigc·产品经理