2016-01-28 1 views
2

J'essaie de changer la valeur de retour d'une méthode d'une classe déjà chargée. De la documentation de ByteBuddy (http://bytebuddy.net/#/tutorial) cela semble possible en utilisant l'agent Java, tant que je n'ajoute aucun champ/méthode.ByteBuddy - Modifier la valeur par défaut de la classe de chargement

Mon code est le suivant:

ByteBuddyAgent.install(); 

new ByteBuddy() 
     .redefine(StuffImpl.class) 
     .method(returns(Result.class)) 
     .intercept(FixedValue.value(new Result("intercepted"))) 
     .make() 
     .load(StuffImpl.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); 

Mais je reçois l'exception suivante:

java.lang.UnsupportedOperationException: class redefinition failed: attempted to change the schema (add/remove fields) 

La chose est, je ne suis pas une méthode ajoutais. Où Byte Buddy ajoute un champ ou une méthode dans le code ci-dessus?

EDIT:

public class StuffImpl { 

    public Result validate() { 
     return new Result("original"); 
    } 
} 

public class Result { 

    private String result; 

    public Result(String result) { 
     this.result = result; 
    } 
} 

Répondre

2

Vous définissez une délégation à une valeur fixe new Result("intercepted") que Byte amis a besoin de stocker quelque part. L'implémentation FixedValue crée un champ statique pour vous afin que la méthode générée puisse lire votre valeur à partir de celle-ci. Vous pouvez travailler arround de différentes manières en évitant FixedValue, par exemple:

  1. délégué à une autre classe qui détient la valeur dans un champ (conserve l'identité de référence).

    MethodDelegation.to(Holder.class); 
    class Holder { 
        static Result result = new Result("intercepted"); 
        static Result intercept() { return result; } 
    } 
    

    Ceci est l'approche la plus universelle, vous pouvez bien sûr revenir new Result("intercepted") directement à partir de la méthode.

  2. Créer l'instance dynamique:

    MethodCall.construct(Result.class.getDeclaredConstructor(String.class)) 
          .with("intercepted"); 
    

    Dans ce cas, la chaîne "intercepted" n'a pas besoin d'être stocké dans un champ, car il peut être référencé dans la piscine constante de la classe (en va de même pour les valeurs primitives).


Votre StuffImpl définit probablement un initialiseur statique. Cet initialiseur est factorisé par Byte Buddy dans une méthode private de sorte qu'il peut ajouter des instructions supplémentaires.

Vous pouvez désactiver ce comportement en définissant:

new ByteBuddy().with(Implementation.Context.Disabled.Factory.INSTANCE); 

Cela devrait vraiment être dans la documentation et je vais l'ajouter pour la prochaine version.