动态代理最大性能瓶颈是invocationhandler中method.invoke的反射开销,它比直接调用慢30–50倍;应缓存method、避免invoke内耗时操作,并在高性能场景改用静态代理或cglib/bytebuddy。

动态代理里最常被低估的性能瓶颈,不是接口设计,也不是业务逻辑,而是 InvocationHandler 中那一行 method.invoke(target, args) —— 它看起来轻巧,实则承载了完整的反射调用开销。
反射调用是动态代理的性能主因
每次通过代理对象调用方法,实际执行路径是:
代理类 → invoke() 入口 → 反射调用目标方法。
其中反射调用(Method.invoke)本身包含参数封装、访问检查、类型转换、栈帧创建等步骤,基准测试显示:它比直接方法调用慢 30–50 倍。
- 即使目标方法体为空,反射调用本身的开销仍占主导
- 频繁调用 getter/setter 或简单计算方法时,性能落差尤为明显
- Java 9+ 的模块系统还会额外增加
setAccessible(true)的安全校验成本
避免在 invoke 中做耗时操作
invoke 方法是所有代理方法的统一入口,它串行阻塞整个代理实例的调用链。一旦在里面加入 IO、锁、日志写入或复杂条件判断,所有经由该代理的方法都会被拖慢。
- 日志建议异步记录,或仅记录关键字段(如方法名、耗时),避免序列化整个参数数组
- 权限/事务等横切逻辑尽量轻量,把重操作下沉到目标方法内部或拦截器后置阶段
- 绝对不要在
invoke中调用Thread.sleep、System.out.println或数据库查询
缓存 Method 对象能小幅提效
虽然 Method.invoke 本身无法绕过,但可以减少反射查找开销:每次调用前都用 target.getClass().getMethod(...) 是低效的;应提前获取并缓存 Method 实例。
- 在
InvocationHandler构造时,按需缓存常用方法(如toString、hashCode) - 使用
ConcurrentHashMap<string method></string>按方法签名索引,避免重复getDeclaredMethod - 注意缓存的
Method需调用setAccessible(true)(若涉及私有方法),且只对同一类加载器下的类有效
替代方案:该用静态代理或字节码增强时别硬扛
当性能敏感且接口稳定,动态代理就不再是唯一选择:
- 高频调用的核心服务(如订单查询、缓存读取),可回归静态代理 + 编译期生成(如 Lombok
@Delegate或 MapStruct) - 需要代理具体类(非接口)或彻底规避反射,CGLIB 或 ByteBuddy 更合适 —— 它们生成的是真实子类字节码,调用走虚方法分派,无反射开销
- Spring AOP 在开启
proxy-target-class=true后会自动切换为 CGLIB,前提是目标类不能是final










