InvocationHandler的核心作用是作为代理对象方法调用的最终执行者,所有调用均被转发至invoke方法,由开发者显式决定是否及如何执行原逻辑(如method.invoke(target, args)),而非自动拦截或分阶段增强。

InvocationHandler 的核心作用是接管代理对象的方法调用
它不是用来“拦截”方法的中间件,而是方法调用的最终执行者——所有对代理对象的方法调用,都会被转发到 invoke 方法里,由你决定怎么处理。JDK 动态代理不支持“前置/后置拦截”这种分阶段钩子,只有统一入口 invoke(Object proxy, Method method, Object[] args)。
常见错误现象:NullPointerException 在 invoke 里访问 method.getDeclaringClass() 后直接调用 method.invoke(null, ...)(忘了静态方法才能传 null);或误以为能跳过原逻辑自动继续执行,结果没调用 method.invoke(target, args) 导致功能丢失。
- 必须显式调用
method.invoke(target, args)才能触发原始逻辑(target是你传入的真实对象) -
proxy参数是代理对象本身,可用于proxy.getClass().getName()或proxy instanceof SomeInterface - 若想跳过原方法,直接返回自定义值,就别调
method.invoke,但要确保返回类型兼容 - 异常必须显式抛出或包装,JVM 不会自动透传:原方法抛
IOException,你不能只 catch 了事,得throw new RuntimeException(e)或重新 throw 原异常
为什么必须用接口 + Proxy.newProxyInstance,而不能代理类
JDK 动态代理只支持接口代理,这是硬限制。试图代理一个普通类(如 new Person())会直接抛 IllegalArgumentException: target class is not an interface。
使用场景很明确:你手上有 SomeService 接口和它的实现类 SomeServiceImpl,想在不改实现类的前提下加日志、权限、重试等逻辑——这时才轮到 InvocationHandler 出场。
- 代理对象的类型是接口类型(如
(SomeService) proxy),不是实现类类型 -
Proxy.newProxyInstance第三个参数必须是InvocationHandler实例,不能为null - 类加载器要用目标接口的类加载器(
SomeService.class.getClassLoader()),跨模块时用错会导致ClassNotFoundException或代理失效 - 如果需要代理类(非接口),得换 CGLIB 或 Javassist,它们原理不同,不走
InvocationHandler
invoke 方法里怎么区分是哪个方法被调了
靠 method.getName() 和 method.getParameterTypes() 组合判断,而不是仅靠方法名——重载方法名字相同但参数不同,必须一起比对。
性能影响很小,但写法不当容易漏判。比如只写 "save".equals(method.getName()),遇到 save(String) 和 save(String, int) 就无法区分。
- 推荐用
Method.equals()对比整个签名:method.equals(SomeService.class.getMethod("save", String.class)) - 更轻量的做法是先比名,再比参数个数和类型名:
method.getName().equals("save") && method.getParameterCount() == 1 && method.getParameterTypes()[0] == String.class - 避免用
toString()解析方法签名,不可靠且慢 - 如果方法来自父接口(如
SomeService extends BaseService),method.getDeclaringClass()返回的是声明该方法的接口,不一定是SomeService.class
代理对象调用 toString / hashCode / equals 时的陷阱
这些方法来自 Object,但代理对象没有继承关系,所以默认行为不符合直觉:直接调 proxy.toString() 会返回类似 com.sun.proxy.$Proxy1@3d4eac69,而不是目标对象的 toString() 结果。
这不是 bug,是 JDK 明确设计的行为。如果你希望代理对象表现得像真实对象,就得在 invoke 里手动转发这仨方法。
-
toString:应调用target.toString(),否则日志打印全是$ProxyN -
hashCode:应返回target.hashCode(),否则放入HashSet会重复 -
equals:需判断args[0] == proxy(自比较),或args[0] instanceof Proxy并比对内部target,否则proxy.equals(anotherProxy)永远是false - 注意:不要无条件转发所有
Object方法——比如你加了权限校验,proxy.equals()也该走校验逻辑,而不是绕过
Object 方法的处理,以及重载方法的精准识别。这两处不补全,代理对象在集合操作、日志、单元测试里就会悄悄出问题。

















