如何使用InvocationHandler#invoke方法的proxy参数?有哪些用途?
有如下代码:
public class Test { public static void main(String[] args) { InvocationHandler ih = new MyHandler(); ClassLoader cl = Test.class.getClassLoader(); Class[] mapClass = {Map.class}; ((Map)Proxy.newProxyInstance(cl,mapClass,ih)).put("hello", 11); ((Map)Proxy.newProxyInstance(cl,mapClass,ih)).put("hi", 55); } } class MyHandler implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("\nInvoked method `" + method.getName() + "` args: " + Arrays.toString(args)); System.out.println(proxy.getClass()); //how to use proxy parameter? and what purposes it can be used? return 42; } }
运行输出结果如下:
Invoked method `put` args: [hello, 11] class com.sun.proxy.$Proxy0 Invoked method `put` args: [hi, 55] class com.sun.proxy.$Proxy0
提问:调用proxy的非原生方法会触发栈溢出错误,该proxy参数应当如何使用?可以用于哪些场景?
问题解答
栈溢出的原因
JDK动态代理生成的代理实例,所有被代理接口的方法、以及Object类的toString、equals、hashCode方法调用,都会被转发到InvocationHandler的invoke方法执行。如果在invoke内部直接调用proxy的上述方法,就会再次触发invoke回调,形成无限递归,最终抛出StackOverflowError。
示例中调用proxy.getClass()没有触发问题,是因为getClass()是本地方法,JVM做了特殊处理,不会走代理转发逻辑。
proxy参数的正确用法
这个参数本质就是当前生成的代理实例本身,使用时只要避免触发代理递归即可,常见合法用法如下:
- 作为方法返回值返回,实现链式调用。如果被代理的接口方法返回值是接口自身类型,直接返回proxy,就可以支持
proxy.xxx().yyy()的链式调用写法。 - 作为实例标识使用。可以把proxy存入集合、作为缓存key,用来区分不同的代理实例。
- 安全调用不会触发代理的方法。比如示例中调用
getClass()获取代理类信息做日志打印、类型判断都是合法的。 - 作为参数传递给外部方法。如果有外部工具方法需要接收被代理接口的实例,直接传proxy即可,只要不在invoke内部调用它的代理方法就不会有问题。
适用场景
- 链式调用设计的接口代理,比如MyBatis的Mapper接口、各类Builder模式接口的代理,返回proxy实现链式调用。
- 代理实例的身份校验,比如做权限代理时,需要判断当前调用的是哪个代理实例,对应校验不同的权限规则。
- 需要把代理实例透传给其他业务逻辑的场景,比如切面逻辑中需要把代理实例传递给下游方法使用。
内容的提问来源于stack exchange,提问作者the Hutt
相关产品推荐
相关产品推荐

