Byte Buddy: создание реализации для абстрактного класса

Я хотел бы создать реализацию во время выполнения для абстрактного класса с использованием Byte Buddy, и я столкнулся с проблемой, что java.lang.AbstractMethodError выдается при вызове метода из созданного экземпляра. У меня есть такой существующий класс abstract (который я на самом деле не могу изменить и который на самом деле содержит больше логики):

public abstract class Algorithm {
    abstract int execute();
}

Используя следующий минимальный образец, я хотел бы, чтобы мой экземпляр Algorithm возвращал постоянное значение:

Class<?> type = new ByteBuddy()
                        .subclass(Algorithm.class)
                        .method(ElementMatchers.named("execute"))
                        .intercept(FixedValue.value(42))
                        .make()
                        .load(classLoader, ClassLoadingStrategy.Default.WRAPPER)
                        .getLoaded();
Algorithm instance = (Algorithm) type.newInstance();
System.out.println(myInstance.execute());

Однако это приводит к следующему исключению:

Exception in thread "main" java.lang.AbstractMethodError: package.Algorithm.execute()I

(когда я экспериментально меняю Algorithm на interface, все работает нормально, но это не решает мою конкретную проблему).


person qqilihq    schedule 10.01.2016    source источник


Ответы (1)


Это может вас удивить, но точно то же самое произойдет, если вы сгенерируете тот же код, используя javac, используя ту же настройку загрузчика классов. То, что вы наблюдаете, подразумевается тем, как в JLS указана конфиденциальность пакетов. Ваш класс не интерфейс

public abstract class Algorithm {
  abstract int execute(); 
}

определяет закрытый для пакета метод. Поскольку вы не определяете пользовательское имя для сгенерированного класса, Byte Buddy создает подкласс со случайным именем, который находится в том же пакете. Byte Buddy далее обнаруживает метод executable как переопределяемый из сгенерированного подкласса и реализует его именно так, как вы ожидаете.

Однако вы используете стратегию ClassLoadingStrategy.Default.WRAPPER для загрузки класса, который создает новый загрузчик дочернего класса того, который загружает Algorithm. Однако в Java во время выполнения два пакета равны только в том случае, если имя пакета одинаково и оба пакета загружаются одним и тем же ClassLoader. Последнее условие неверно для вашего случая, так что JVM больше не применяет полиморфизм к классу execute. Позвонив

((Algorithm) type.newInstance()).execute();

поэтому вы вызываете не сгенерированный метод, а исходный абстрактный метод. Поэтому - в соответствии с JLS - выбрасывается AbstractMethodError.

Чтобы решить эту проблему, вам нужно либо загрузить сгенерированный класс в том же пакете, используя стратегию INJECTION по умолчанию, либо вы должны определить execute как метод public (это неявно при определении интерфейса) или protected, чтобы правила полиморфизма что вы ожидаете применить. В качестве третьего варианта вы можете вызвать правильный метод времени выполнения,

type.getDeclaredMethod("execute").invoke(type.newInstance());
person Rafael Winterhalter    schedule 10.01.2016
comment
Спасибо, Рафаэль, за ваше очень подробное объяснение, так как случайно я обнаружил две минуты назад, что корень моей проблемы был вызван тем фактом, что абстрактный метод был частным пакетом. INJECTION - мое решение. Кстати, отличная работа над Byte Buddy! - person qqilihq; 11.01.2016