Когда я компилирую и запускаю ваш пример с 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)
при компиляции с версией Java до 8.
Из-за стирания типа интерфейс A
объявляет только метод с типом Object
, который всегда вызывается при вызове test
для ссылки типа компиляции A
. Интерфейс B
объявляет специализированную версию с параметром String
, который вызывается только тогда, когда тип ссылки во время компиляции равен B
.
Классы реализации должны реализовывать оба метода, которые вы обычно не замечаете, так как компилятор автоматически реализует для вас метод моста , здесь test(Object)
, который приведёт аргумент (ы) ) и вызвать метод реальной реализации, здесь 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 теперь поддерживает не-1039 * методы в интерфейсах. Прокси все еще переопределяет оба, как вы можете заметить из-за типа параметра, но поскольку компилятор скопировал все аннотации, объявленные в фактическом методе интерфейса, в метод bridge, вы увидите их в обработчике вызовов. Кроме того, декларирующий класс теперь является предполагаемым классом B
.
Обратите внимание, что поведение Proxy
во время выполнения не изменилось, это - компилятор, имеющий значение. Поэтому вам нужно будет перекомпилировать исходные коды, чтобы получить выгоду от более новых версий (и результат не будет работать на более старых версиях).