动态代理方法调用目标疑问及泛型方法签名下Method异常咨询
关于Java动态代理与泛型方法的两个问题解答
1. 动态代理的方法调用,到底指向动态类型还是静态类型的方法?
其实这事儿得看方法签名的匹配逻辑(还要考虑泛型擦除的影响):
- 当你用某个静态类型的引用调用方法时,JVM会先按照这个静态类型擦除泛型后的方法签名,去代理类里找对应的处理逻辑。
- 代理类会为它实现的每一个接口的每一个方法都生成对应的处理代码,所以如果你的静态类型是父接口,哪怕代理实际实现的是子接口,拦截到的也可能是父接口的方法——除非子接口的方法在泛型擦除后和父接口的签名完全一致,没有桥接方法搞事情。
就拿你的代码举例:bAsAProxy的静态类型是A,调用test("")时,JVM匹配的是A接口擦除后的test(Object)方法,所以invoke里拿到的就是A类的test方法;而bProxy是B类型的引用,调用时匹配的是B的test(String),自然就能拿到带@C注解的方法。
2. 泛型方法签名下,动态代理拿到的Method属于引用类型而非实际实现类的,这是正常设计吗?
太正常了!这完全是Java泛型擦除和桥接机制导致的:
- 当
B继承A<String>并重写test(String)时,泛型擦除后,父接口A<T>的test(T)会变成test(Object)。为了让B满足父接口的契约,编译器会给B自动生成一个桥接方法test(Object),这个方法内部会调用你写的test(String)。 - 但当你用
A<String>类型的引用调用test时,JVM找的是擦除后的test(Object)签名对应的方法——在代理场景里,这个方法对应的Method对象是父接口A的那个,不是B的桥接方法也不是你写的test(String),所以自然看不到@C注解。 - 要是你把
B的泛型改成Object或者直接去掉泛型,A<Object>的test(Object)和B的test(Object)签名就完全一致了,编译器不会生成桥接方法,这时候不管用A还是B的引用调用,匹配的都是B的test方法,输出就符合预期了。
给你加个调试验证
你可以在invoke方法里加一行打印参数类型的代码,就能更直观看到差异:
@Override public Object invoke(Object proxy, Method method, Object[] args) { System.out.println(method.getDeclaringClass().getSimpleName() + ": " + (method.getAnnotation(C.class) != null ? "C" : "null") + ", 参数类型: " + Arrays.toString(method.getParameterTypes())); return null; }
运行后会看到:
aProxy.test(""):A: null, 参数类型: [class java.lang.Object](A擦除后的方法)bAsAProxy.test(""):A: null, 参数类型: [class java.lang.Object](匹配A的擦除后方法)bProxy.test(""):B: C, 参数类型: [class java.lang.String](B的实际重写方法)bProxyAssignedToA.test(""):A: null, 参数类型: [class java.lang.Object](还是匹配A的擦除后方法)
这样就把背后的逻辑展示得明明白白啦~
内容的提问来源于stack exchange,提问作者Olard
相关产品推荐
相关产品推荐

