Java 对动态代理的调用应该转到动态类型的方法还是静态类型的方法?

Java 对动态代理的调用应该转到动态类型的方法还是静态类型的方法?,java,generics,inheritance,dynamic-proxy,Java,Generics,Inheritance,Dynamic Proxy,动态代理接收的方法对象似乎是引用类型而不是对象类型,但仅当泛型涉及方法签名时。这样行吗 例如: public class ProxyTest implements InvocationHandler { public static interface A<T> { void test(T t); } public static interface B extends A<String> { @C

动态代理接收的方法对象似乎是引用类型而不是对象类型,但仅当泛型涉及方法签名时。这样行吗

例如:

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;
    }
}
public类ProxyTest实现调用处理程序{
公共静态接口A{
孔隙试验(T);
}
公共静态接口B扩展了{
@C
@凌驾
孔隙试验(管柱e);
}
@保留(RetentionPolicy.RUNTIME)
公共静态@接口C{}
公共静态void main(字符串[]args){
a类=a类;

Class当我使用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]:空
测试[类java.lang.Object]:C
B.test[class java.lang.String]:C
测试[类java.lang.Object]:C
使用较旧的Java版本编译:
A.test[class java.lang.Object]:空
A.test[类java.lang.Object]:null
B.test[class java.lang.String]:C
A.test[类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();
}
使用8之前的Java版本编译时

由于类型擦除的原因,
A
接口仅声明类型为
Object
的方法,当对编译时类型
A
的引用调用
test
时,将始终调用该方法。接口
B
声明一个带有
String
参数类型的专用版本,该参数类型仅被调用当引用的编译时类型为
B

实现类必须实现这两种方法,您通常不会注意到这一点,因为编译器将为您自动实现一个桥接方法,这里是
test(Object)
,它将强制转换参数并调用真正的实现方法,这里是
test(String)
。但您确实注意到,当生成一个代理时,它将调用任何一种方法的调用处理程序,而不是实现桥接逻辑

当您在Java8或更新版本下编译和运行代码时,它将打印出来

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)
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现在支持接口中的非
抽象方法。代理仍然覆盖这两个方法,正如您注意到的,由于参数类型,但是由于编译器将在实际接口方法中声明的所有注释复制到桥接方法,所以您将在调用处理程序中看到它们。此外,现在声明的类是预期的类
B


请注意,
代理的运行时行为没有改变,这是编译器造成的。因此,您需要重新编译源代码,以从新版本中获益(并且结果不会在旧版本上运行).

Ah,这也许可以解释为什么eclipse中编译的代码与maven构建的代码表现不同。