InvocationHandler的核心职责是代理逻辑分发,增强代码须写在invoke方法内并显式调用method.invoke(target, args);它只支持接口代理,需注意target非空、泛型桥接方法匹配及基本类型返回值处理。

InvocationHandler 的核心职责是代理逻辑分发
它不是用来直接写业务增强代码的地方,而是承接 Proxy.newProxyInstance 发来的调用请求,并决定如何转发、改写或拦截。真正做增强(比如日志、权限、重试)的代码必须放在 invoke 方法体内,且必须显式调用 method.invoke(target, args) 才能触发原逻辑——漏掉这句,目标方法就永远不会执行。
常见错误现象包括:
- 代理对象调用后静默无反应(没调 method.invoke)
- 报 NullPointerException(target 为 null 或未正确传入)
- 增强逻辑只在特定方法生效(忘了对 method.getName() 做判断,或条件写错)
使用场景集中在需要统一横切逻辑又不想侵入原类时,比如 SDK 封装、测试桩替换、接口调用埋点。注意它只对接口有效,不能代理普通 class(那是 CGLIB 的事)。
invoke 方法里必须处理好三类参数
Object proxy 是生成的代理对象本身,一般不用;Method method 描述被调用的方法,可用于反射调用或元信息判断;Object[] args 是实参数组,可修改(比如加默认值、脱敏、转换类型),但要注意原方法签名是否接受。
关键细节:
- args 是原始传入值,若增强逻辑中修改了数组元素(如 args[0] = "new"),会影响后续 method.invoke 的行为
- 若方法返回基本类型(如 int),而 invoke 返回 null,会抛 NullPointerException;需用包装类判空或兜底返回默认值
- 异常不捕获会原样抛出,若想统一处理(如把 SQLException 转成 RuntimeException),得在 try-catch 里做
示例片段:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("toString".equals(method.getName())) {
return "EnhancedProxy";
}
System.out.println("Before " + method.getName());
try {
Object result = method.invoke(target, args);
System.out.println("After " + method.getName());
return result;
} catch (InvocationTargetException e) {
throw e.getTargetException(); // 不要直接 throw e
}
}
代理对象创建时 target 实例不能为 null
Proxy.newProxyInstance 只负责生成字节码,不管理目标对象生命周期。你得自己确保 target 在 invoke 被调用时已初始化且非 null。否则一调用就崩,错误信息是 NullPointerException,但堆栈指向 invoke 内部,容易误判。
常见疏漏点:
- 把 target 声明为局部变量,在 InvocationHandler 构造后被 GC(尤其在 lambda 或匿名内部类中)
- 多线程环境下共享同一个 InvocationHandler 实例,但 target 是可变的,导致 A 线程刚 set,B 线程就 get 到 null
- 使用 Spring 时混淆了代理对象和被代理 bean,误以为 @Autowired 进来的就是 target
稳妥做法是:在 InvocationHandler 构造器中接收并 final 保存 target,不提供 setter。
泛型方法和桥接方法会导致 method 匹配失效
Java 编译器为泛型生成的桥接方法(bridge method)和原始方法共存,它们有相同名字但签名不同。如果仅靠 method.getName() 判断,可能误匹配到桥接方法,导致 method.invoke 报 IllegalArgumentException(参数类型不匹配)。
解决方式:
- 用 method.getDeclaringClass() 和 method.getName() + method.getParameterTypes() 联合判断,避免单靠名字
- 或直接检查 method.isBridge(),跳过桥接方法(除非你明确需要拦截它)
- 更稳妥的是:用 target.getClass().getMethod(method.getName(), method.getParameterTypes()) 动态获取真实方法引用,再 invoke(但注意性能开销)
这个坑在涉及泛型集合操作(如 List<string> getList()</string>)或函数式接口代理时特别明显,不留意就会在运行时才暴露。
代理逻辑越薄越好,invoke 里别塞太多业务判断;真要复杂路由或条件增强,抽成独立策略类更可控。

















