深入浅出 动态代理

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

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

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

  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() 完成。
相关推荐
石小石Orz14 分钟前
如何将本地文件转成流数据传递给后端?
前端·vue.js
Codebee1 小时前
OneCode核心概念解析——View(视图)
前端·人工智能
GIS之路1 小时前
GIS 数据质检:验证 Geometry 有效性
前端
GIS之路1 小时前
GeoJSON 数据简介
前端
今阳1 小时前
鸿蒙开发笔记-16-应用间跳转
android·前端·harmonyos
前端小饭桌1 小时前
CSS属性值太多记不住?一招教你搞定
前端·css
快起来别睡了1 小时前
深入浏览器底层原理:从输入URL到页面显示全过程解析
前端·架构
阿星做前端1 小时前
一个倒计时功能引发的线上故障
前端·javascript·react.js
莯炗1 小时前
CSS知识补充 --- 控制继承
前端·css·css继承·css控制继承
tianzhiyi1989sq2 小时前
Vue框架深度解析:从Vue2到Vue3的技术演进与实践指南
前端·javascript·vue.js