J'ai écrit cet exemple:

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();
}

Et je voudrais l'utiliser pour obtenir l'attribut number redéfini:

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

Ou pour obtenir l'attribut stringVal nouvellement défini:

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

Mon problème est que (1) fonctionne assez bien, tandis que (2) ne fonctionne pas. J'obtiens l'erreur suivante:

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

symbol:   method getStringVal()

De plus, est-il possible de faire quelque chose comme ça avec une classe générée dynamique:

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

?

EDIT: J'apprécie votre aide, cet exemple était ma première tentative d'utilisation de la bibliothèque ByteBuddy. J'ai pensé que defineMethod définit en fait une implémentation d'une méthode d'interface, pas simplement ajouter une méthode aléatoire à la classe. J'ai donc décidé d'expliquer ici ce que j'essaie d'accomplir exactement.

Pour chaque attribut Date dans une classe E, je veux ajouter deux autres champs (et leurs getters et setters respectifs), disons (atribute name)InitialDate et (atribute name)FinalDate, afin que je puisse utiliser la fonctionnalité d'intervalles pour chaque date en E.

Je me demandais si je pouvais utiliser la génération de code pour ajouter ces méthodes sans avoir à créer des sous-classes pour chaque E.

PS: E ne peut pas être modifié, il appartient à un module hérité.

PS2: Je ne sais pas combien d'attributs de date il y aurait dans chaque entité E, mais les nouveaux attributs et méthodes seraient créés en utilisant des conventions (par exemple __FisrtDay, __LastDay), comme indiqué ci-dessous:

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: Ce que j'essaie d'accomplir est-il encore possible avec ByteBuddy ou pas du tout? Y a-t-il un autre moyen?

PS4: Mon EDIT aurait-il dû être une nouvelle question?

2
Reya Gistrout 17 janv. 2017 à 01:17

2 réponses

Meilleure réponse

Vous devez E pour être une superclasse / ou une interface qui inclut les méthodes que vous essayez d'appeler - vous ne pourrez pas résoudre les méthodes sous-typées qui n'existent pas sur E.

Ce n'est pas un problème ByteBuddy, c'est un problème de conception de classe - vous devez concevoir et regrouper les fonctionnalités que vous avez l'intention de générer en parties abstraites, afin qu'elles puissent être exposées via des types qui sont significatifs au moment de la compilation.

Par exemple, nous pourrions utiliser un supertype «ValueProvider», puis utiliser ByteBuddy pour définir un 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.

Votre prototype avait 3 fonctionnalités distinctes (si nous considérons les champs privés non référencés comme le stub d'un comportement prévu) sans abstraction évidente pour les englober. Cela pourrait être mieux conçu comme 3 comportements atomiques simples, pour lesquels les abstractions seraient évidentes.

Vous pouvez utiliser la réflexion pour trouver des méthodes arbitraires sur une classe arbitraire définie dynamiquement, mais ce n'est pas vraiment significatif à partir d'un PDV de codage ou de conception (comment votre code sait-il quelles méthodes appeler? S'il ne sait pas, pourquoi ne pas utiliser un type pour exprimer cela?) ni très performant.

SUIVRE LA MODIFICATION DE LA QUESTION - Les propriétés Java Bean fonctionnent par réflexion, donc l'exemple de recherche de "propriétés associées" (telles que la date de premier / dernier jour) à partir de propriétés connues n'est pas déraisonnable.

Cependant, il pourrait être envisagé d'utiliser une classe DateInterval (FirstDate, LastDate) de sorte qu'une seule une propriété supplémentaire soit nécessaire par propriété de base.

4
Thomas W 17 janv. 2017 à 02:08

Comme Thomas le souligne, Byte Buddy génère des classes au moment de l'exécution de telle sorte que votre compilateur ne peut pas valider leur existence pendant la compilation.

Ce que vous pouvez faire est d'appliquer votre génération de code au moment de la construction. Si votre EntityExample.class existe dans un module spécifique, vous pouvez améliorer ce module avec le plugin Byte Buddy Maven ou Gradle et ensuite, après amélioration, permettre à votre compilateur de valider leur existence.

Ce que vous pouvez également faire serait de définir des interfaces comme

interface StringVal {
  String getStringVal();
}

Que vous pouvez demander à Byte Buddy d'implémenter dans votre sous-classe, ce qui permet à votre compilateur de valider l'existence de la méthode si vous représentez votre sous-classe comme cette interface.

En dehors de cela, votre compilateur fait exactement ce qu'il est censé faire: vous dire que vous appelez une méthode qui n'existe pas (à ce moment-là).

0
Rafael Winterhalter 17 janv. 2017 à 14:02