Как мы можем получить доступ к методам, сгенерированным ByteBuddy во время компиляции?

Я написал этот пример:

E someCreateMethod(Class<E> clazz) {
    Class<? extends E> dynamicType = new ByteBuddy()
            .subclass(clazz)
            .name("NewEntity")
            .method(named("getNumber"))
            .intercept(FixedValue.value(100))
            .defineField("stringVal", String.class, Visibility.PRIVATE)
            .defineMethod("getStringVal", String.class, Visibility.PUBLIC)
            .intercept(FieldAccessor.ofBeanProperty())
            .make()
            .load(clazz.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
            .getLoaded();

    return dynamicType.newInstance();
}

И я хотел бы использовать его для получения переопределенного атрибута number:

Integer num = someCreateMethod(EntityExample.class).getNumber();  //(1)

Или, чтобы получить вновь определенный атрибут stringVal:

String sVal = someCreateMethod(EntityExample.class).getStringVal(); //(2)  

Моя проблема в том, что (1) работает очень хорошо, а (2) нет. Я получаю следующую ошибку:

Error:(40, 67) java: cannot find symbol

symbol:   method getStringVal()

Кроме того, можно ли сделать что-то подобное с динамически сгенерированным классом:

NewEntity newEntity = someCreateMethod(EntityExample.class);
Integer num = newEntity.getNumber();
String sVal = newEntity.getStringVal();

?

РЕДАКТИРОВАТЬ: я ценю вашу помощь, этот пример был моей первой попыткой использования библиотеки ByteBuddy. Я понял, что defineMethod на самом деле определяет реализацию метода интерфейса, а не просто добавляет случайный метод в класс. Поэтому я решил объяснить здесь, чего именно я пытаюсь достичь.

Для каждого атрибута Date в классе E я хочу добавить еще два поля (и соответствующие им геттеры и сеттеры), скажем, (atribute name)InitialDate и (atribute name)FinalDate, чтобы я мог использовать функциональные интервалы для каждой даты в E.

Мне было интересно, могу ли я использовать генерацию кода для добавления этих методов без необходимости создавать подклассы для каждого E.

PS: E изменить нельзя, он принадлежит устаревшему модулю.

PS2: я не знаю, сколько атрибутов даты будет в каждой сущности E, но новые атрибуты и методы будут созданы с использованием соглашений (например, __FisrtDay , __LastDay), как показано ниже:

NewA a = eb.create(A.class);
a.getDeadLine(); //inherited
a.getDeadLineFirstDay(); //added 
a.getDeadLineLastDay(); //added

NewA b = eb.create(B.class);
b.getBirthday(); //inherited
b.getBirthdayFirstDay(); //added
b.getBirthdayLastDay(); //added

b.getAnniversary(); //inherited
b.getAnniversaryFirstDay(); //added
b.getAnniversaryLastDay(); //added

PS3: То, что я пытаюсь сделать, возможно с ByteBuddy или вообще возможно? Есть ли другой способ?

PS4: Должен ли мой РЕДАКТИРОВАТЬ быть новым вопросом?


person Reya Gistrout    schedule 16.01.2017    source источник
comment
Я собираюсь предположить, что getNumber определен в интерфейсе EntityExample, а getStringVal — нет.   -  person Joe C    schedule 17.01.2017
comment
Кроме того, простите за глупый вопрос с моей стороны... но почему вы думаете, что хотите это сделать?   -  person Joe C    schedule 17.01.2017
comment
@Joe C Ну, ByteBuddy позволяет нам определять новые методы, верно? (начиная с части .defineMethod) Поэтому я думаю, что это должно дать нам какой-то способ доступа к методам, которые мы создаем... Я просто не знаю, как это сделать. Что касается вашего второго комментария, мне нужно динамически создавать подклассы любого класса, который я передаю в качестве параметра методу someCreateMethod, и эти подклассы должны реализовывать два новых метода. Вот почему я хочу этого.   -  person Reya Gistrout    schedule 17.01.2017


Ответы (2)


Вам нужно, чтобы E был суперклассом/или интерфейсом, который включает в себя методы, которые вы пытаетесь вызвать, — вы не сможете разрешать подтипы методов, которых нет в E.

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

Например, мы могли бы использовать супертип «ValueProvider», а затем использовать ByteBuddy для определения IntConstantProvider.

public interface ValueProvider<T> {
    public T getValue();
}

Class<? extends ValueProvider<Integer>> dynamicType = new ByteBuddy()
    .subclass(clazz)
    .name("ConstantIntProvider")
    .method(named("getValue"))
    .intercept(FixedValue.value(100))
    // etc.

В вашем прототипе было 3 отдельных функциональности (если мы считаем нереферентные частные поля заглушкой некоторого предполагаемого поведения) без какой-либо очевидной абстракции для их охвата. Это могло бы быть лучше спроектировано как 3 простых атомарных поведения, для которых абстракции были бы очевидны.

Вы можете использовать отражение для поиска произвольных методов в произвольном динамически определяемом классе, но это не имеет особого смысла с точки зрения кодирования или дизайна (откуда ваш код узнает, какие методы вызывать? Если он знает, почему бы не использовать тип для выразить это?), и это не очень эффективно.

СЛЕДУЮЩЕЕ ИЗМЕНЕНИЕ ВОПРОСА — свойства Java Bean работают путем отражения, поэтому пример поиска «связанных свойств» (таких как первая/последняя дата) из известных свойств не лишен смысла.

Однако можно рассмотреть возможность использования класса DateInterval(FirstDate, LastDate), чтобы для каждого базового свойства требовалось только одно дополнительное свойство.

person Thomas W    schedule 16.01.2017
comment
Спасибо, Томас! Не могли бы вы взглянуть на мой РЕДАКТИРОВАТЬ вопрос? Я попытался объяснить лучше, что я пытаюсь сделать. Любые идеи/предложения/коррекции приветствуются. - person Reya Gistrout; 17.01.2017
comment
Спасибо за разъяснение вашей цели @ReyaGistrout, отредактировал мой ответ. - person Thomas W; 17.01.2017

Как указывает Томас, Byte Buddy генерирует классы во время выполнения, так что ваш компилятор не может проверить их существование во время компиляции.

Что вы можете сделать, так это применить генерацию кода во время сборки. Если ваш EntityExample.class существует в определенном модуле, вы можете улучшить этот модуль с помощью плагина Byte Buddy Maven или Gradle, а затем, после улучшения, позволить вашему компилятору проверить их существование.

Что вы также можете сделать, так это определить интерфейсы, такие как

interface StringVal {
  String getStringVal();
}

который вы можете попросить Byte Buddy реализовать в своем подклассе, что позволит вашему компилятору проверить существование метода, если вы представляете свой подкласс как этот интерфейс.

Кроме этого, ваш компилятор делает именно то, что он должен делать: сообщает вам, что вы вызываете метод, который не существует (в то время).

person Rafael Winterhalter    schedule 17.01.2017