java - 对动态代理的调用应该转到动态类型的方法还是静态类型的方法?
问题描述
动态代理接收的方法对象似乎是引用类型而不是对象类型,但只有在方法签名中涉及泛型时。它应该那样工作吗?
例子:
public class ProxyTest implements InvocationHandler {
public static interface A<T> {
void test(T t);
}
public static interface B extends A<String> {
@C
@Override
void test(String e);
}
@Retention(RetentionPolicy.RUNTIME)
public static @interface C {}
public static void main(String[] args) {
Class<A> a = A.class;
Class<? extends A<String>> bAsA = B.class;
Class<B> b = B.class;
A aProxy = ((A) Proxy.newProxyInstance(a.getClassLoader(), new Class[] {a}, new ProxyTest()));
A bAsAProxy = ((A) Proxy.newProxyInstance(bAsA.getClassLoader(), new Class[] {bAsA}, new ProxyTest()));
B bProxy = ((B) Proxy.newProxyInstance(b.getClassLoader(), new Class[] {b}, new ProxyTest()));
A bProxyAssignedToA = bProxy;
aProxy.test("");
bAsAProxy.test("");
bProxy.test("");
bProxyAssignedToA.test("");
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
System.out.println(method.getDeclaringClass().getSimpleName() + ": " + (method.getAnnotation(C.class) != null ? "C" : "null"));
return null;
}
}
我希望它打印:
A: null
B: C
B: C
B: C
但实际输出是
A: null
B: null
B: C
B: null
当我将 B 的泛型更改为 Object 或将其删除时,它会正确打印:
A: null
B: C
B: C
B: C
解决方案
当我使用 Java 8 或更高版本编译并运行您的示例时,我会得到您期望的输出:
A: null
B: C
B: C
B: C
如果您将调用处理程序代码更改为
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
System.out.println(method.getDeclaringClass().getSimpleName()
+ "." + method.getName()
+ Arrays.toString(method.getParameterTypes())
+ ": " + (method.getAnnotation(C.class) != null ? "C" : "null"));
return null;
}
你会得到
使用 Java 8 或更高版本编译:A.test[class java.lang.Object]: null
B.test[class java.lang.Object]: C
B.test[class java.lang.String]: C
B.test[class java.lang.Object]: C
使用较旧的 Java 版本编译:
A.test[class java.lang.Object]: null
A.test[class java.lang.Object]: null
B.test[class java.lang.String]: C
A.test[class java.lang.Object]: null
为了进一步说明问题,请将以下内容添加到您的main
方法中
Class<?>[] classes = { A.class, B.class };
for(Class<?> c: classes) {
System.out.println(c);
for(Method m: c.getDeclaredMethods()) {
for(Annotation a: m.getDeclaredAnnotations())
System.out.print(a+" ");
System.out.println(m);
}
System.out.println();
}
它会打印
interface ProxyTest$A
public abstract void ProxyTest$A.test(java.lang.Object)
interface ProxyTest$B
@ProxyTest$C() public abstract void ProxyTest$B.test(java.lang.String)
使用 8 之前的 Java 版本编译时。
由于类型擦除,A
接口只声明了一个带有 type 的方法,当在编译时 type 的引用上Object
调用时总是会调用该方法。该接口声明了一个带有参数类型的专用版本,仅当引用的编译时类型为 时才会调用该版本。test
A
B
String
B
实现类必须实现这两种方法,您通常不会注意到,因为编译器会自动为您实现一个桥接方法here test(Object)
,它将转换参数并调用真正的实现方法 here test(String)
。但是您在生成代理时会注意到,该代理将为任一方法调用您的调用处理程序,而不是实现桥接逻辑。
当您在 Java 8 或更高版本下编译和运行代码时,它将打印
interface ProxyTest$A
public abstract void ProxyTest$A.test(java.lang.Object)
interface ProxyTest$B
@ProxyTest$C() public abstract void ProxyTest$B.test(java.lang.String)
@ProxyTest$C() public default void ProxyTest$B.test(java.lang.Object)
现在,接口B
有自己的桥接方法,这成为可能,因为 Java 现在支持abstract
接口中的非方法。由于参数类型,您可以注意到代理仍然覆盖两者,但由于编译器将在实际接口方法中声明的所有注释复制到桥接方法,您将在调用处理程序中看到它们。此外,现在声明的类是预期的类B
。
请注意, 的运行时行为Proxy
没有改变,这是编译器产生的影响。因此,您需要重新编译您的源代码,以便从新版本中受益(并且结果不会在旧版本上运行)。
推荐阅读
- visualization - Tableau 中的数据合并
- sql - 查询数据得到平均成绩
- sql - 我无法在 SQL Server 2005 中放置默认架构
- ios - 带有外部站点的 Ionic 4 ion-content iframe 使用粘性标头
- google-analytics - 谷歌标签管理器不跟踪链接点击图像和图标
- sql - 多列的 SELECT 和 INSERT 之间的竞争条件
- google-apps-script - 使用触发器根据 X 列中的值移动行
- reactjs - 反应导航抽屉 OnPress 未正确触发
- javascript - 我如何使用 javascript fetch api 插入数据
- javascript - 如何将画布图像从 javascript 上传到 Django